diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 9f808af38e69df7..fa30aee478cf33a 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -2,11 +2,11 @@ FROM docker.io/library/fedora:37 ENV CC=clang -ENV WASI_SDK_VERSION=20 +ENV WASI_SDK_VERSION=21 ENV WASI_SDK_PATH=/opt/wasi-sdk ENV WASMTIME_HOME=/opt/wasmtime -ENV WASMTIME_VERSION=14.0.4 +ENV WASMTIME_VERSION=18.0.3 ENV WASMTIME_CPU_ARCH=x86_64 RUN dnf -y --nodocs --setopt=install_weak_deps=False install /usr/bin/{blurb,clang,curl,git,ln,tar,xz} 'dnf-command(builddep)' && \ diff --git a/.gitattributes b/.gitattributes index 159cd83ff7407d6..5b81d2cb3c90e9c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -83,6 +83,8 @@ Include/opcode_ids.h generated Include/token.h generated Lib/_opcode_metadata.py generated Lib/keyword.py generated +Lib/test/certdata/*.pem generated +Lib/test/certdata/*.0 generated Lib/test/levenshtein_examples.json generated Lib/test/test_stable_abi_ctypes.py generated Lib/token.py generated @@ -95,7 +97,7 @@ Programs/test_frozenmain.h generated Python/Python-ast.c generated Python/executor_cases.c.h generated Python/generated_cases.c.h generated -Python/tier2_redundancy_eliminator_cases.c.h generated +Python/optimizer_cases.c.h generated Python/opcode_targets.h generated Python/stdlib_module_names.h generated Tools/peg_generator/pegen/grammar_parser.py generated diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5dbfbbb8ebaf7e9..e8eed400d961fcb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -38,7 +38,7 @@ Python/ast_opt.c @isidentical Python/bytecodes.c @markshannon @gvanrossum Python/optimizer*.c @markshannon @gvanrossum Python/optimizer_analysis.c @Fidget-Spinner -Python/tier2_redundancy_eliminator_bytecodes.c @Fidget-Spinner +Python/optimizer_bytecodes.c @Fidget-Spinner Lib/test/test_patma.py @brandtbucher Lib/test/test_type_*.py @JelleZijlstra Lib/test/test_capi/test_misc.py @markshannon @gvanrossum @@ -247,5 +247,10 @@ Lib/test/test_interpreters/ @ericsnowcurrently /Tools/wasm/ @brettcannon # SBOM +/Misc/externals.spdx.json @sethmlarson /Misc/sbom.spdx.json @sethmlarson /Tools/build/generate_sbom.py @sethmlarson + +# Config Parser +Lib/configparser.py @jaraco +Lib/test/test_configparser.py @jaraco diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 20d1fad40ecafee..9e236534ae37707 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -97,7 +97,7 @@ jobs: - name: Get a list of the changed documentation-related files if: github.event_name == 'pull_request' id: changed-docs-files - uses: Ana06/get-changed-files@v2.2.0 + uses: Ana06/get-changed-files@v2.3.0 with: filter: | Doc/** @@ -206,6 +206,8 @@ jobs: uses: ./.github/workflows/reusable-macos.yml with: config_hash: ${{ needs.check_source.outputs.config_hash }} + # macos-14 is M1, macos-13 is Intel + os-matrix: '["macos-14", "macos-13"]' build_macos_free_threading: name: 'macOS (free-threading)' @@ -215,6 +217,8 @@ jobs: with: config_hash: ${{ needs.check_source.outputs.config_hash }} free-threading: true + # macos-14 is M1 + os-matrix: '["macos-14"]' build_ubuntu: name: 'Ubuntu' @@ -301,6 +305,14 @@ jobs: - name: SSL tests run: ./python Lib/test/ssltests.py + build_wasi: + name: 'WASI' + needs: check_source + if: needs.check_source.outputs.run_tests == 'true' + uses: ./.github/workflows/reusable-wasi.yml + with: + config_hash: ${{ needs.check_source.outputs.config_hash }} + test_hypothesis: name: "Hypothesis tests on Ubuntu" runs-on: ubuntu-20.04 @@ -472,6 +484,24 @@ jobs: - name: Tests run: xvfb-run make test + build_tsan: + name: 'Thread sanitizer' + needs: check_source + if: needs.check_source.outputs.run_tests == 'true' + uses: ./.github/workflows/reusable-tsan.yml + with: + config_hash: ${{ needs.check_source.outputs.config_hash }} + options: ./configure --config-cache --with-thread-sanitizer --with-pydebug + + build_tsan_free_threading: + name: 'Thread sanitizer (free-threading)' + needs: check_source + if: needs.check_source.outputs.run_tests == 'true' + uses: ./.github/workflows/reusable-tsan.yml + with: + config_hash: ${{ needs.check_source.outputs.config_hash }} + options: ./configure --config-cache --disable-gil --with-thread-sanitizer --with-pydebug + # CIFuzz job based on https://google.github.io/oss-fuzz/getting-started/continuous-integration/ cifuzz: name: CIFuzz @@ -525,10 +555,13 @@ jobs: - build_ubuntu - build_ubuntu_free_threading - build_ubuntu_ssltests + - build_wasi - build_windows - build_windows_free_threading - test_hypothesis - build_asan + - build_tsan + - build_tsan_free_threading - cifuzz runs-on: ubuntu-latest @@ -558,9 +591,12 @@ jobs: build_ubuntu, build_ubuntu_free_threading, build_ubuntu_ssltests, + build_wasi, build_windows, build_windows_free_threading, build_asan, + build_tsan, + build_tsan_free_threading, ' || '' }} diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml index 69c7b45376a411f..48c6f555fdc5a01 100644 --- a/.github/workflows/jit.yml +++ b/.github/workflows/jit.yml @@ -5,13 +5,13 @@ on: - '**jit**' - 'Python/bytecodes.c' - 'Python/optimizer*.c' - - 'Python/tier2_redundancy_eliminator_bytecodes.c' + - 'Python/optimizer_bytecodes.c' push: paths: - '**jit**' - 'Python/bytecodes.c' - 'Python/optimizer*.c' - - 'Python/tier2_redundancy_eliminator_bytecodes.c' + - 'Python/optimizer_bytecodes.c' workflow_dispatch: concurrency: @@ -29,6 +29,7 @@ jobs: target: - i686-pc-windows-msvc/msvc - x86_64-pc-windows-msvc/msvc + - aarch64-pc-windows-msvc/msvc - x86_64-apple-darwin/clang - aarch64-apple-darwin/clang - x86_64-unknown-linux-gnu/gcc @@ -49,6 +50,10 @@ jobs: architecture: x64 runner: windows-latest compiler: msvc + - target: aarch64-pc-windows-msvc/msvc + architecture: ARM64 + runner: windows-latest + compiler: msvc - target: x86_64-apple-darwin/clang architecture: x86_64 runner: macos-13 @@ -70,13 +75,13 @@ jobs: runner: ubuntu-latest compiler: gcc # These fail because of emulation, not because of the JIT: - exclude: test_unix_events test_init test_process_pool test_shutdown test_multiprocessing_fork test_cmd_line test_faulthandler test_os test_perf_profiler test_posix test_signal test_socket test_subprocess test_threading test_venv + exclude: test_unix_events test_init test_process_pool test_shutdown test_multiprocessing_fork test_cmd_line test_faulthandler test_os test_perf_profiler test_posix test_signal test_socket test_subprocess test_threading test_venv test_external_inspection - target: aarch64-unknown-linux-gnu/clang architecture: aarch64 runner: ubuntu-latest compiler: clang # These fail because of emulation, not because of the JIT: - exclude: test_unix_events test_init test_process_pool test_shutdown test_multiprocessing_fork test_cmd_line test_faulthandler test_os test_perf_profiler test_posix test_signal test_socket test_subprocess test_threading test_venv + exclude: test_unix_events test_init test_process_pool test_shutdown test_multiprocessing_fork test_cmd_line test_faulthandler test_os test_perf_profiler test_posix test_signal test_socket test_subprocess test_threading test_venv test_external_inspection env: CC: ${{ matrix.compiler }} steps: @@ -85,19 +90,26 @@ jobs: with: python-version: '3.11' - - name: Windows - if: runner.os == 'Windows' + - name: Native Windows + if: runner.os == 'Windows' && matrix.architecture != 'ARM64' run: | choco install llvm --allow-downgrade --no-progress --version ${{ matrix.llvm }} ./PCbuild/build.bat --experimental-jit ${{ matrix.debug && '-d' || '--pgo' }} -p ${{ matrix.architecture }} ./PCbuild/rt.bat ${{ matrix.debug && '-d' }} -p ${{ matrix.architecture }} -q --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3 - - name: macOS + # No PGO or tests (yet): + - name: Emulated Windows + if: runner.os == 'Windows' && matrix.architecture == 'ARM64' + run: | + choco install llvm --allow-downgrade --no-progress --version ${{ matrix.llvm }} + ./PCbuild/build.bat --experimental-jit ${{ matrix.debug && '-d' || '' }} -p ${{ matrix.architecture }} + + - name: Native macOS if: runner.os == 'macOS' run: | brew install llvm@${{ matrix.llvm }} - export SDKROOT="$(xcrun --show-sdk-path)" - ./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }} + SDKROOT="$(xcrun --show-sdk-path)" \ + ./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }} make all --jobs 4 ./python.exe -m test --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3 @@ -122,10 +134,10 @@ jobs: sudo apt install --yes "gcc-$HOST" qemu-user ${{ !matrix.debug && matrix.compiler == 'clang' && './configure --enable-optimizations' || '' }} ${{ !matrix.debug && matrix.compiler == 'clang' && 'make profile-run-stamp --jobs 4' || '' }} - export CC="${{ matrix.compiler == 'clang' && 'clang --target=$HOST' || '$HOST-gcc' }}" - export CPP="$CC --preprocess" - export HOSTRUNNER=qemu-${{ matrix.architecture }} export QEMU_LD_PREFIX="/usr/$HOST" - ./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }} --build=x86_64-linux-gnu --host="$HOST" --with-build-python=../build/bin/python3 --with-pkg-config=no ac_cv_buggy_getaddrinfo=no ac_cv_file__dev_ptc=no ac_cv_file__dev_ptmx=yes + CC="${{ matrix.compiler == 'clang' && 'clang --target=$HOST' || '$HOST-gcc' }}" \ + CPP="$CC --preprocess" \ + HOSTRUNNER=qemu-${{ matrix.architecture }} \ + ./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }} --build=x86_64-linux-gnu --host="$HOST" --with-build-python=../build/bin/python3 --with-pkg-config=no ac_cv_buggy_getaddrinfo=no ac_cv_file__dev_ptc=no ac_cv_file__dev_ptmx=yes make all --jobs 4 ./python -m test --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4a70ec6205a05bb..ccde03f91983df0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -23,4 +23,4 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.x" - - uses: pre-commit/action@v3.0.0 + - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/project-updater.yml b/.github/workflows/project-updater.yml index 7574bfc208ff768..56e508d453c346f 100644 --- a/.github/workflows/project-updater.yml +++ b/.github/workflows/project-updater.yml @@ -23,7 +23,7 @@ jobs: - { project: 32, label: sprint } steps: - - uses: actions/add-to-project@v0.1.0 + - uses: actions/add-to-project@v0.6.0 with: project-url: https://github.com/orgs/python/projects/${{ matrix.project }} github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} diff --git a/.github/workflows/reusable-macos.yml b/.github/workflows/reusable-macos.yml index ba62d9568c6b80a..dabeca8c81ece1c 100644 --- a/.github/workflows/reusable-macos.yml +++ b/.github/workflows/reusable-macos.yml @@ -8,6 +8,9 @@ on: required: false type: boolean default: false + os-matrix: + required: false + type: string jobs: build_macos: @@ -17,14 +20,12 @@ jobs: HOMEBREW_NO_ANALYTICS: 1 HOMEBREW_NO_AUTO_UPDATE: 1 HOMEBREW_NO_INSTALL_CLEANUP: 1 + HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 PYTHONSTRICTEXTENSIONBUILD: 1 strategy: fail-fast: false matrix: - os: [ - "macos-14", # M1 - "macos-13", # Intel - ] + os: ${{fromJson(inputs.os-matrix)}} runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/reusable-tsan.yml b/.github/workflows/reusable-tsan.yml new file mode 100644 index 000000000000000..96a9c1b0cda3c35 --- /dev/null +++ b/.github/workflows/reusable-tsan.yml @@ -0,0 +1,51 @@ +on: + workflow_call: + inputs: + config_hash: + required: true + type: string + options: + required: true + type: string + +jobs: + build_tsan_reusable: + name: 'Thread sanitizer' + runs-on: ubuntu-22.04 + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + - name: Runner image version + run: echo "IMAGE_VERSION=${ImageVersion}" >> $GITHUB_ENV + - name: Restore config.cache + uses: actions/cache@v4 + with: + path: config.cache + key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-${{ inputs.config_hash }} + - name: Install Dependencies + run: | + sudo ./.github/workflows/posix-deps-apt.sh + sudo apt install -y clang + # Reduce ASLR to avoid TSAN crashing + sudo sysctl -w vm.mmap_rnd_bits=28 + - name: TSAN Option Setup + run: | + echo "TSAN_OPTIONS=suppressions=${GITHUB_WORKSPACE}/Tools/tsan/supressions.txt" >> $GITHUB_ENV + echo "CC=clang" >> $GITHUB_ENV + echo "CXX=clang++" >> $GITHUB_ENV + - name: Add ccache to PATH + run: | + echo "PATH=/usr/lib/ccache:$PATH" >> $GITHUB_ENV + - name: Configure ccache action + uses: hendrikmuhs/ccache-action@v1.2 + with: + save: ${{ github.event_name == 'push' }} + max-size: "200M" + - name: Configure CPython + run: ${{ inputs.options }} + - name: Build CPython + run: make -j4 + - name: Display build info + run: make pythoninfo + - name: Tests + run: ./python -m test --tsan -j4 diff --git a/.github/workflows/reusable-wasi.yml b/.github/workflows/reusable-wasi.yml new file mode 100644 index 000000000000000..60eef7bc478bbfe --- /dev/null +++ b/.github/workflows/reusable-wasi.yml @@ -0,0 +1,71 @@ +on: + workflow_call: + inputs: + config_hash: + required: true + type: string + +jobs: + build_wasi_reusable: + name: 'build and test' + timeout-minutes: 60 + runs-on: ubuntu-20.04 + env: + WASMTIME_VERSION: 18.0.3 + WASI_SDK_VERSION: 21 + WASI_SDK_PATH: /opt/wasi-sdk + CROSS_BUILD_PYTHON: cross-build/build + CROSS_BUILD_WASI: cross-build/wasm32-wasi + steps: + - uses: actions/checkout@v4 + # No problem resolver registered as one doesn't currently exist for Clang. + - name: "Install wasmtime" + uses: jcbhmr/setup-wasmtime@v2 + with: + wasmtime-version: ${{ env.WASMTIME_VERSION }} + - name: "Restore WASI SDK" + id: cache-wasi-sdk + uses: actions/cache@v4 + with: + path: ${{ env.WASI_SDK_PATH }} + key: ${{ runner.os }}-wasi-sdk-${{ env.WASI_SDK_VERSION }} + - name: "Install WASI SDK" + if: steps.cache-wasi-sdk.outputs.cache-hit != 'true' + run: | + mkdir ${{ env.WASI_SDK_PATH }} && \ + curl -s -S --location https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${{ env.WASI_SDK_VERSION }}/wasi-sdk-${{ env.WASI_SDK_VERSION }}.0-linux.tar.gz | \ + tar --strip-components 1 --directory ${{ env.WASI_SDK_PATH }} --extract --gunzip + - name: "Configure ccache action" + uses: hendrikmuhs/ccache-action@v1.2 + with: + save: ${{ github.event_name == 'push' }} + max-size: "200M" + - name: "Add ccache to PATH" + run: echo "PATH=/usr/lib/ccache:$PATH" >> $GITHUB_ENV + - name: "Install Python" + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: "Restore Python build config.cache" + uses: actions/cache@v4 + with: + path: ${{ env.CROSS_BUILD_PYTHON }}/config.cache + key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-${{ inputs.config_hash }} + - name: "Configure build Python" + run: python3 Tools/wasm/wasi.py configure-build-python -- --config-cache --with-pydebug + - name: "Make build Python" + run: python3 Tools/wasm/wasi.py make-build-python + - name: "Restore host config.cache" + uses: actions/cache@v4 + with: + path: ${{ env.CROSS_BUILD_WASI }}/config.cache + key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-wasi-sdk-${{ env.WASI_SDK_VERSION }}-${{ inputs.config_hash }} + - name: "Configure host" + # `--with-pydebug` inferred from configure-build-python + run: python3 Tools/wasm/wasi.py configure-host -- --config-cache + - name: "Make host" + run: python3 Tools/wasm/wasi.py make-host + - name: "Display build info" + run: make --directory ${{ env.CROSS_BUILD_WASI }} pythoninfo + - name: "Test" + run: make --directory ${{ env.CROSS_BUILD_WASI }} test diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 07608fe91b4dbe6..f97587e68cbbe43 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,7 +2,7 @@ name: Mark stale pull requests on: schedule: - - cron: "0 0 * * *" + - cron: "0 */6 * * *" permissions: pull-requests: write diff --git a/.gitignore b/.gitignore index 6ed7197e3ab6269..3e1213ef925305a 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,17 @@ Lib/test/data/* /_bootstrap_python /Makefile /Makefile.pre +iOSTestbed.* +iOS/Frameworks/ +iOS/Resources/Info.plist +iOS/testbed/build +iOS/testbed/Python.xcframework/ios-*/bin +iOS/testbed/Python.xcframework/ios-*/include +iOS/testbed/Python.xcframework/ios-*/lib +iOS/testbed/Python.xcframework/ios-*/Python.framework +iOS/testbed/iOSTestbed.xcodeproj/project.xcworkspace +iOS/testbed/iOSTestbed.xcodeproj/xcuserdata +iOS/testbed/iOSTestbed.xcodeproj/xcshareddata Mac/Makefile Mac/PythonLauncher/Info.plist Mac/PythonLauncher/Makefile diff --git a/Doc/c-api/contextvars.rst b/Doc/c-api/contextvars.rst index d970f5443b1df57..fe7b8f93f2c6cf7 100644 --- a/Doc/c-api/contextvars.rst +++ b/Doc/c-api/contextvars.rst @@ -6,6 +6,8 @@ Context Variables Objects ------------------------- .. _contextvarsobjects_pointertype_change: +.. versionadded:: 3.7 + .. versionchanged:: 3.7.1 .. note:: @@ -24,8 +26,6 @@ Context Variables Objects See :issue:`34762` for more details. -.. versionadded:: 3.7 - This section details the public C API for the :mod:`contextvars` module. .. c:type:: PyContext diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index e6309ae7614d347..ba13fd1b9973e03 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -105,7 +105,7 @@ Printing and clearing parameters help format the warning message; they have the same meaning and values as in :c:func:`PyUnicode_FromFormat`. ``PyErr_WriteUnraisable(obj)`` is roughtly equivalent to - ``PyErr_FormatUnraisable("Exception ignored in: %R, obj)``. + ``PyErr_FormatUnraisable("Exception ignored in: %R", obj)``. If *format* is ``NULL``, only the traceback is printed. .. versionadded:: 3.13 diff --git a/Doc/c-api/hash.rst b/Doc/c-api/hash.rst index 91d88ae27bc9f49..1cf094cfcdca24e 100644 --- a/Doc/c-api/hash.rst +++ b/Doc/c-api/hash.rst @@ -3,7 +3,7 @@ PyHash API ---------- -See also the :c:member:`PyTypeObject.tp_hash` member. +See also the :c:member:`PyTypeObject.tp_hash` member and :ref:`numeric-hash`. .. c:type:: Py_hash_t @@ -17,6 +17,29 @@ See also the :c:member:`PyTypeObject.tp_hash` member. .. versionadded:: 3.2 +.. c:macro:: PyHASH_MODULUS + + The `Mersenne prime `_ ``P = 2**n -1``, used for numeric hash scheme. + + .. versionadded:: 3.13 + +.. c:macro:: PyHASH_BITS + + The exponent ``n`` of ``P`` in :c:macro:`PyHASH_MODULUS`. + + .. versionadded:: 3.13 + +.. c:macro:: PyHASH_INF + + The hash value returned for a positive infinity. + + .. versionadded:: 3.13 + +.. c:macro:: PyHASH_IMAG + + The multiplier used for the imaginary part of a complex number. + + .. versionadded:: 3.13 .. c:type:: PyHash_FuncDef diff --git a/Doc/c-api/import.rst b/Doc/c-api/import.rst index 7c74e9e88678dc0..1054b38cb92f7d0 100644 --- a/Doc/c-api/import.rst +++ b/Doc/c-api/import.rst @@ -308,7 +308,7 @@ Importing Modules The module name, as an ASCII encoded string. - .. c: member:: PyObject* (*initfunc)(void) + .. c:member:: PyObject* (*initfunc)(void) Initialization function for a module built into the interpreter. diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index e7199ad5e0c1b15..05f2fd13cf2069b 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1575,14 +1575,14 @@ pointer and a void pointer argument. function is generally **not** suitable for calling Python code from arbitrary C threads. Instead, use the :ref:`PyGILState API`. + .. versionadded:: 3.1 + .. versionchanged:: 3.9 If this function is called in a subinterpreter, the function *func* is now scheduled to be called from the subinterpreter, rather than being called from the main interpreter. Each subinterpreter now has its own list of scheduled calls. - .. versionadded:: 3.1 - .. _profiling: Profiling and Tracing diff --git a/Doc/c-api/long.rst b/Doc/c-api/long.rst index 582f5c7bf05471c..6a7eba7761de1ae 100644 --- a/Doc/c-api/long.rst +++ b/Doc/c-api/long.rst @@ -450,7 +450,7 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate. a “fast path” for small integers. For compact values use :c:func:`PyUnstable_Long_CompactValue`; for others fall back to a :c:func:`PyLong_As* ` function or - :c:func:`calling ` :meth:`int.to_bytes`. + :c:func:`PyLong_AsNativeBytes`. The speedup is expected to be negligible for most users. diff --git a/Doc/c-api/refcounting.rst b/Doc/c-api/refcounting.rst index 75e1d46474f1e75..bf50107347e0e78 100644 --- a/Doc/c-api/refcounting.rst +++ b/Doc/c-api/refcounting.rst @@ -23,12 +23,12 @@ of Python objects. Use the :c:func:`Py_SET_REFCNT()` function to set an object reference count. - .. versionchanged:: 3.11 - The parameter type is no longer :c:expr:`const PyObject*`. - .. versionchanged:: 3.10 :c:func:`Py_REFCNT()` is changed to the inline static function. + .. versionchanged:: 3.11 + The parameter type is no longer :c:expr:`const PyObject*`. + .. c:function:: void Py_SET_REFCNT(PyObject *o, Py_ssize_t refcnt) diff --git a/Doc/c-api/structures.rst b/Doc/c-api/structures.rst index e2943f18ddc6010..f9461ab01f60492 100644 --- a/Doc/c-api/structures.rst +++ b/Doc/c-api/structures.rst @@ -702,12 +702,12 @@ Defining Getters and Setters .. c:member:: void* closure - Optional function pointer, providing additional data for getter and setter. + Optional user data pointer, providing additional data for getter and setter. .. c:type:: PyObject *(*getter)(PyObject *, void *) The ``get`` function takes one :c:expr:`PyObject*` parameter (the - instance) and a function pointer (the associated ``closure``): + instance) and a user data pointer (the associated ``closure``): It should return a new reference on success or ``NULL`` with a set exception on failure. @@ -715,7 +715,7 @@ Defining Getters and Setters .. c:type:: int (*setter)(PyObject *, PyObject *, void *) ``set`` functions take two :c:expr:`PyObject*` parameters (the instance and - the value to be set) and a function pointer (the associated ``closure``): + the value to be set) and a user data pointer (the associated ``closure``): In case the attribute should be deleted the second parameter is ``NULL``. Should return ``0`` on success or ``-1`` with a set exception on failure. diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index 5aaa8147dd3176a..0cae5c09505ebe1 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -185,6 +185,21 @@ Type Objects .. versionadded:: 3.11 +.. c:function:: PyObject* PyType_GetFullyQualifiedName(PyTypeObject *type) + + Return the type's fully qualified name. Equivalent to + ``f"{type.__module__}.{type.__qualname__}"``, or ``type.__qualname__`` if + ``type.__module__`` is not a string or is equal to ``"builtins"``. + + .. versionadded:: 3.13 + +.. c:function:: PyObject* PyType_GetModuleName(PyTypeObject *type) + + Return the type's module name. Equivalent to getting the ``type.__module__`` + attribute. + + .. versionadded:: 3.13 + .. c:function:: void* PyType_GetSlot(PyTypeObject *type, int slot) Return the function pointer stored in the given slot. If the diff --git a/Doc/c-api/unicode.rst b/Doc/c-api/unicode.rst index 666ffe89605c56f..78eec14e3a24d65 100644 --- a/Doc/c-api/unicode.rst +++ b/Doc/c-api/unicode.rst @@ -518,6 +518,26 @@ APIs: - :c:expr:`PyObject*` - The result of calling :c:func:`PyObject_Repr`. + * - ``T`` + - :c:expr:`PyObject*` + - Get the fully qualified name of an object type; + call :c:func:`PyType_GetFullyQualifiedName`. + + * - ``T#`` + - :c:expr:`PyObject*` + - Similar to ``T`` format, but use a colon (``:``) as separator between + the module name and the qualified name. + + * - ``N`` + - :c:expr:`PyTypeObject*` + - Get the fully qualified name of a type; + call :c:func:`PyType_GetFullyQualifiedName`. + + * - ``N#`` + - :c:expr:`PyTypeObject*` + - Similar to ``N`` format, but use a colon (``:``) as separator between + the module name and the qualified name. + .. note:: The width formatter unit is number of characters rather than bytes. The precision formatter unit is number of bytes or :c:type:`wchar_t` @@ -553,6 +573,9 @@ APIs: In previous versions it caused all the rest of the format string to be copied as-is to the result string, and any extra arguments discarded. + .. versionchanged:: 3.13 + Support for ``%T``, ``%T#``, ``%N`` and ``%N#`` formats added. + .. c:function:: PyObject* PyUnicode_FromFormatV(const char *format, va_list vargs) diff --git a/Doc/conf.py b/Doc/conf.py index 7c4817320a7de2c..f4c75c5758cb281 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -101,11 +101,13 @@ ('c:func', 'dlopen'), ('c:func', 'exec'), ('c:func', 'fcntl'), + ('c:func', 'flock'), ('c:func', 'fork'), ('c:func', 'free'), ('c:func', 'gettimeofday'), ('c:func', 'gmtime'), ('c:func', 'grantpt'), + ('c:func', 'ioctl'), ('c:func', 'localeconv'), ('c:func', 'localtime'), ('c:func', 'main'), diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 25629b4da053da8..9d0ad3d036dac34 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -677,7 +677,9 @@ function,PyType_FromSpecWithBases,3.3,, function,PyType_GenericAlloc,3.2,, function,PyType_GenericNew,3.2,, function,PyType_GetFlags,3.2,, +function,PyType_GetFullyQualifiedName,3.13,, function,PyType_GetModule,3.10,, +function,PyType_GetModuleName,3.13,, function,PyType_GetModuleState,3.10,, function,PyType_GetName,3.11,, function,PyType_GetQualName,3.11,, diff --git a/Doc/faq/design.rst b/Doc/faq/design.rst index 300e1b6cc40a58a..c8beb64e39bc1a3 100644 --- a/Doc/faq/design.rst +++ b/Doc/faq/design.rst @@ -259,9 +259,11 @@ is evaluated in all cases. Why isn't there a switch or case statement in Python? ----------------------------------------------------- -You can do this easily enough with a sequence of ``if... elif... elif... else``. -For literal values, or constants within a namespace, you can also use a -``match ... case`` statement. +In general, structured switch statements execute one block of code +when an expression has a particular value or set of values. +Since Python 3.10 one can easily match literal values, or constants +within a namespace, with a ``match ... case`` statement. +An older alternative is a sequence of ``if... elif... elif... else``. For cases where you need to choose from a very large number of possibilities, you can create a dictionary mapping case values to functions to call. For @@ -290,6 +292,9 @@ It's suggested that you use a prefix for the method names, such as ``visit_`` in this example. Without such a prefix, if values are coming from an untrusted source, an attacker would be able to call any method on your object. +Imitating switch with fallthrough, as with C's switch-case-default, +is possible, much harder, and less needed. + Can't you emulate threads in the interpreter instead of relying on an OS-specific thread implementation? -------------------------------------------------------------------------------------------------------- diff --git a/Doc/faq/extending.rst b/Doc/faq/extending.rst index 2a8b976925d042c..1cff2c4091df060 100644 --- a/Doc/faq/extending.rst +++ b/Doc/faq/extending.rst @@ -50,7 +50,7 @@ to learn Python's C API. If you need to interface to some C or C++ library for which no Python extension currently exists, you can try wrapping the library's data types and functions with a tool such as `SWIG `_. `SIP -`__, `CXX +`__, `CXX `_ `Boost `_, or `Weave `_ are also diff --git a/Doc/faq/general.rst b/Doc/faq/general.rst index 8727332594bda69..ec7c28975949990 100644 --- a/Doc/faq/general.rst +++ b/Doc/faq/general.rst @@ -133,8 +133,6 @@ Python versions are numbered "A.B.C" or "A.B": changes. * *C* is the micro version number -- it is incremented for each bugfix release. -See :pep:`6` for more information about bugfix releases. - Not all releases are bugfix releases. In the run-up to a new feature release, a series of development releases are made, denoted as alpha, beta, or release candidate. Alphas are early releases in which interfaces aren't yet finalized; @@ -157,7 +155,11 @@ unreleased versions, built directly from the CPython development repository. In practice, after a final minor release is made, the version is incremented to the next minor version, which becomes the "a0" version, e.g. "2.4a0". -See also the documentation for :data:`sys.version`, :data:`sys.hexversion`, and +See the `Developer's Guide +`__ +for more information about the development cycle, and +:pep:`387` to learn more about Python's backward compatibility policy. See also +the documentation for :data:`sys.version`, :data:`sys.hexversion`, and :data:`sys.version_info`. diff --git a/Doc/glossary.rst b/Doc/glossary.rst index f656e32514c7178..72fb09ef6207c9c 100644 --- a/Doc/glossary.rst +++ b/Doc/glossary.rst @@ -841,10 +841,11 @@ Glossary Some named tuples are built-in types (such as the above examples). Alternatively, a named tuple can be created from a regular class definition that inherits from :class:`tuple` and that defines named - fields. Such a class can be written by hand or it can be created with - the factory function :func:`collections.namedtuple`. The latter - technique also adds some extra methods that may not be found in - hand-written or built-in named tuples. + fields. Such a class can be written by hand, or it can be created by + inheriting :class:`typing.NamedTuple`, or with the factory function + :func:`collections.namedtuple`. The latter techniques also add some + extra methods that may not be found in hand-written or built-in named + tuples. namespace The place where a variable is stored. Namespaces are implemented as diff --git a/Doc/howto/gdb_helpers.rst b/Doc/howto/gdb_helpers.rst new file mode 100644 index 000000000000000..53bbf7ddaa2ab96 --- /dev/null +++ b/Doc/howto/gdb_helpers.rst @@ -0,0 +1,449 @@ +.. _gdb: + +========================================================= +Debugging C API extensions and CPython Internals with GDB +========================================================= + +.. highlight:: none + +This document explains how the Python GDB extension, ``python-gdb.py``, can +be used with the GDB debugger to debug CPython extensions and the +CPython interpreter itself. + +When debugging low-level problems such as crashes or deadlocks, a low-level +debugger, such as GDB, is useful to diagnose and correct the issue. +By default, GDB (or any of its front-ends) doesn't support high-level +information specific to the CPython interpreter. + +The ``python-gdb.py`` extension adds CPython interpreter information to GDB. +The extension helps introspect the stack of currently executing Python functions. +Given a Python object represented by a :c:expr:`PyObject *` pointer, +the extension surfaces the type and value of the object. + +Developers who are working on CPython extensions or tinkering with parts +of CPython that are written in C can use this document to learn how to use the +``python-gdb.py`` extension with GDB. + +.. note:: + + This document assumes that you are familiar with the basics of GDB and the + CPython C API. It consolidates guidance from the + `devguide `_ and the + `Python wiki `_. + + +Prerequisites +============= + +You need to have: + +- GDB 7 or later. (For earlier versions of GDB, see ``Misc/gdbinit`` in the + sources of Python 3.11 or earlier.) +- GDB-compatible debugging information for Python and any extension you are + debugging. +- The ``python-gdb.py`` extension. + +The extension is built with Python, but might be distributed separately or +not at all. Below, we include tips for a few common systems as examples. +Note that even if the instructions match your system, they might be outdated. + + +Setup with Python built from source +----------------------------------- + +When you build CPython from source, debugging information should be available, +and the build should add a ``python-gdb.py`` file to the root directory of +your repository. + +To activate support, you must add the directory containing ``python-gdb.py`` +to GDB's "auto-load-safe-path". +If you haven't done this, recent versions of GDB will print out a warning +with instructions on how to do this. + +.. note:: + + If you do not see instructions for your version of GDB, put this in your + configuration file (``~/.gdbinit`` or ``~/.config/gdb/gdbinit``):: + + add-auto-load-safe-path /path/to/cpython + + You can also add multiple paths, separated by ``:``. + + +Setup for Python from a Linux distro +------------------------------------ + +Most Linux systems provide debug information for the system Python +in a package called ``python-debuginfo``, ``python-dbg`` or similar. +For example: + +- Fedora: + + .. code-block:: shell + + sudo dnf install gdb + sudo dnf debuginfo-install python3 + +- Ubuntu: + + .. code-block:: shell + + sudo apt install gdb python3-dbg + +On several recent Linux systems, GDB can download debugging symbols +automatically using *debuginfod*. +However, this will not install the ``python-gdb.py`` extension; +you generally do need to install the debug info package separately. + + +Using the Debug build and Development mode +========================================== + +For easier debugging, you might want to: + +- Use a :ref:`debug build ` of Python. (When building from source, + use ``configure --with-pydebug``. On Linux distros, install and run a package + like ``python-debug`` or ``python-dbg``, if available.) +- Use the runtime :ref:`development mode ` (``-X dev``). + +Both enable extra assertions and disable some optimizations. +Sometimes this hides the bug you are trying to find, but in most cases they +make the process easier. + + +Using the ``python-gdb`` extension +================================== + +When the extension is loaded, it provides two main features: +pretty printers for Python values, and additional commands. + +Pretty-printers +--------------- + +This is what a GDB backtrace looks like (truncated) when this extension is +enabled:: + + #0 0x000000000041a6b1 in PyObject_Malloc (nbytes=Cannot access memory at address 0x7fffff7fefe8 + ) at Objects/obmalloc.c:748 + #1 0x000000000041b7c0 in _PyObject_DebugMallocApi (id=111 'o', nbytes=24) at Objects/obmalloc.c:1445 + #2 0x000000000041b717 in _PyObject_DebugMalloc (nbytes=24) at Objects/obmalloc.c:1412 + #3 0x000000000044060a in _PyUnicode_New (length=11) at Objects/unicodeobject.c:346 + #4 0x00000000004466aa in PyUnicodeUCS2_DecodeUTF8Stateful (s=0x5c2b8d "__lltrace__", size=11, errors=0x0, consumed= + 0x0) at Objects/unicodeobject.c:2531 + #5 0x0000000000446647 in PyUnicodeUCS2_DecodeUTF8 (s=0x5c2b8d "__lltrace__", size=11, errors=0x0) + at Objects/unicodeobject.c:2495 + #6 0x0000000000440d1b in PyUnicodeUCS2_FromStringAndSize (u=0x5c2b8d "__lltrace__", size=11) + at Objects/unicodeobject.c:551 + #7 0x0000000000440d94 in PyUnicodeUCS2_FromString (u=0x5c2b8d "__lltrace__") at Objects/unicodeobject.c:569 + #8 0x0000000000584abd in PyDict_GetItemString (v= + {'Yuck': , '__builtins__': , '__file__': 'Lib/test/crashers/nasty_eq_vs_dict.py', '__package__': None, 'y': , 'dict': {0: 0, 1: 1, 2: 2, 3: 3}, '__cached__': None, '__name__': '__main__', 'z': , '__doc__': None}, key= + 0x5c2b8d "__lltrace__") at Objects/dictobject.c:2171 + +Notice how the dictionary argument to ``PyDict_GetItemString`` is displayed +as its ``repr()``, rather than an opaque ``PyObject *`` pointer. + +The extension works by supplying a custom printing routine for values of type +``PyObject *``. If you need to access lower-level details of an object, then +cast the value to a pointer of the appropriate type. For example:: + + (gdb) p globals + $1 = {'__builtins__': , '__name__': + '__main__', 'ctypes': , '__doc__': None, + '__package__': None} + + (gdb) p *(PyDictObject*)globals + $2 = {ob_refcnt = 3, ob_type = 0x3dbdf85820, ma_fill = 5, ma_used = 5, + ma_mask = 7, ma_table = 0x63d0f8, ma_lookup = 0x3dbdc7ea70 + , ma_smalltable = {{me_hash = 7065186196740147912, + me_key = '__builtins__', me_value = }, + {me_hash = -368181376027291943, me_key = '__name__', + me_value ='__main__'}, {me_hash = 0, me_key = 0x0, me_value = 0x0}, + {me_hash = 0, me_key = 0x0, me_value = 0x0}, + {me_hash = -9177857982131165996, me_key = 'ctypes', + me_value = }, + {me_hash = -8518757509529533123, me_key = '__doc__', me_value = None}, + {me_hash = 0, me_key = 0x0, me_value = 0x0}, { + me_hash = 6614918939584953775, me_key = '__package__', me_value = None}}} + +Note that the pretty-printers do not actually call ``repr()``. +For basic types, they try to match its result closely. + +An area that can be confusing is that the custom printer for some types look a +lot like GDB's built-in printer for standard types. For example, the +pretty-printer for a Python ``int`` (:c:expr:`PyLongObject *`) +gives a representation that is not distinguishable from one of a +regular machine-level integer:: + + (gdb) p some_machine_integer + $3 = 42 + + (gdb) p some_python_integer + $4 = 42 + +The internal structure can be revealed with a cast to :c:expr:`PyLongObject *`: + + (gdb) p *(PyLongObject*)some_python_integer + $5 = {ob_base = {ob_base = {ob_refcnt = 8, ob_type = 0x3dad39f5e0}, ob_size = 1}, + ob_digit = {42}} + +A similar confusion can arise with the ``str`` type, where the output looks a +lot like gdb's built-in printer for ``char *``:: + + (gdb) p ptr_to_python_str + $6 = '__builtins__' + +The pretty-printer for ``str`` instances defaults to using single-quotes (as +does Python's ``repr`` for strings) whereas the standard printer for ``char *`` +values uses double-quotes and contains a hexadecimal address:: + + (gdb) p ptr_to_char_star + $7 = 0x6d72c0 "hello world" + +Again, the implementation details can be revealed with a cast to +:c:expr:`PyUnicodeObject *`:: + + (gdb) p *(PyUnicodeObject*)$6 + $8 = {ob_base = {ob_refcnt = 33, ob_type = 0x3dad3a95a0}, length = 12, + str = 0x7ffff2128500, hash = 7065186196740147912, state = 1, defenc = 0x0} + +``py-list`` +----------- + + The extension adds a ``py-list`` command, which + lists the Python source code (if any) for the current frame in the selected + thread. The current line is marked with a ">":: + + (gdb) py-list + 901 if options.profile: + 902 options.profile = False + 903 profile_me() + 904 return + 905 + >906 u = UI() + 907 if not u.quit: + 908 try: + 909 gtk.main() + 910 except KeyboardInterrupt: + 911 # properly quit on a keyboard interrupt... + + Use ``py-list START`` to list at a different line number within the Python + source, and ``py-list START,END`` to list a specific range of lines within + the Python source. + +``py-up`` and ``py-down`` +------------------------- + + The ``py-up`` and ``py-down`` commands are analogous to GDB's regular ``up`` + and ``down`` commands, but try to move at the level of CPython frames, rather + than C frames. + + GDB is not always able to read the relevant frame information, depending on + the optimization level with which CPython was compiled. Internally, the + commands look for C frames that are executing the default frame evaluation + function (that is, the core bytecode interpreter loop within CPython) and + look up the value of the related ``PyFrameObject *``. + + They emit the frame number (at the C level) within the thread. + + For example:: + + (gdb) py-up + #37 Frame 0x9420b04, for file /usr/lib/python2.6/site-packages/ + gnome_sudoku/main.py, line 906, in start_game () + u = UI() + (gdb) py-up + #40 Frame 0x948e82c, for file /usr/lib/python2.6/site-packages/ + gnome_sudoku/gnome_sudoku.py, line 22, in start_game(main=) + main.start_game() + (gdb) py-up + Unable to find an older python frame + + so we're at the top of the Python stack. + + The frame numbers correspond to those displayed by GDB's standard + ``backtrace`` command. + The command skips C frames which are not executing Python code. + + Going back down:: + + (gdb) py-down + #37 Frame 0x9420b04, for file /usr/lib/python2.6/site-packages/gnome_sudoku/main.py, line 906, in start_game () + u = UI() + (gdb) py-down + #34 (unable to read python frame information) + (gdb) py-down + #23 (unable to read python frame information) + (gdb) py-down + #19 (unable to read python frame information) + (gdb) py-down + #14 Frame 0x99262ac, for file /usr/lib/python2.6/site-packages/gnome_sudoku/game_selector.py, line 201, in run_swallowed_dialog (self=, puzzle=None, saved_games=[{'gsd.auto_fills': 0, 'tracking': {}, 'trackers': {}, 'notes': [], 'saved_at': 1270084485, 'game': '7 8 0 0 0 0 0 5 6 0 0 9 0 8 0 1 0 0 0 4 6 0 0 0 0 7 0 6 5 0 0 0 4 7 9 2 0 0 0 9 0 1 0 0 0 3 9 7 6 0 0 0 1 8 0 6 0 0 0 0 2 8 0 0 0 5 0 4 0 6 0 0 2 1 0 0 0 0 0 4 5\n7 8 0 0 0 0 0 5 6 0 0 9 0 8 0 1 0 0 0 4 6 0 0 0 0 7 0 6 5 1 8 3 4 7 9 2 0 0 0 9 0 1 0 0 0 3 9 7 6 0 0 0 1 8 0 6 0 0 0 0 2 8 0 0 0 5 0 4 0 6 0 0 2 1 0 0 0 0 0 4 5', 'gsd.impossible_hints': 0, 'timer.__absolute_start_time__': , 'gsd.hints': 0, 'timer.active_time': , 'timer.total_time': }], dialog=, saved_game_model=, sudoku_maker=, main_page=0) at remote 0x98fa6e4>, d=) + gtk.main() + (gdb) py-down + #8 (unable to read python frame information) + (gdb) py-down + Unable to find a newer python frame + + and we're at the bottom of the Python stack. + + Note that in Python 3.12 and newer, the same C stack frame can be used for + multiple Python stack frames. This means that ``py-up`` and ``py-down`` + may move multiple Python frames at once. For example:: + + (gdb) py-up + #6 Frame 0x7ffff7fb62b0, for file /tmp/rec.py, line 5, in recursive_function (n=0) + time.sleep(5) + #6 Frame 0x7ffff7fb6240, for file /tmp/rec.py, line 7, in recursive_function (n=1) + recursive_function(n-1) + #6 Frame 0x7ffff7fb61d0, for file /tmp/rec.py, line 7, in recursive_function (n=2) + recursive_function(n-1) + #6 Frame 0x7ffff7fb6160, for file /tmp/rec.py, line 7, in recursive_function (n=3) + recursive_function(n-1) + #6 Frame 0x7ffff7fb60f0, for file /tmp/rec.py, line 7, in recursive_function (n=4) + recursive_function(n-1) + #6 Frame 0x7ffff7fb6080, for file /tmp/rec.py, line 7, in recursive_function (n=5) + recursive_function(n-1) + #6 Frame 0x7ffff7fb6020, for file /tmp/rec.py, line 9, in () + recursive_function(5) + (gdb) py-up + Unable to find an older python frame + + +``py-bt`` +--------- + + The ``py-bt`` command attempts to display a Python-level backtrace of the + current thread. + + For example:: + + (gdb) py-bt + #8 (unable to read python frame information) + #11 Frame 0x9aead74, for file /usr/lib/python2.6/site-packages/gnome_sudoku/dialog_swallower.py, line 48, in run_dialog (self=, main_page=0) at remote 0x98fa6e4>, d=) + gtk.main() + #14 Frame 0x99262ac, for file /usr/lib/python2.6/site-packages/gnome_sudoku/game_selector.py, line 201, in run_swallowed_dialog (self=, puzzle=None, saved_games=[{'gsd.auto_fills': 0, 'tracking': {}, 'trackers': {}, 'notes': [], 'saved_at': 1270084485, 'game': '7 8 0 0 0 0 0 5 6 0 0 9 0 8 0 1 0 0 0 4 6 0 0 0 0 7 0 6 5 0 0 0 4 7 9 2 0 0 0 9 0 1 0 0 0 3 9 7 6 0 0 0 1 8 0 6 0 0 0 0 2 8 0 0 0 5 0 4 0 6 0 0 2 1 0 0 0 0 0 4 5\n7 8 0 0 0 0 0 5 6 0 0 9 0 8 0 1 0 0 0 4 6 0 0 0 0 7 0 6 5 1 8 3 4 7 9 2 0 0 0 9 0 1 0 0 0 3 9 7 6 0 0 0 1 8 0 6 0 0 0 0 2 8 0 0 0 5 0 4 0 6 0 0 2 1 0 0 0 0 0 4 5', 'gsd.impossible_hints': 0, 'timer.__absolute_start_time__': , 'gsd.hints': 0, 'timer.active_time': , 'timer.total_time': }], dialog=, saved_game_model=, sudoku_maker=) + main.start_game() + + The frame numbers correspond to those displayed by GDB's standard + ``backtrace`` command. + +``py-print`` +------------ + + The ``py-print`` command looks up a Python name and tries to print it. + It looks in locals within the current thread, then globals, then finally + builtins:: + + (gdb) py-print self + local 'self' = , + main_page=0) at remote 0x98fa6e4> + (gdb) py-print __name__ + global '__name__' = 'gnome_sudoku.dialog_swallower' + (gdb) py-print len + builtin 'len' = + (gdb) py-print scarlet_pimpernel + 'scarlet_pimpernel' not found + + If the current C frame corresponds to multiple Python frames, ``py-print`` + only considers the first one. + +``py-locals`` +------------- + + The ``py-locals`` command looks up all Python locals within the current + Python frame in the selected thread, and prints their representations:: + + (gdb) py-locals + self = , + main_page=0) at remote 0x98fa6e4> + d = + + If the current C frame corresponds to multiple Python frames, locals from + all of them will be shown:: + + (gdb) py-locals + Locals for recursive_function + n = 0 + Locals for recursive_function + n = 1 + Locals for recursive_function + n = 2 + Locals for recursive_function + n = 3 + Locals for recursive_function + n = 4 + Locals for recursive_function + n = 5 + Locals for + + +Use with GDB commands +===================== + +The extension commands complement GDB's built-in commands. +For example, you can use a frame numbers shown by ``py-bt`` with the ``frame`` +command to go a specific frame within the selected thread, like this:: + + (gdb) py-bt + (output snipped) + #68 Frame 0xaa4560, for file Lib/test/regrtest.py, line 1548, in () + main() + (gdb) frame 68 + #68 0x00000000004cd1e6 in PyEval_EvalFrameEx (f=Frame 0xaa4560, for file Lib/test/regrtest.py, line 1548, in (), throwflag=0) at Python/ceval.c:2665 + 2665 x = call_function(&sp, oparg); + (gdb) py-list + 1543 # Run the tests in a context manager that temporary changes the CWD to a + 1544 # temporary and writable directory. If it's not possible to create or + 1545 # change the CWD, the original CWD will be used. The original CWD is + 1546 # available from test_support.SAVEDCWD. + 1547 with test_support.temp_cwd(TESTCWD, quiet=True): + >1548 main() + +The ``info threads`` command will give you a list of the threads within the +process, and you can use the ``thread`` command to select a different one:: + + (gdb) info threads + 105 Thread 0x7fffefa18710 (LWP 10260) sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86 + 104 Thread 0x7fffdf5fe710 (LWP 10259) sem_wait () at ../nptl/sysdeps/unix/sysv/linux/x86_64/sem_wait.S:86 + * 1 Thread 0x7ffff7fe2700 (LWP 10145) 0x00000038e46d73e3 in select () at ../sysdeps/unix/syscall-template.S:82 + +You can use ``thread apply all COMMAND`` or (``t a a COMMAND`` for short) to run +a command on all threads. With ``py-bt``, this lets you see what every +thread is doing at the Python level:: + + (gdb) t a a py-bt + + Thread 105 (Thread 0x7fffefa18710 (LWP 10260)): + #5 Frame 0x7fffd00019d0, for file /home/david/coding/python-svn/Lib/threading.py, line 155, in _acquire_restore (self=<_RLock(_Verbose__verbose=False, _RLock__owner=140737354016512, _RLock__block=, _RLock__count=1) at remote 0xd7ff40>, count_owner=(1, 140737213728528), count=1, owner=140737213728528) + self.__block.acquire() + #8 Frame 0x7fffac001640, for file /home/david/coding/python-svn/Lib/threading.py, line 269, in wait (self=<_Condition(_Condition__lock=<_RLock(_Verbose__verbose=False, _RLock__owner=140737354016512, _RLock__block=, _RLock__count=1) at remote 0xd7ff40>, acquire=, _is_owned=, _release_save=, release=, _acquire_restore=, _Verbose__verbose=False, _Condition__waiters=[]) at remote 0xd7fd10>, timeout=None, waiter=, saved_state=(1, 140737213728528)) + self._acquire_restore(saved_state) + #12 Frame 0x7fffb8001a10, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 348, in f () + cond.wait() + #16 Frame 0x7fffb8001c40, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 37, in task (tid=140737213728528) + f() + + Thread 104 (Thread 0x7fffdf5fe710 (LWP 10259)): + #5 Frame 0x7fffe4001580, for file /home/david/coding/python-svn/Lib/threading.py, line 155, in _acquire_restore (self=<_RLock(_Verbose__verbose=False, _RLock__owner=140737354016512, _RLock__block=, _RLock__count=1) at remote 0xd7ff40>, count_owner=(1, 140736940992272), count=1, owner=140736940992272) + self.__block.acquire() + #8 Frame 0x7fffc8002090, for file /home/david/coding/python-svn/Lib/threading.py, line 269, in wait (self=<_Condition(_Condition__lock=<_RLock(_Verbose__verbose=False, _RLock__owner=140737354016512, _RLock__block=, _RLock__count=1) at remote 0xd7ff40>, acquire=, _is_owned=, _release_save=, release=, _acquire_restore=, _Verbose__verbose=False, _Condition__waiters=[]) at remote 0xd7fd10>, timeout=None, waiter=, saved_state=(1, 140736940992272)) + self._acquire_restore(saved_state) + #12 Frame 0x7fffac001c90, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 348, in f () + cond.wait() + #16 Frame 0x7fffac0011c0, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 37, in task (tid=140736940992272) + f() + + Thread 1 (Thread 0x7ffff7fe2700 (LWP 10145)): + #5 Frame 0xcb5380, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 16, in _wait () + time.sleep(0.01) + #8 Frame 0x7fffd00024a0, for file /home/david/coding/python-svn/Lib/test/lock_tests.py, line 378, in _check_notify (self=, skipped=[], _mirrorOutput=False, testsRun=39, buffer=False, _original_stderr=, _stdout_buffer=, _stderr_buffer=, _moduleSetUpFailed=False, expectedFailures=[], errors=[], _previousTestClass=, unexpectedSuccesses=[], failures=[], shouldStop=False, failfast=False) at remote 0xc185a0>, _threads=(0,), _cleanups=[], _type_equality_funcs={: , : , : , : , `_ The NumPy package defines another array type. - diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index c943c2f498173e8..d10f3f275c0eafe 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -103,20 +103,15 @@ Node classes For example, to create and populate an :class:`ast.UnaryOp` node, you could use :: - node = ast.UnaryOp() - node.op = ast.USub() - node.operand = ast.Constant() - node.operand.value = 5 - node.operand.lineno = 0 - node.operand.col_offset = 0 - node.lineno = 0 - node.col_offset = 0 - - or the more compact :: - node = ast.UnaryOp(ast.USub(), ast.Constant(5, lineno=0, col_offset=0), lineno=0, col_offset=0) + If a field that is optional in the grammar is omitted from the constructor, + it defaults to ``None``. If a list field is omitted, it defaults to the empty + list. If any other field is omitted, a :exc:`DeprecationWarning` is raised + and the AST node will not have this field. In Python 3.15, this condition will + raise an error. + .. versionchanged:: 3.8 Class :class:`ast.Constant` is now used for all constants. @@ -140,6 +135,14 @@ Node classes In the meantime, instantiating them will return an instance of a different class. +.. deprecated-removed:: 3.13 3.15 + + Previous versions of Python allowed the creation of AST nodes that were missing + required fields. Similarly, AST node constructors allowed arbitrary keyword + arguments that were set as attributes of the AST node, even if they did not + match any of the fields of the AST node. This behavior is deprecated and will + be removed in Python 3.15. + .. note:: The descriptions of the specific node classes displayed here were initially adapted from the fantastic `Green Tree @@ -2180,14 +2183,17 @@ and classes for traversing abstract syntax trees: modified to correspond to :pep:`484` "signature type comments", e.g. ``(str, int) -> List[str]``. - Also, setting ``feature_version`` to a tuple ``(major, minor)`` - will attempt to parse using that Python version's grammar. - Currently ``major`` must equal to ``3``. For example, setting - ``feature_version=(3, 4)`` will allow the use of ``async`` and - ``await`` as variable names. The lowest supported version is - ``(3, 7)``; the highest is ``sys.version_info[0:2]``. - - If source contains a null character ('\0'), :exc:`ValueError` is raised. + Setting ``feature_version`` to a tuple ``(major, minor)`` will result in + a "best-effort" attempt to parse using that Python version's grammar. + For example, setting ``feature_version=(3, 9)`` will attempt to disallow + parsing of :keyword:`match` statements. + Currently ``major`` must equal to ``3``. The lowest supported version is + ``(3, 7)`` (and this may increase in future Python versions); + the highest is ``sys.version_info[0:2]``. "Best-effort" attempt means there + is no guarantee that the parse (or success of the parse) is the same as + when run on the Python version corresponding to ``feature_version``. + + If source contains a null character (``\0``), :exc:`ValueError` is raised. .. warning:: Note that successfully parsing source code into an AST object doesn't diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index 828e506a72c9374..06c5c877ccc173e 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -605,6 +605,9 @@ Opening network connections The *family*, *proto*, *flags*, *reuse_address*, *reuse_port*, *allow_broadcast*, and *sock* parameters were added. + .. versionchanged:: 3.8 + Added support for Windows. + .. versionchanged:: 3.8.1 The *reuse_address* parameter is no longer supported, as using :ref:`socket.SO_REUSEADDR ` @@ -622,11 +625,8 @@ Opening network connections prevents processes with differing UIDs from assigning sockets to the same socket address. - .. versionchanged:: 3.8 - Added support for Windows. - .. versionchanged:: 3.11 - The *reuse_address* parameter, disabled since Python 3.9.0, 3.8.1, + The *reuse_address* parameter, disabled since Python 3.8.1, 3.7.6 and 3.6.10, has been entirely removed. .. coroutinemethod:: loop.create_unix_connection(protocol_factory, \ diff --git a/Doc/library/asyncio-stream.rst b/Doc/library/asyncio-stream.rst index 3427da1b43caef3..68b1dff20213e15 100644 --- a/Doc/library/asyncio-stream.rst +++ b/Doc/library/asyncio-stream.rst @@ -347,7 +347,7 @@ StreamWriter be resumed. When there is nothing to wait for, the :meth:`drain` returns immediately. - .. coroutinemethod:: start_tls(sslcontext, \*, server_hostname=None, \ + .. coroutinemethod:: start_tls(sslcontext, *, server_hostname=None, \ ssl_handshake_timeout=None, ssl_shutdown_timeout=None) Upgrade an existing stream-based connection to TLS. diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index 24bd36e6431b4f4..2aab62c64d29205 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -334,6 +334,13 @@ and reliable way to wait for all tasks in the group to finish. Create a task in this task group. The signature matches that of :func:`asyncio.create_task`. + If the task group is inactive (e.g. not yet entered, + already finished, or in the process of shutting down), + we will close the given ``coro``. + + .. versionchanged:: 3.13 + + Close the given coroutine if the task group is not active. Example:: diff --git a/Doc/library/audit_events.rst b/Doc/library/audit_events.rst index 8227a7955bef812..a2a90a00d0cfca3 100644 --- a/Doc/library/audit_events.rst +++ b/Doc/library/audit_events.rst @@ -7,7 +7,7 @@ Audit events table This table contains all events raised by :func:`sys.audit` or :c:func:`PySys_Audit` calls throughout the CPython runtime and the -standard library. These calls were added in 3.8.0 or later (see :pep:`578`). +standard library. These calls were added in 3.8 or later (see :pep:`578`). See :func:`sys.addaudithook` and :c:func:`PySys_AddAuditHook` for information on handling these events. diff --git a/Doc/library/base64.rst b/Doc/library/base64.rst index d5b6af8c1928eff..e596893358f3fb3 100644 --- a/Doc/library/base64.rst +++ b/Doc/library/base64.rst @@ -244,6 +244,24 @@ The modern interface provides: .. versionadded:: 3.4 +.. function:: z85encode(s) + + Encode the :term:`bytes-like object` *s* using Z85 (as used in ZeroMQ) + and return the encoded :class:`bytes`. See `Z85 specification + `_ for more information. + + .. versionadded:: 3.13 + + +.. function:: z85decode(s) + + Decode the Z85-encoded :term:`bytes-like object` or ASCII string *s* and + return the decoded :class:`bytes`. See `Z85 specification + `_ for more information. + + .. versionadded:: 3.13 + + The legacy interface: .. function:: decode(input, output) diff --git a/Doc/library/bz2.rst b/Doc/library/bz2.rst index 6a95a4a6e8d1832..5e0aa3e493f2249 100644 --- a/Doc/library/bz2.rst +++ b/Doc/library/bz2.rst @@ -156,7 +156,6 @@ The :mod:`bz2` module contains: Support was added for *filename* being a :term:`file object` instead of an actual filename. - .. versionchanged:: 3.3 The ``'a'`` (append) mode was added, along with support for reading multi-stream files. diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index c246173c1bbf53b..f868799e7f5c105 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -343,7 +343,7 @@ superset relationships: ``==``, ``!=``, ``<``, ``<=``, ``>``, ``>=``. All of those tests treat missing elements as having zero counts so that ``Counter(a=1) == Counter(a=1, b=0)`` returns true. -.. versionadded:: 3.10 +.. versionchanged:: 3.10 Rich comparison operations were added. .. versionchanged:: 3.10 diff --git a/Doc/library/configparser.rst b/Doc/library/configparser.rst index 18e5bc20f3f6904..445626c267fb6fe 100644 --- a/Doc/library/configparser.rst +++ b/Doc/library/configparser.rst @@ -978,6 +978,10 @@ ConfigParser Objects The default *dict_type* is :class:`dict`, since it now preserves insertion order. + .. versionchanged:: 3.13 + Raise a :exc:`MultilineContinuationError` when *allow_no_value* is + ``True``, and a key without a value is continued with an indented line. + .. method:: defaults() Return a dictionary containing the instance-wide defaults. @@ -1349,6 +1353,13 @@ Exceptions The ``filename`` attribute and :meth:`!__init__` constructor argument were removed. They have been available using the name ``source`` since 3.2. +.. exception:: MultilineContinuationError + + Exception raised when a key without a corresponding value is continued with + an indented line. + + .. versionadded:: 3.13 + .. rubric:: Footnotes .. [1] Config parsers allow for heavy customization. If you are interested in diff --git a/Doc/library/constants.rst b/Doc/library/constants.rst index 401dc9a320c5e06..93a7244f87de6b9 100644 --- a/Doc/library/constants.rst +++ b/Doc/library/constants.rst @@ -33,27 +33,27 @@ A small number of constants live in the built-in namespace. They are: the other type; may be returned by the in-place binary special methods (e.g. :meth:`~object.__imul__`, :meth:`~object.__iand__`, etc.) for the same purpose. It should not be evaluated in a boolean context. - ``NotImplemented`` is the sole instance of the :data:`types.NotImplementedType` type. + :data:`!NotImplemented` is the sole instance of the :data:`types.NotImplementedType` type. .. note:: - When a binary (or in-place) method returns ``NotImplemented`` the + When a binary (or in-place) method returns :data:`!NotImplemented` the interpreter will try the reflected operation on the other type (or some other fallback, depending on the operator). If all attempts return - ``NotImplemented``, the interpreter will raise an appropriate exception. - Incorrectly returning ``NotImplemented`` will result in a misleading - error message or the ``NotImplemented`` value being returned to Python code. + :data:`!NotImplemented`, the interpreter will raise an appropriate exception. + Incorrectly returning :data:`!NotImplemented` will result in a misleading + error message or the :data:`!NotImplemented` value being returned to Python code. See :ref:`implementing-the-arithmetic-operations` for examples. .. note:: - ``NotImplementedError`` and ``NotImplemented`` are not interchangeable, + ``NotImplementedError`` and :data:`!NotImplemented` are not interchangeable, even though they have similar names and purposes. See :exc:`NotImplementedError` for details on when to use it. .. versionchanged:: 3.9 - Evaluating ``NotImplemented`` in a boolean context is deprecated. While + Evaluating :data:`!NotImplemented` in a boolean context is deprecated. While it currently evaluates as true, it will emit a :exc:`DeprecationWarning`. It will raise a :exc:`TypeError` in a future version of Python. diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index 73779547b35a1f2..36976470b5a4688 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -93,7 +93,6 @@ Accessing functions from loaded dlls Functions are accessed as attributes of dll objects:: - >>> from ctypes import * >>> libc.printf <_FuncPtr object at 0x...> >>> print(windll.kernel32.GetModuleHandleA) # doctest: +WINDOWS @@ -200,7 +199,7 @@ calls). Python objects that can directly be used as parameters in these function calls. ``None`` is passed as a C ``NULL`` pointer, bytes objects and strings are passed as pointer to the memory block that contains their data (:c:expr:`char *` or -:c:expr:`wchar_t *`). Python integers are passed as the platforms default C +:c:expr:`wchar_t *`). Python integers are passed as the platform's default C :c:expr:`int` type, their value is masked to fit into the C type. Before we move on calling functions with other parameter types, we have to learn @@ -1117,10 +1116,6 @@ api:: >>> print(hex(version.value)) 0x30c00a0 -If the interpreter would have been started with :option:`-O`, the sample would -have printed ``c_long(1)``, or ``c_long(2)`` if :option:`-OO` would have been -specified. - An extended example which also demonstrates the use of pointers accesses the :c:data:`PyImport_FrozenModules` pointer exported by Python. @@ -1445,7 +1440,7 @@ function exported by these libraries, and reacquired afterwards. All these classes can be instantiated by calling them with at least one argument, the pathname of the shared library. If you have an existing handle to an already loaded shared library, it can be passed as the ``handle`` named -parameter, otherwise the underlying platforms :c:func:`!dlopen` or +parameter, otherwise the underlying platform's :c:func:`!dlopen` or :c:func:`!LoadLibrary` function is used to load the library into the process, and to get a handle to it. @@ -1456,7 +1451,7 @@ configurable. The *use_errno* parameter, when set to true, enables a ctypes mechanism that allows accessing the system :data:`errno` error number in a safe way. -:mod:`ctypes` maintains a thread-local copy of the systems :data:`errno` +:mod:`ctypes` maintains a thread-local copy of the system's :data:`errno` variable; if you call foreign functions created with ``use_errno=True`` then the :data:`errno` value before the function call is swapped with the ctypes private copy, the same happens immediately after the function call. diff --git a/Doc/library/dataclasses.rst b/Doc/library/dataclasses.rst index a8816df11b313a2..08d5eb56084ff53 100644 --- a/Doc/library/dataclasses.rst +++ b/Doc/library/dataclasses.rst @@ -724,7 +724,7 @@ Using dataclasses, *if* this code was valid:: class D: x: list = [] # This code raises ValueError def add(self, element): - self.x += element + self.x.append(element) it would generate code similar to:: @@ -733,7 +733,7 @@ it would generate code similar to:: def __init__(self, x=x): self.x = x def add(self, element): - self.x += element + self.x.append(element) assert D().x is D().x diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 4602132f37f7331..1905c9e1ca755d9 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -1209,6 +1209,9 @@ Supported operations: Naive and aware :class:`!datetime` objects are never equal. + If both comparands are aware, and have the same :attr:`!tzinfo` attribute, + the :attr:`!tzinfo` and :attr:`~.datetime.fold` attributes are ignored and + the base datetimes are compared. If both comparands are aware and have different :attr:`~.datetime.tzinfo` attributes, the comparison acts as comparands were first converted to UTC datetimes except that the implementation never overflows. @@ -1222,6 +1225,9 @@ Supported operations: Order comparison between naive and aware :class:`.datetime` objects raises :exc:`TypeError`. + If both comparands are aware, and have the same :attr:`!tzinfo` attribute, + the :attr:`!tzinfo` and :attr:`~.datetime.fold` attributes are ignored and + the base datetimes are compared. If both comparands are aware and have different :attr:`~.datetime.tzinfo` attributes, the comparison acts as comparands were first converted to UTC datetimes except that the implementation never overflows. @@ -1778,8 +1784,8 @@ Naive and aware :class:`!time` objects are never equal. Order comparison between naive and aware :class:`!time` objects raises :exc:`TypeError`. -If both comparands are aware, and have -the same :attr:`~.time.tzinfo` attribute, the common :attr:`!tzinfo` attribute is +If both comparands are aware, and have the same :attr:`~.time.tzinfo` +attribute, the :attr:`!tzinfo` and :attr:`!fold` attributes are ignored and the base times are compared. If both comparands are aware and have different :attr:`!tzinfo` attributes, the comparands are first adjusted by subtracting their UTC offsets (obtained from ``self.utcoffset()``). diff --git a/Doc/library/decimal.rst b/Doc/library/decimal.rst index fb8bbf72adf2688..8c671bf900712b0 100644 --- a/Doc/library/decimal.rst +++ b/Doc/library/decimal.rst @@ -1517,7 +1517,7 @@ are also included in the pure Python version for compatibility. the C version uses a thread-local rather than a coroutine-local context and the value is ``False``. This is slightly faster in some nested context scenarios. -.. versionadded:: 3.9 backported to 3.7 and 3.8. +.. versionadded:: 3.8.3 Rounding modes diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index 30d80ce8d488ccb..d84d9d9b4161b1e 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -170,7 +170,7 @@ Data Types final *enum*, as well as creating the enum members, properly handling duplicates, providing iteration over the enum class, etc. - .. method:: EnumType.__call__(cls, value, names=None, \*, module=None, qualname=None, type=None, start=1, boundary=None) + .. method:: EnumType.__call__(cls, value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None) This method is called in two different ways: @@ -279,6 +279,8 @@ Data Types >>> Color.RED.value 1 + Value of the member, can be set in :meth:`~object.__new__`. + .. note:: Enum member values Member values can be anything: :class:`int`, :class:`str`, etc. If @@ -286,6 +288,11 @@ Data Types appropriate value will be chosen for you. See :class:`auto` for the details. + While mutable/unhashable values, such as :class:`dict`, :class:`list` or + a mutable :class:`~dataclasses.dataclass`, can be used, they will have a + quadratic performance impact during creation relative to the + total number of mutable/unhashable values in the enum. + .. attribute:: Enum._name_ Name of the member. @@ -350,7 +357,7 @@ Data Types >>> PowersOfThree.SECOND.value 9 - .. method:: Enum.__init__(self, \*args, \**kwds) + .. method:: Enum.__init__(self, *args, **kwds) By default, does nothing. If multiple values are given in the member assignment, those values become separate arguments to ``__init__``; e.g. @@ -361,7 +368,7 @@ Data Types ``Weekday.__init__()`` would be called as ``Weekday.__init__(self, 1, 'Mon')`` - .. method:: Enum.__init_subclass__(cls, \**kwds) + .. method:: Enum.__init_subclass__(cls, **kwds) A *classmethod* that is used to further configure subsequent subclasses. By default, does nothing. @@ -388,7 +395,7 @@ Data Types >>> Build('deBUG') - .. method:: Enum.__new__(cls, \*args, \**kwds) + .. method:: Enum.__new__(cls, *args, **kwds) By default, doesn't exist. If specified, either in the enum class definition or in a mixin class (such as ``int``), all values given @@ -400,6 +407,9 @@ Data Types results in the call ``int('1a', 16)`` and a value of ``17`` for the member. + ..note:: When writing a custom ``__new__``, do not use ``super().__new__`` -- + call the appropriate ``__new__`` instead. + .. method:: Enum.__repr__(self) Returns the string used for *repr()* calls. By default, returns the diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index 88417b40e4aa7f9..7879fb015bddfa3 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -335,9 +335,9 @@ The following exceptions are the exceptions that are usually raised. .. note:: - ``NotImplementedError`` and ``NotImplemented`` are not interchangeable, + ``NotImplementedError`` and :data:`NotImplemented` are not interchangeable, even though they have similar names and purposes. See - :data:`NotImplemented` for details on when to use it. + :data:`!NotImplemented` for details on when to use it. .. exception:: OSError([arg]) OSError(errno, strerror[, filename[, winerror[, filename2]]]) diff --git a/Doc/library/faulthandler.rst b/Doc/library/faulthandler.rst index f64dfeb5e081c7c..96593ee97a139d9 100644 --- a/Doc/library/faulthandler.rst +++ b/Doc/library/faulthandler.rst @@ -118,12 +118,12 @@ Dumping the tracebacks after a timeout This function is implemented using a watchdog thread. - .. versionchanged:: 3.7 - This function is now always available. - .. versionchanged:: 3.5 Added support for passing file descriptor to this function. + .. versionchanged:: 3.7 + This function is now always available. + .. function:: cancel_dump_traceback_later() Cancel the last call to :func:`dump_traceback_later`. diff --git a/Doc/library/fcntl.rst b/Doc/library/fcntl.rst index 13ad2dd7da5090a..b93d6ac7aab9565 100644 --- a/Doc/library/fcntl.rst +++ b/Doc/library/fcntl.rst @@ -13,10 +13,10 @@ ---------------- -This module performs file control and I/O control on file descriptors. It is an -interface to the :c:func:`fcntl` and :c:func:`ioctl` Unix routines. For a -complete description of these calls, see :manpage:`fcntl(2)` and -:manpage:`ioctl(2)` Unix manual pages. +This module performs file and I/O control on file descriptors. It is an +interface to the :c:func:`fcntl` and :c:func:`ioctl` Unix routines. +See the :manpage:`fcntl(2)` and :manpage:`ioctl(2)` Unix manual pages +for full details. .. availability:: Unix, not Emscripten, not WASI. @@ -101,7 +101,7 @@ The module defines the following functions: most likely to result in a segmentation violation or a more subtle data corruption. - If the :c:func:`fcntl` fails, an :exc:`OSError` is raised. + If the :c:func:`fcntl` call fails, an :exc:`OSError` is raised. .. audit-event:: fcntl.fcntl fd,cmd,arg fcntl.fcntl @@ -139,7 +139,7 @@ The module defines the following functions: buffer 1024 bytes long which is then passed to :func:`ioctl` and copied back into the supplied buffer. - If the :c:func:`ioctl` fails, an :exc:`OSError` exception is raised. + If the :c:func:`ioctl` call fails, an :exc:`OSError` exception is raised. An example:: @@ -164,7 +164,7 @@ The module defines the following functions: :manpage:`flock(2)` for details. (On some systems, this function is emulated using :c:func:`fcntl`.) - If the :c:func:`flock` fails, an :exc:`OSError` exception is raised. + If the :c:func:`flock` call fails, an :exc:`OSError` exception is raised. .. audit-event:: fcntl.flock fd,operation fcntl.flock @@ -176,17 +176,28 @@ The module defines the following functions: method are accepted as well) of the file to lock or unlock, and *cmd* is one of the following values: - * :const:`LOCK_UN` -- unlock - * :const:`LOCK_SH` -- acquire a shared lock - * :const:`LOCK_EX` -- acquire an exclusive lock + .. data:: LOCK_UN - When *cmd* is :const:`LOCK_SH` or :const:`LOCK_EX`, it can also be - bitwise ORed with :const:`LOCK_NB` to avoid blocking on lock acquisition. - If :const:`LOCK_NB` is used and the lock cannot be acquired, an + Release an existing lock. + + .. data:: LOCK_SH + + Acquire a shared lock. + + .. data:: LOCK_EX + + Acquire an exclusive lock. + + .. data:: LOCK_NB + + Bitwise OR with any of the other three ``LOCK_*`` constants to make + the request non-blocking. + + If :const:`!LOCK_NB` is used and the lock cannot be acquired, an :exc:`OSError` will be raised and the exception will have an *errno* - attribute set to :const:`EACCES` or :const:`EAGAIN` (depending on the + attribute set to :const:`~errno.EACCES` or :const:`~errno.EAGAIN` (depending on the operating system; for portability, check for both values). On at least some - systems, :const:`LOCK_EX` can only be used if the file descriptor refers to a + systems, :const:`!LOCK_EX` can only be used if the file descriptor refers to a file opened for writing. *len* is the number of bytes to lock, *start* is the byte offset at diff --git a/Doc/library/filecmp.rst b/Doc/library/filecmp.rst index dfe4b7c59fd578b..42d20b9c2017830 100644 --- a/Doc/library/filecmp.rst +++ b/Doc/library/filecmp.rst @@ -70,7 +70,7 @@ The :mod:`filecmp` module defines the following functions: The :class:`dircmp` class ------------------------- -.. class:: dircmp(a, b, ignore=None, hide=None) +.. class:: dircmp(a, b, ignore=None, hide=None, shallow=True) Construct a new directory comparison object, to compare the directories *a* and *b*. *ignore* is a list of names to ignore, and defaults to @@ -78,7 +78,12 @@ The :class:`dircmp` class defaults to ``[os.curdir, os.pardir]``. The :class:`dircmp` class compares files by doing *shallow* comparisons - as described for :func:`filecmp.cmp`. + as described for :func:`filecmp.cmp` by default using the *shallow* + parameter. + + .. versionchanged:: 3.13 + + Added the *shallow* parameter. The :class:`dircmp` class provides the following methods: diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 27fce5aa0f1a636..e598ef423de4972 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -526,9 +526,20 @@ are always available. They are listed here in alphabetical order. .. function:: eval(expression, globals=None, locals=None) - The arguments are a string and optional globals and locals. If provided, - *globals* must be a dictionary. If provided, *locals* can be any mapping - object. + :param expression: + A Python expression. + :type expression: :class:`str` | :ref:`code object ` + + :param globals: + The global namespace (default: ``None``). + :type globals: :class:`dict` | ``None`` + + :param locals: + The local namespace (default: ``None``). + :type locals: :term:`mapping` | ``None`` + + :returns: The result of the evaluated expression. + :raises: Syntax errors are reported as exceptions. The *expression* argument is parsed and evaluated as a Python expression (technically speaking, a condition list) using the *globals* and *locals* @@ -545,8 +556,7 @@ are always available. They are listed here in alphabetical order. :term:`nested scopes ` (non-locals) in the enclosing environment. - The return value is the result of - the evaluated expression. Syntax errors are reported as exceptions. Example: + Example: >>> x = 1 >>> eval('x+1') @@ -1569,6 +1579,16 @@ are always available. They are listed here in alphabetical order. If :func:`sys.displayhook` is not accessible, this function will raise :exc:`RuntimeError`. + This class has a custom representation that can be evaluated:: + + class Person: + def __init__(self, name, age): + self.name = name + self.age = age + + def __repr__(self): + return f"Person('{self.name}', {self.age})" + .. function:: reversed(seq) diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst index 20fcbe76c36985e..82c970d25a7aac3 100644 --- a/Doc/library/functools.rst +++ b/Doc/library/functools.rst @@ -667,13 +667,9 @@ The :mod:`functools` module defines the following functions: on the wrapper function). :exc:`AttributeError` is still raised if the wrapper function itself is missing any attributes named in *updated*. - .. versionadded:: 3.2 - Automatic addition of the ``__wrapped__`` attribute. - - .. versionadded:: 3.2 - Copying of the ``__annotations__`` attribute by default. - .. versionchanged:: 3.2 + The ``__wrapped__`` attribute is now automatically added. + The ``__annotations__`` attribute is now copied by default. Missing attributes no longer trigger an :exc:`AttributeError`. .. versionchanged:: 3.4 diff --git a/Doc/library/glob.rst b/Doc/library/glob.rst index 19a0bbba8966bae..15fef747296ed4f 100644 --- a/Doc/library/glob.rst +++ b/Doc/library/glob.rst @@ -136,8 +136,7 @@ The :mod:`glob` module defines the following functions: separators, and ``*`` pattern segments match precisely one path segment. If *recursive* is true, the pattern segment "``**``" will match any number - of path segments. If "``**``" occurs in any position other than a full - pattern segment, :exc:`ValueError` is raised. + of path segments. If *include_hidden* is true, wildcards can match path segments that start with a dot (``.``). diff --git a/Doc/library/hashlib.rst b/Doc/library/hashlib.rst index 761dd84edee2996..aa0c6fc503e8ff2 100644 --- a/Doc/library/hashlib.rst +++ b/Doc/library/hashlib.rst @@ -77,8 +77,6 @@ accessible by name via :func:`new`. See :data:`algorithms_available`. SHA3 (Keccak) and SHAKE constructors :func:`sha3_224`, :func:`sha3_256`, :func:`sha3_384`, :func:`sha3_512`, :func:`shake_128`, :func:`shake_256` were added. - -.. versionadded:: 3.6 :func:`blake2b` and :func:`blake2s` were added. .. _hashlib-usedforsecurity: @@ -121,7 +119,7 @@ More condensed: Constructors ------------ -.. function:: new(name[, data], \*, usedforsecurity=True) +.. function:: new(name[, data], *, usedforsecurity=True) Is a generic constructor that takes the string *name* of the desired algorithm as its first parameter. It also exists to allow access to the diff --git a/Doc/library/http.server.rst b/Doc/library/http.server.rst index bc59d3d17912fd0..886e359bd8cd621 100644 --- a/Doc/library/http.server.rst +++ b/Doc/library/http.server.rst @@ -520,6 +520,12 @@ the ``--cgi`` option:: :mod:`http.server` command line ``--cgi`` support is being removed because :class:`CGIHTTPRequestHandler` is being removed. +.. warning:: + + :class:`CGIHTTPRequestHandler` and the ``--cgi`` command line option + are not intended for use by untrusted clients and may be vulnerable + to exploitation. Always use within a secure environment. + .. _http.server-security: Security Considerations diff --git a/Doc/library/idle.rst b/Doc/library/idle.rst index 249dc0ea6ba735b..17a5144b4c0635f 100644 --- a/Doc/library/idle.rst +++ b/Doc/library/idle.rst @@ -604,7 +604,7 @@ in an editor window. The editing features described in previous subsections work when entering code interactively. IDLE's Shell window also responds to the following: -* :kbd:`C-c` attemps to interrupt statement execution (but may fail). +* :kbd:`C-c` attempts to interrupt statement execution (but may fail). * :kbd:`C-d` closes Shell if typed at a ``>>>`` prompt. diff --git a/Doc/library/importlib.metadata.rst b/Doc/library/importlib.metadata.rst index cc4a0da92da60a9..f4fb7791855307b 100644 --- a/Doc/library/importlib.metadata.rst +++ b/Doc/library/importlib.metadata.rst @@ -219,7 +219,6 @@ all the metadata in a JSON-compatible form per :PEP:`566`:: The ``Description`` is now included in the metadata when presented through the payload. Line continuation characters have been removed. -.. versionadded:: 3.10 The ``json`` attribute was added. diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst index 2402bc5cd3ee2c3..d92bb2f8e5cf83d 100644 --- a/Doc/library/importlib.rst +++ b/Doc/library/importlib.rst @@ -265,7 +265,7 @@ ABC hierarchy:: when invalidating the caches of all finders on :data:`sys.meta_path`. .. versionchanged:: 3.4 - Returns ``None`` when called instead of ``NotImplemented``. + Returns ``None`` when called instead of :data:`NotImplemented`. .. class:: PathEntryFinder diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 8a74cadb98a0dbd..4a0a090facb8bb4 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -55,6 +55,11 @@ attributes (see :ref:`import-mod-attrs` for module attributes): | | __module__ | name of module in which | | | | this class was defined | +-----------+-------------------+---------------------------+ +| | __type_params__ | A tuple containing the | +| | | :ref:`type parameters | +| | | ` of | +| | | a generic class | ++-----------+-------------------+---------------------------+ | method | __doc__ | documentation string | +-----------+-------------------+---------------------------+ | | __name__ | name with which this | @@ -103,6 +108,11 @@ attributes (see :ref:`import-mod-attrs` for module attributes): | | | reserved for return | | | | annotations. | +-----------+-------------------+---------------------------+ +| | __type_params__ | A tuple containing the | +| | | :ref:`type parameters | +| | | ` of | +| | | a generic function | ++-----------+-------------------+---------------------------+ | | __module__ | name of module in which | | | | this function was defined | +-----------+-------------------+---------------------------+ @@ -665,9 +675,6 @@ function. Accepts a wide range of Python callables, from plain functions and classes to :func:`functools.partial` objects. - If the passed object has a ``__signature__`` attribute, this function - returns it without further computations. - For objects defined in modules using stringized annotations (``from __future__ import annotations``), :func:`signature` will attempt to automatically un-stringize the annotations using @@ -702,6 +709,13 @@ function. Python. For example, in CPython, some built-in functions defined in C provide no metadata about their arguments. + .. impl-detail:: + + If the passed object has a :attr:`!__signature__` attribute, + we may use it to create the signature. + The exact semantics are an implementation detail and are subject to + unannounced changes. Consult the source code for current semantics. + .. class:: Signature(parameters=None, *, return_annotation=Signature.empty) diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index 1de36b643c4dca6..73f4960082617b8 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -121,22 +121,12 @@ write code that handles both IP versions correctly. Address objects are Leading zeros are tolerated, even in ambiguous cases that look like octal notation. - .. versionchanged:: 3.10 + .. versionchanged:: 3.9.5 Leading zeros are no longer tolerated and are treated as an error. IPv4 address strings are now parsed as strict as glibc :func:`~socket.inet_pton`. - .. versionchanged:: 3.9.5 - - The above change was also included in Python 3.9 starting with - version 3.9.5. - - .. versionchanged:: 3.8.12 - - The above change was also included in Python 3.8 starting with - version 3.8.12. - .. attribute:: version The appropriate version number: ``4`` for IPv4, ``6`` for IPv6. @@ -188,15 +178,34 @@ write code that handles both IP versions correctly. Address objects are .. attribute:: is_private - ``True`` if the address is allocated for private networks. See + ``True`` if the address is defined as not globally reachable by iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ - (for IPv6). + (for IPv6) with the following exceptions: + + * ``is_private`` is ``False`` for the shared address space (``100.64.0.0/10``) + * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the + semantics of the underlying IPv4 addresses and the following condition holds + (see :attr:`IPv6Address.ipv4_mapped`):: + + address.is_private == address.ipv4_mapped.is_private + + ``is_private`` has value opposite to :attr:`is_global`, except for the shared address space + (``100.64.0.0/10`` range) where they are both ``False``. .. attribute:: is_global - ``True`` if the address is allocated for public networks. See + ``True`` if the address is defined as globally reachable by iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ - (for IPv6). + (for IPv6) with the following exception: + + For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the + semantics of the underlying IPv4 addresses and the following condition holds + (see :attr:`IPv6Address.ipv4_mapped`):: + + address.is_global == address.ipv4_mapped.is_global + + ``is_global`` has value opposite to :attr:`is_private`, except for the shared address space + (``100.64.0.0/10`` range) where they are both ``False``. .. versionadded:: 3.4 diff --git a/Doc/library/itertools.rst b/Doc/library/itertools.rst index 338a5f9615aae31..b092efe0dc362b0 100644 --- a/Doc/library/itertools.rst +++ b/Doc/library/itertools.rst @@ -41,9 +41,9 @@ operator can be mapped across two vectors to form an efficient dot-product: ================== ================= ================================================= ========================================= Iterator Arguments Results Example ================== ================= ================================================= ========================================= -:func:`count` [start[, step]] start, start+step, start+2*step, ... ``count(10) --> 10 11 12 13 14 ...`` -:func:`cycle` p p0, p1, ... plast, p0, p1, ... ``cycle('ABCD') --> A B C D A B C D ...`` -:func:`repeat` elem [,n] elem, elem, elem, ... endlessly or up to n times ``repeat(10, 3) --> 10 10 10`` +:func:`count` [start[, step]] start, start+step, start+2*step, ... ``count(10) → 10 11 12 13 14 ...`` +:func:`cycle` p p0, p1, ... plast, p0, p1, ... ``cycle('ABCD') → A B C D A B C D ...`` +:func:`repeat` elem [,n] elem, elem, elem, ... endlessly or up to n times ``repeat(10, 3) → 10 10 10`` ================== ================= ================================================= ========================================= **Iterators terminating on the shortest input sequence:** @@ -51,20 +51,20 @@ Iterator Arguments Results ============================ ============================ ================================================= ============================================================= Iterator Arguments Results Example ============================ ============================ ================================================= ============================================================= -:func:`accumulate` p [,func] p0, p0+p1, p0+p1+p2, ... ``accumulate([1,2,3,4,5]) --> 1 3 6 10 15`` -:func:`batched` p, n (p0, p1, ..., p_n-1), ... ``batched('ABCDEFG', n=3) --> ABC DEF G`` -:func:`chain` p, q, ... p0, p1, ... plast, q0, q1, ... ``chain('ABC', 'DEF') --> A B C D E F`` -:func:`chain.from_iterable` iterable p0, p1, ... plast, q0, q1, ... ``chain.from_iterable(['ABC', 'DEF']) --> A B C D E F`` -:func:`compress` data, selectors (d[0] if s[0]), (d[1] if s[1]), ... ``compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F`` -:func:`dropwhile` pred, seq seq[n], seq[n+1], starting when pred fails ``dropwhile(lambda x: x<5, [1,4,6,4,1]) --> 6 4 1`` -:func:`filterfalse` pred, seq elements of seq where pred(elem) is false ``filterfalse(lambda x: x%2, range(10)) --> 0 2 4 6 8`` +:func:`accumulate` p [,func] p0, p0+p1, p0+p1+p2, ... ``accumulate([1,2,3,4,5]) → 1 3 6 10 15`` +:func:`batched` p, n (p0, p1, ..., p_n-1), ... ``batched('ABCDEFG', n=3) → ABC DEF G`` +:func:`chain` p, q, ... p0, p1, ... plast, q0, q1, ... ``chain('ABC', 'DEF') → A B C D E F`` +:func:`chain.from_iterable` iterable p0, p1, ... plast, q0, q1, ... ``chain.from_iterable(['ABC', 'DEF']) → A B C D E F`` +:func:`compress` data, selectors (d[0] if s[0]), (d[1] if s[1]), ... ``compress('ABCDEF', [1,0,1,0,1,1]) → A C E F`` +:func:`dropwhile` predicate, seq seq[n], seq[n+1], starting when predicate fails ``dropwhile(lambda x: x<5, [1,4,6,4,1]) → 6 4 1`` +:func:`filterfalse` predicate, seq elements of seq where predicate(elem) fails ``filterfalse(lambda x: x%2, range(10)) → 0 2 4 6 8`` :func:`groupby` iterable[, key] sub-iterators grouped by value of key(v) -:func:`islice` seq, [start,] stop [, step] elements from seq[start:stop:step] ``islice('ABCDEFG', 2, None) --> C D E F G`` -:func:`pairwise` iterable (p[0], p[1]), (p[1], p[2]) ``pairwise('ABCDEFG') --> AB BC CD DE EF FG`` -:func:`starmap` func, seq func(\*seq[0]), func(\*seq[1]), ... ``starmap(pow, [(2,5), (3,2), (10,3)]) --> 32 9 1000`` -:func:`takewhile` pred, seq seq[0], seq[1], until pred fails ``takewhile(lambda x: x<5, [1,4,6,4,1]) --> 1 4`` +:func:`islice` seq, [start,] stop [, step] elements from seq[start:stop:step] ``islice('ABCDEFG', 2, None) → C D E F G`` +:func:`pairwise` iterable (p[0], p[1]), (p[1], p[2]) ``pairwise('ABCDEFG') → AB BC CD DE EF FG`` +:func:`starmap` func, seq func(\*seq[0]), func(\*seq[1]), ... ``starmap(pow, [(2,5), (3,2), (10,3)]) → 32 9 1000`` +:func:`takewhile` predicate, seq seq[0], seq[1], until predicate fails ``takewhile(lambda x: x<5, [1,4,6,4,1]) → 1 4`` :func:`tee` it, n it1, it2, ... itn splits one iterator into n -:func:`zip_longest` p, q, ... (p[0], q[0]), (p[1], q[1]), ... ``zip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-`` +:func:`zip_longest` p, q, ... (p[0], q[0]), (p[1], q[1]), ... ``zip_longest('ABCD', 'xy', fillvalue='-') → Ax By C- D-`` ============================ ============================ ================================================= ============================================================= **Combinatoric iterators:** @@ -84,13 +84,13 @@ Examples Results ``product('ABCD', repeat=2)`` ``AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD`` ``permutations('ABCD', 2)`` ``AB AC AD BA BC BD CA CB CD DA DB DC`` ``combinations('ABCD', 2)`` ``AB AC AD BC BD CD`` -``combinations_with_replacement('ABCD', 2)`` ``AA AB AC AD BB BC BD CC CD DD`` +``combinations_with_replacement('ABCD', 2)`` ``AA AB AC AD BB BC BD CC CD DD`` ============================================== ============================================================= .. _itertools-functions: -Itertool functions +Itertool Functions ------------------ The following module functions all construct and return iterators. Some provide @@ -119,9 +119,9 @@ loops that truncate the stream. def accumulate(iterable, func=operator.add, *, initial=None): 'Return running totals' - # accumulate([1,2,3,4,5]) --> 1 3 6 10 15 - # accumulate([1,2,3,4,5], initial=100) --> 100 101 103 106 110 115 - # accumulate([1,2,3,4,5], operator.mul) --> 1 2 6 24 120 + # accumulate([1,2,3,4,5]) → 1 3 6 10 15 + # accumulate([1,2,3,4,5], initial=100) → 100 101 103 106 110 115 + # accumulate([1,2,3,4,5], operator.mul) → 1 2 6 24 120 it = iter(iterable) total = initial if initial is None: @@ -194,7 +194,7 @@ loops that truncate the stream. Roughly equivalent to:: def batched(iterable, n, *, strict=False): - # batched('ABCDEFG', 3) --> ABC DEF G + # batched('ABCDEFG', 3) → ABC DEF G if n < 1: raise ValueError('n must be at least one') it = iter(iterable) @@ -217,7 +217,7 @@ loops that truncate the stream. Roughly equivalent to:: def chain(*iterables): - # chain('ABC', 'DEF') --> A B C D E F + # chain('ABC', 'DEF') → A B C D E F for it in iterables: for element in it: yield element @@ -229,7 +229,7 @@ loops that truncate the stream. single iterable argument that is evaluated lazily. Roughly equivalent to:: def from_iterable(iterables): - # chain.from_iterable(['ABC', 'DEF']) --> A B C D E F + # chain.from_iterable(['ABC', 'DEF']) → A B C D E F for it in iterables: for element in it: yield element @@ -250,8 +250,8 @@ loops that truncate the stream. Roughly equivalent to:: def combinations(iterable, r): - # combinations('ABCD', 2) --> AB AC AD BC BD CD - # combinations(range(4), 3) --> 012 013 023 123 + # combinations('ABCD', 2) → AB AC AD BC BD CD + # combinations(range(4), 3) → 012 013 023 123 pool = tuple(iterable) n = len(pool) if r > n: @@ -299,7 +299,7 @@ loops that truncate the stream. Roughly equivalent to:: def combinations_with_replacement(iterable, r): - # combinations_with_replacement('ABC', 2) --> AA AB AC BB BC CC + # combinations_with_replacement('ABC', 2) → AA AB AC BB BC CC pool = tuple(iterable) n = len(pool) if not n and r: @@ -339,7 +339,7 @@ loops that truncate the stream. Roughly equivalent to:: def compress(data, selectors): - # compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F + # compress('ABCDEF', [1,0,1,0,1,1]) → A C E F return (d for d, s in zip(data, selectors) if s) .. versionadded:: 3.1 @@ -352,8 +352,8 @@ loops that truncate the stream. Also, used with :func:`zip` to add sequence numbers. Roughly equivalent to:: def count(start=0, step=1): - # count(10) --> 10 11 12 13 14 ... - # count(2.5, 0.5) --> 2.5 3.0 3.5 ... + # count(10) → 10 11 12 13 14 ... + # count(2.5, 0.5) → 2.5 3.0 3.5 ... n = start while True: yield n @@ -373,7 +373,7 @@ loops that truncate the stream. indefinitely. Roughly equivalent to:: def cycle(iterable): - # cycle('ABCD') --> A B C D A B C D A B C D ... + # cycle('ABCD') → A B C D A B C D A B C D ... saved = [] for element in iterable: yield element @@ -394,7 +394,7 @@ loops that truncate the stream. start-up time. Roughly equivalent to:: def dropwhile(predicate, iterable): - # dropwhile(lambda x: x<5, [1,4,6,4,1]) --> 6 4 1 + # dropwhile(lambda x: x<5, [1,4,6,4,1]) → 6 4 1 iterable = iter(iterable) for x in iterable: if not predicate(x): @@ -410,7 +410,7 @@ loops that truncate the stream. that are false. Roughly equivalent to:: def filterfalse(predicate, iterable): - # filterfalse(lambda x: x%2, range(10)) --> 0 2 4 6 8 + # filterfalse(lambda x: x%2, range(10)) → 0 2 4 6 8 if predicate is None: predicate = bool for x in iterable: @@ -447,8 +447,8 @@ loops that truncate the stream. :func:`groupby` is roughly equivalent to:: class groupby: - # [k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B - # [list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D + # [k for k, g in groupby('AAAABBBCCDAABBB')] → A B C D A B + # [list(g) for k, g in groupby('AAAABBBCCD')] → AAAA BBB CC D def __init__(self, iterable, key=None): if key is None: @@ -499,10 +499,10 @@ loops that truncate the stream. Roughly equivalent to:: def islice(iterable, *args): - # islice('ABCDEFG', 2) --> A B - # islice('ABCDEFG', 2, 4) --> C D - # islice('ABCDEFG', 2, None) --> C D E F G - # islice('ABCDEFG', 0, None, 2) --> A C E G + # islice('ABCDEFG', 2) → A B + # islice('ABCDEFG', 2, 4) → C D + # islice('ABCDEFG', 2, None) → C D E F G + # islice('ABCDEFG', 0, None, 2) → A C E G s = slice(*args) start, stop, step = s.start or 0, s.stop or sys.maxsize, s.step or 1 it = iter(range(start, stop, step)) @@ -535,10 +535,12 @@ loops that truncate the stream. Roughly equivalent to:: def pairwise(iterable): - # pairwise('ABCDEFG') --> AB BC CD DE EF FG - a, b = tee(iterable) - next(b, None) - return zip(a, b) + # pairwise('ABCDEFG') → AB BC CD DE EF FG + iterator = iter(iterable) + a = next(iterator, None) + for b in iterator: + yield a, b + a = b .. versionadded:: 3.10 @@ -562,8 +564,8 @@ loops that truncate the stream. Roughly equivalent to:: def permutations(iterable, r=None): - # permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC - # permutations(range(3)) --> 012 021 102 120 201 210 + # permutations('ABCD', 2) → AB AC AD BA BC BD CA CB CD DA DB DC + # permutations(range(3)) → 012 021 102 120 201 210 pool = tuple(iterable) n = len(pool) r = n if r is None else r @@ -621,8 +623,8 @@ loops that truncate the stream. actual implementation does not build up intermediate results in memory:: def product(*args, repeat=1): - # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy - # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111 + # product('ABCD', 'xy') → Ax Ay Bx By Cx Cy Dx Dy + # product(range(2), repeat=3) → 000 001 010 011 100 101 110 111 pools = [tuple(pool) for pool in args] * repeat result = [[]] for pool in pools: @@ -642,7 +644,7 @@ loops that truncate the stream. Roughly equivalent to:: def repeat(object, times=None): - # repeat(10, 3) --> 10 10 10 + # repeat(10, 3) → 10 10 10 if times is None: while True: yield object @@ -670,7 +672,7 @@ loops that truncate the stream. equivalent to:: def starmap(function, iterable): - # starmap(pow, [(2,5), (3,2), (10,3)]) --> 32 9 1000 + # starmap(pow, [(2,5), (3,2), (10,3)]) → 32 9 1000 for args in iterable: yield function(*args) @@ -681,13 +683,21 @@ loops that truncate the stream. predicate is true. Roughly equivalent to:: def takewhile(predicate, iterable): - # takewhile(lambda x: x<5, [1,4,6,4,1]) --> 1 4 + # takewhile(lambda x: x<5, [1,4,6,4,1]) → 1 4 for x in iterable: if predicate(x): yield x else: break + Note, the element that first fails the predicate condition is + consumed from the input iterator and there is no way to access it. + This could be an issue if an application wants to further consume the + input iterator after takewhile has been run to exhaustion. To work + around this problem, consider using `more-iterools before_and_after() + `_ + instead. + .. function:: tee(iterable, n=2) @@ -733,7 +743,7 @@ loops that truncate the stream. Iteration continues until the longest iterable is exhausted. Roughly equivalent to:: def zip_longest(*args, fillvalue=None): - # zip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D- + # zip_longest('ABCD', 'xy', fillvalue='-') → Ax By C- D- iterators = [iter(it) for it in args] num_active = len(iterators) if not num_active: @@ -770,15 +780,15 @@ The primary purpose of the itertools recipes is educational. The recipes show various ways of thinking about individual tools — for example, that ``chain.from_iterable`` is related to the concept of flattening. The recipes also give ideas about ways that the tools can be combined — for example, how -``compress()`` and ``range()`` can work together. The recipes also show patterns +``starmap()`` and ``repeat()`` can work together. The recipes also show patterns for using itertools with the :mod:`operator` and :mod:`collections` modules as well as with the built-in itertools such as ``map()``, ``filter()``, ``reversed()``, and ``enumerate()``. A secondary purpose of the recipes is to serve as an incubator. The ``accumulate()``, ``compress()``, and ``pairwise()`` itertools started out as -recipes. Currently, the ``sliding_window()`` and ``iter_index()`` recipes -are being tested to see whether they prove their worth. +recipes. Currently, the ``sliding_window()``, ``iter_index()``, and ``sieve()`` +recipes are being tested to see whether they prove their worth. Substantially all of these recipes and many, many others can be installed from the `more-itertools project `_ found @@ -787,12 +797,12 @@ on the Python Package Index:: python -m pip install more-itertools Many of the recipes offer the same high performance as the underlying toolset. -Superior memory performance is kept by processing elements one at a time -rather than bringing the whole iterable into memory all at once. Code volume is -kept small by linking the tools together in a functional style which helps -eliminate temporary variables. High speed is retained by preferring -"vectorized" building blocks over the use of for-loops and :term:`generator`\s -which incur interpreter overhead. +Superior memory performance is kept by processing elements one at a time rather +than bringing the whole iterable into memory all at once. Code volume is kept +small by linking the tools together in a `functional style +`_. High speed +is retained by preferring "vectorized" building blocks over the use of for-loops +and :term:`generators ` which incur interpreter overhead. .. testcode:: @@ -808,7 +818,7 @@ which incur interpreter overhead. def prepend(value, iterable): "Prepend a single value in front of an iterable." - # prepend(1, [2, 3, 4]) --> 1 2 3 4 + # prepend(1, [2, 3, 4]) → 1 2 3 4 return chain([value], iterable) def tabulate(function, start=0): @@ -834,7 +844,7 @@ which incur interpreter overhead. def tail(n, iterable): "Return an iterator over the last n items." - # tail(3, 'ABCDEFG') --> E F G + # tail(3, 'ABCDEFG') → E F G return iter(collections.deque(iterable, maxlen=n)) def consume(iterator, n=None): @@ -851,32 +861,33 @@ which incur interpreter overhead. "Returns the nth item or a default value." return next(islice(iterable, n, None), default) - def quantify(iterable, pred=bool): + def quantify(iterable, predicate=bool): "Given a predicate that returns True or False, count the True results." - return sum(map(pred, iterable)) - - def all_equal(iterable): - "Returns True if all the elements are equal to each other." - g = groupby(iterable) - return next(g, True) and not next(g, False) + return sum(map(predicate, iterable)) - def first_true(iterable, default=False, pred=None): - """Returns the first true value in the iterable. + def first_true(iterable, default=False, predicate=None): + "Returns the first true value or the *default* if there is no true value." + # first_true([a,b,c], x) → a or b or c or x + # first_true([a,b], x, f) → a if f(a) else b if f(b) else x + return next(filter(predicate, iterable), default) - If no true value is found, returns *default* - - If *pred* is not None, returns the first item - for which pred(item) is true. + def all_equal(iterable, key=None): + "Returns True if all the elements are equal to each other." + # all_equal('4٤໔4৪', key=int) → True + return len(take(2, groupby(iterable, key))) <= 1 - """ - # first_true([a,b,c], x) --> a or b or c or x - # first_true([a,b], x, f) --> a if f(a) else b if f(b) else x - return next(filter(pred, iterable), default) + def unique_justseen(iterable, key=None): + "List unique elements, preserving order. Remember only the element just seen." + # unique_justseen('AAAABBBCCDAABBB') → A B C D A B + # unique_justseen('ABBcCAD', str.casefold) → A B c A D + if key is None: + return map(operator.itemgetter(0), groupby(iterable)) + return map(next, map(operator.itemgetter(1), groupby(iterable, key))) def unique_everseen(iterable, key=None): "List unique elements, preserving order. Remember all elements ever seen." - # unique_everseen('AAAABBBCCDAABBB') --> A B C D - # unique_everseen('ABBcCAD', str.casefold) --> A B c D + # unique_everseen('AAAABBBCCDAABBB') → A B C D + # unique_everseen('ABBcCAD', str.casefold) → A B c D seen = set() if key is None: for element in filterfalse(seen.__contains__, iterable): @@ -889,37 +900,9 @@ which incur interpreter overhead. seen.add(k) yield element - def unique_justseen(iterable, key=None): - "List unique elements, preserving order. Remember only the element just seen." - # unique_justseen('AAAABBBCCDAABBB') --> A B C D A B - # unique_justseen('ABBcCAD', str.casefold) --> A B c A D - if key is None: - return map(operator.itemgetter(0), groupby(iterable)) - return map(next, map(operator.itemgetter(1), groupby(iterable, key))) - - def iter_index(iterable, value, start=0, stop=None): - "Return indices where a value occurs in a sequence or iterable." - # iter_index('AABCADEAF', 'A') --> 0 1 4 7 - seq_index = getattr(iterable, 'index', None) - if seq_index is None: - # Slow path for general iterables - it = islice(iterable, start, stop) - for i, element in enumerate(it, start): - if element is value or element == value: - yield i - else: - # Fast path for sequences - stop = len(iterable) if stop is None else stop - i = start - 1 - try: - while True: - yield (i := seq_index(value, i+1, stop)) - except ValueError: - pass - def sliding_window(iterable, n): "Collect data into overlapping fixed-length chunks or blocks." - # sliding_window('ABCDEFG', 4) --> ABCD BCDE CDEF DEFG + # sliding_window('ABCDEFG', 4) → ABCD BCDE CDEF DEFG it = iter(iterable) window = collections.deque(islice(it, n-1), maxlen=n) for x in it: @@ -928,156 +911,125 @@ which incur interpreter overhead. def grouper(iterable, n, *, incomplete='fill', fillvalue=None): "Collect data into non-overlapping fixed-length chunks or blocks." - # grouper('ABCDEFG', 3, fillvalue='x') --> ABC DEF Gxx - # grouper('ABCDEFG', 3, incomplete='strict') --> ABC DEF ValueError - # grouper('ABCDEFG', 3, incomplete='ignore') --> ABC DEF - args = [iter(iterable)] * n + # grouper('ABCDEFG', 3, fillvalue='x') → ABC DEF Gxx + # grouper('ABCDEFG', 3, incomplete='strict') → ABC DEF ValueError + # grouper('ABCDEFG', 3, incomplete='ignore') → ABC DEF + iterators = [iter(iterable)] * n match incomplete: case 'fill': - return zip_longest(*args, fillvalue=fillvalue) + return zip_longest(*iterators, fillvalue=fillvalue) case 'strict': - return zip(*args, strict=True) + return zip(*iterators, strict=True) case 'ignore': - return zip(*args) + return zip(*iterators) case _: raise ValueError('Expected fill, strict, or ignore') def roundrobin(*iterables): "Visit input iterables in a cycle until each is exhausted." - # roundrobin('ABC', 'D', 'EF') --> A D E B F C - # Recipe credited to George Sakkis - num_active = len(iterables) - nexts = cycle(iter(it).__next__ for it in iterables) - while num_active: - try: - for next in nexts: - yield next() - except StopIteration: - # Remove the iterator we just exhausted from the cycle. - num_active -= 1 - nexts = cycle(islice(nexts, num_active)) - - def partition(pred, iterable): + # roundrobin('ABC', 'D', 'EF') → A D E B F C + # Algorithm credited to George Sakkis + iterators = map(iter, iterables) + for num_active in range(len(iterables), 0, -1): + iterators = cycle(islice(iterators, num_active)) + yield from map(next, iterators) + + def partition(predicate, iterable): """Partition entries into false entries and true entries. - If *pred* is slow, consider wrapping it with functools.lru_cache(). + If *predicate* is slow, consider wrapping it with functools.lru_cache(). """ - # partition(is_odd, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9 + # partition(is_odd, range(10)) → 0 2 4 6 8 and 1 3 5 7 9 t1, t2 = tee(iterable) - return filterfalse(pred, t1), filter(pred, t2) + return filterfalse(predicate, t1), filter(predicate, t2) def subslices(seq): "Return all contiguous non-empty subslices of a sequence." - # subslices('ABCD') --> A AB ABC ABCD B BC BCD C CD D + # subslices('ABCD') → A AB ABC ABCD B BC BCD C CD D slices = starmap(slice, combinations(range(len(seq) + 1), 2)) return map(operator.getitem, repeat(seq), slices) + def iter_index(iterable, value, start=0, stop=None): + "Return indices where a value occurs in a sequence or iterable." + # iter_index('AABCADEAF', 'A') → 0 1 4 7 + seq_index = getattr(iterable, 'index', None) + if seq_index is None: + # Path for general iterables + it = islice(iterable, start, stop) + for i, element in enumerate(it, start): + if element is value or element == value: + yield i + else: + # Path for sequences with an index() method + stop = len(iterable) if stop is None else stop + i = start + try: + while True: + yield (i := seq_index(value, i, stop)) + i += 1 + except ValueError: + pass + def iter_except(func, exception, first=None): """ Call a function repeatedly until an exception is raised. Converts a call-until-exception interface to an iterator interface. - Like builtins.iter(func, sentinel) but uses an exception instead - of a sentinel to end the loop. - - Priority queue iterator: - iter_except(functools.partial(heappop, h), IndexError) - - Non-blocking dictionary iterator: - iter_except(d.popitem, KeyError) - - Non-blocking deque iterator: - iter_except(d.popleft, IndexError) - - Non-blocking iterator over a producer Queue: - iter_except(q.get_nowait, Queue.Empty) - - Non-blocking set iterator: - iter_except(s.pop, KeyError) - """ + # iter_except(d.popitem, KeyError) → non-blocking dictionary iterator try: if first is not None: - # For database APIs needing an initial call to db.first() yield first() while True: yield func() except exception: pass - def before_and_after(predicate, it): - """ Variant of takewhile() that allows complete - access to the remainder of the iterator. - - >>> it = iter('ABCdEfGhI') - >>> all_upper, remainder = before_and_after(str.isupper, it) - >>> ''.join(all_upper) - 'ABC' - >>> ''.join(remainder) # takewhile() would lose the 'd' - 'dEfGhI' - - Note that the true iterator must be fully consumed - before the remainder iterator can generate valid results. - """ - it = iter(it) - transition = [] - - def true_iterator(): - for elem in it: - if predicate(elem): - yield elem - else: - transition.append(elem) - return - - return true_iterator(), chain(transition, it) - The following recipes have a more mathematical flavor: .. testcode:: def powerset(iterable): - "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)" + "powerset([1,2,3]) → () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)" s = list(iterable) return chain.from_iterable(combinations(s, r) for r in range(len(s)+1)) - def sum_of_squares(it): + def sum_of_squares(iterable): "Add up the squares of the input values." - # sum_of_squares([10, 20, 30]) -> 1400 - return math.sumprod(*tee(it)) + # sum_of_squares([10, 20, 30]) → 1400 + return math.sumprod(*tee(iterable)) def reshape(matrix, cols): "Reshape a 2-D matrix to have a given number of columns." - # reshape([(0, 1), (2, 3), (4, 5)], 3) --> (0, 1, 2), (3, 4, 5) + # reshape([(0, 1), (2, 3), (4, 5)], 3) → (0, 1, 2), (3, 4, 5) return batched(chain.from_iterable(matrix), cols, strict=True) def transpose(matrix): "Swap the rows and columns of a 2-D matrix." - # transpose([(1, 2, 3), (11, 22, 33)]) --> (1, 11) (2, 22) (3, 33) + # transpose([(1, 2, 3), (11, 22, 33)]) → (1, 11) (2, 22) (3, 33) return zip(*matrix, strict=True) def matmul(m1, m2): "Multiply two matrices." - # matmul([(7, 5), (3, 5)], [(2, 5), (7, 9)]) --> (49, 80), (41, 60) + # matmul([(7, 5), (3, 5)], [(2, 5), (7, 9)]) → (49, 80), (41, 60) n = len(m2[0]) return batched(starmap(math.sumprod, product(m1, transpose(m2))), n) def convolve(signal, kernel): """Discrete linear convolution of two iterables. + Equivalent to polynomial multiplication. - The kernel is fully consumed before the calculations begin. - The signal is consumed lazily and can be infinite. - - Convolutions are mathematically commutative. - If the signal and kernel are swapped, - the output will be the same. + Convolutions are mathematically commutative; however, the inputs are + evaluated differently. The signal is consumed lazily and can be + infinite. The kernel is fully consumed before the calculations begin. Article: https://betterexplained.com/articles/intuitive-convolution/ Video: https://www.youtube.com/watch?v=KuXjwB4LzSA """ - # convolve(data, [0.25, 0.25, 0.25, 0.25]) --> Moving average (blur) - # convolve(data, [1/2, 0, -1/2]) --> 1st derivative estimate - # convolve(data, [1, -2, 1]) --> 2nd derivative estimate + # convolve([1, -1, -20], [1, -3]) → 1 -4 -17 60 + # convolve(data, [0.25, 0.25, 0.25, 0.25]) → Moving average (blur) + # convolve(data, [1/2, 0, -1/2]) → 1st derivative estimate + # convolve(data, [1, -2, 1]) → 2nd derivative estimate kernel = tuple(kernel)[::-1] n = len(kernel) padded_signal = chain(repeat(0, n-1), signal, repeat(0, n-1)) @@ -1089,7 +1041,7 @@ The following recipes have a more mathematical flavor: (x - 5) (x + 4) (x - 3) expands to: x³ -4x² -17x + 60 """ - # polynomial_from_roots([5, -4, 3]) --> [1, -4, -17, 60] + # polynomial_from_roots([5, -4, 3]) → [1, -4, -17, 60] factors = zip(repeat(1), map(operator.neg, roots)) return list(functools.reduce(convolve, factors, [1])) @@ -1098,8 +1050,8 @@ The following recipes have a more mathematical flavor: Computes with better numeric stability than Horner's method. """ - # Evaluate x³ -4x² -17x + 60 at x = 2.5 - # polynomial_eval([1, -4, -17, 60], x=2.5) --> 8.125 + # Evaluate x³ -4x² -17x + 60 at x = 5 + # polynomial_eval([1, -4, -17, 60], x=5) → 0 n = len(coefficients) if not n: return type(x)(0) @@ -1112,14 +1064,14 @@ The following recipes have a more mathematical flavor: f(x) = x³ -4x² -17x + 60 f'(x) = 3x² -8x -17 """ - # polynomial_derivative([1, -4, -17, 60]) -> [3, -8, -17] + # polynomial_derivative([1, -4, -17, 60]) → [3, -8, -17] n = len(coefficients) powers = reversed(range(1, n)) return list(map(operator.mul, coefficients, powers)) def sieve(n): "Primes less than n." - # sieve(30) --> 2 3 5 7 11 13 17 19 23 29 + # sieve(30) → 2 3 5 7 11 13 17 19 23 29 if n > 2: yield 2 start = 3 @@ -1133,9 +1085,9 @@ The following recipes have a more mathematical flavor: def factor(n): "Prime factors of n." - # factor(99) --> 3 3 11 - # factor(1_000_000_000_000_007) --> 47 59 360620266859 - # factor(1_000_000_000_000_403) --> 1000000000000403 + # factor(99) → 3 3 11 + # factor(1_000_000_000_000_007) → 47 59 360620266859 + # factor(1_000_000_000_000_403) → 1000000000000403 for prime in sieve(math.isqrt(n) + 1): while not n % prime: yield prime @@ -1148,7 +1100,7 @@ The following recipes have a more mathematical flavor: def totient(n): "Count of natural numbers up to n that are coprime to n." # https://mathworld.wolfram.com/TotientFunction.html - # totient(12) --> 4 because len([1, 5, 7, 11]) == 4 + # totient(12) → 4 because len([1, 5, 7, 11]) == 4 for p in unique_justseen(factor(n)): n -= n // p return n @@ -1214,6 +1166,12 @@ The following recipes have a more mathematical flavor: >>> take(10, count()) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> # Verify that the input is consumed lazily + >>> it = iter('abcdef') + >>> take(3, it) + ['a', 'b', 'c'] + >>> list(it) + ['d', 'e', 'f'] >>> list(prepend(1, [2, 3, 4])) [1, 2, 3, 4] @@ -1226,23 +1184,45 @@ The following recipes have a more mathematical flavor: >>> list(tail(3, 'ABCDEFG')) ['E', 'F', 'G'] + >>> # Verify the input is consumed greedily + >>> input_iterator = iter('ABCDEFG') + >>> output_iterator = tail(3, input_iterator) + >>> list(input_iterator) + [] >>> it = iter(range(10)) >>> consume(it, 3) + >>> # Verify the input is consumed lazily >>> next(it) 3 + >>> # Verify the input is consumed completely >>> consume(it) >>> next(it, 'Done') 'Done' >>> nth('abcde', 3) 'd' - >>> nth('abcde', 9) is None True + >>> # Verify that the input is consumed lazily + >>> it = iter('abcde') + >>> nth(it, 2) + 'c' + >>> list(it) + ['d', 'e'] >>> [all_equal(s) for s in ('', 'A', 'AAAA', 'AAAB', 'AAABA')] [True, True, True, False, False] + >>> [all_equal(s, key=str.casefold) for s in ('', 'A', 'AaAa', 'AAAB', 'AAABA')] + [True, True, True, False, False] + >>> # Verify that the input is consumed lazily and that only + >>> # one element of a second equivalence class is used to disprove + >>> # the assertion that all elements are equal. + >>> it = iter('aaabbbccc') + >>> all_equal(it) + False + >>> ''.join(it) + 'bbccc' >>> quantify(range(99), lambda x: x%2==0) 50 @@ -1250,7 +1230,7 @@ The following recipes have a more mathematical flavor: >>> quantify([True, False, False, True, True]) 3 - >>> quantify(range(12), pred=lambda x: x%2==1) + >>> quantify(range(12), predicate=lambda x: x%2==1) 6 >>> a = [[1, 2, 3], [4, 5, 6]] @@ -1265,6 +1245,11 @@ The following recipes have a more mathematical flavor: >>> list(ncycles('abc', 3)) ['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c'] + >>> # Verify greedy consumption of input iterator + >>> input_iterator = iter('abc') + >>> output_iterator = ncycles(input_iterator, 3) + >>> list(input_iterator) + [] >>> sum_of_squares([10, 20, 30]) 1400 @@ -1291,12 +1276,22 @@ The following recipes have a more mathematical flavor: >>> list(transpose([(1, 2, 3), (11, 22, 33)])) [(1, 11), (2, 22), (3, 33)] + >>> # Verify that the inputs are consumed lazily + >>> input1 = iter([1, 2, 3]) + >>> input2 = iter([11, 22, 33]) + >>> output_iterator = transpose([input1, input2]) + >>> next(output_iterator) + (1, 11) + >>> list(zip(input1, input2)) + [(2, 22), (3, 33)] >>> list(matmul([(7, 5), (3, 5)], [[2, 5], [7, 9]])) [(49, 80), (41, 60)] >>> list(matmul([[2, 5], [7, 9], [3, 4]], [[7, 11, 5, 4, 9], [3, 5, 2, 6, 3]])) [(29, 47, 20, 38, 33), (76, 122, 53, 82, 90), (33, 53, 23, 36, 39)] + >>> list(convolve([1, -1, -20], [1, -3])) == [1, -4, -17, 60] + True >>> data = [20, 40, 24, 32, 20, 28, 16] >>> list(convolve(data, [0.25, 0.25, 0.25, 0.25])) [5.0, 15.0, 21.0, 29.0, 29.0, 26.0, 24.0, 16.0, 11.0, 4.0] @@ -1304,13 +1299,25 @@ The following recipes have a more mathematical flavor: [20, 20, -16, 8, -12, 8, -12, -16] >>> list(convolve(data, [1, -2, 1])) [20, 0, -36, 24, -20, 20, -20, -4, 16] + >>> # Verify signal is consumed lazily and the kernel greedily + >>> signal_iterator = iter([10, 20, 30, 40, 50]) + >>> kernel_iterator = iter([1, 2, 3]) + >>> output_iterator = convolve(signal_iterator, kernel_iterator) + >>> list(kernel_iterator) + [] + >>> next(output_iterator) + 10 + >>> next(output_iterator) + 40 + >>> list(signal_iterator) + [30, 40, 50] >>> from fractions import Fraction >>> from decimal import Decimal - >>> polynomial_eval([1, -4, -17, 60], x=2) - 18 - >>> x = 2; x**3 - 4*x**2 -17*x + 60 - 18 + >>> polynomial_eval([1, -4, -17, 60], x=5) + 0 + >>> x = 5; x**3 - 4*x**2 -17*x + 60 + 0 >>> polynomial_eval([1, -4, -17, 60], x=2.5) 8.125 >>> x = 2.5; x**3 - 4*x**2 -17*x + 60 @@ -1391,6 +1398,33 @@ The following recipes have a more mathematical flavor: >>> # Test list input. Lists do not support None for the stop argument >>> list(iter_index(list('AABCADEAF'), 'A')) [0, 1, 4, 7] + >>> # Verify that input is consumed lazily + >>> input_iterator = iter('AABCADEAF') + >>> output_iterator = iter_index(input_iterator, 'A') + >>> next(output_iterator) + 0 + >>> next(output_iterator) + 1 + >>> next(output_iterator) + 4 + >>> ''.join(input_iterator) + 'DEAF' + + >>> # Verify that the target value can be a sequence. + >>> seq = [[10, 20], [30, 40], 30, 40, [30, 40], 50] + >>> target = [30, 40] + >>> list(iter_index(seq, target)) + [1, 4] + + >>> # Verify faithfulness to type specific index() method behaviors. + >>> # For example, bytes and str perform continuous-subsequence searches + >>> # that do not match the general behavior specified + >>> # in collections.abc.Sequence.index(). + >>> seq = 'abracadabra' + >>> target = 'ab' + >>> list(iter_index(seq, target)) + [0, 7] + >>> list(sieve(30)) [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] @@ -1533,6 +1567,16 @@ The following recipes have a more mathematical flavor: >>> list(roundrobin('abc', 'd', 'ef')) ['a', 'd', 'e', 'b', 'f', 'c'] + >>> ranges = [range(5, 1000), range(4, 3000), range(0), range(3, 2000), range(2, 5000), range(1, 3500)] + >>> collections.Counter(roundrobin(*ranges)) == collections.Counter(chain(*ranges)) + True + >>> # Verify that the inputs are consumed lazily + >>> input_iterators = list(map(iter, ['abcd', 'ef', '', 'ghijk', 'l', 'mnopqr'])) + >>> output_iterator = roundrobin(*input_iterators) + >>> ''.join(islice(output_iterator, 10)) + 'aeglmbfhnc' + >>> ''.join(chain(*input_iterators)) + 'dijkopqr' >>> def is_odd(x): ... return x % 2 == 1 @@ -1542,13 +1586,17 @@ The following recipes have a more mathematical flavor: [0, 2, 4, 6, 8] >>> list(odds) [1, 3, 5, 7, 9] - - >>> it = iter('ABCdEfGhI') - >>> all_upper, remainder = before_and_after(str.isupper, it) - >>> ''.join(all_upper) - 'ABC' - >>> ''.join(remainder) - 'dEfGhI' + >>> # Verify that the input is consumed lazily + >>> input_iterator = iter(range(10)) + >>> evens, odds = partition(is_odd, input_iterator) + >>> next(odds) + 1 + >>> next(odds) + 3 + >>> next(evens) + 0 + >>> list(input_iterator) + [4, 5, 6, 7, 8, 9] >>> list(subslices('ABCD')) ['A', 'AB', 'ABC', 'ABCD', 'B', 'BC', 'BCD', 'C', 'CD', 'D'] @@ -1568,6 +1616,13 @@ The following recipes have a more mathematical flavor: ['A', 'B', 'C', 'D'] >>> list(unique_everseen('ABBcCAD', str.casefold)) ['A', 'B', 'c', 'D'] + >>> # Verify that the input is consumed lazily + >>> input_iterator = iter('AAAABBBCCDAABBB') + >>> output_iterator = unique_everseen(input_iterator) + >>> next(output_iterator) + 'A' + >>> ''.join(input_iterator) + 'AAABBBCCDAABBB' >>> list(unique_justseen('AAAABBBCCDAABBB')) ['A', 'B', 'C', 'D', 'A', 'B'] @@ -1575,6 +1630,13 @@ The following recipes have a more mathematical flavor: ['A', 'B', 'C', 'A', 'D'] >>> list(unique_justseen('ABBcCAD', str.casefold)) ['A', 'B', 'c', 'A', 'D'] + >>> # Verify that the input is consumed lazily + >>> input_iterator = iter('AAAABBBCCDAABBB') + >>> output_iterator = unique_justseen(input_iterator) + >>> next(output_iterator) + 'A' + >>> ''.join(input_iterator) + 'AAABBBCCDAABBB' >>> d = dict(a=1, b=2, c=3) >>> it = iter_except(d.popitem, KeyError) @@ -1595,6 +1657,12 @@ The following recipes have a more mathematical flavor: >>> first_true('ABC0DEF1', '9', str.isdigit) '0' + >>> # Verify that inputs are consumed lazily + >>> it = iter('ABC0DEF1') + >>> first_true(it, predicate=str.isdigit) + '0' + >>> ''.join(it) + 'DEF1' .. testcode:: @@ -1618,7 +1686,7 @@ The following recipes have a more mathematical flavor: def triplewise(iterable): "Return overlapping triplets from an iterable" - # triplewise('ABCDEFG') --> ABC BCD CDE DEF EFG + # triplewise('ABCDEFG') → ABC BCD CDE DEF EFG for (a, _), (b, c) in pairwise(pairwise(iterable)): yield a, b, c @@ -1640,6 +1708,32 @@ The following recipes have a more mathematical flavor: result.append(pool[-1-n]) return tuple(result) + def before_and_after(predicate, it): + """ Variant of takewhile() that allows complete + access to the remainder of the iterator. + + >>> it = iter('ABCdEfGhI') + >>> all_upper, remainder = before_and_after(str.isupper, it) + >>> ''.join(all_upper) + 'ABC' + >>> ''.join(remainder) # takewhile() would lose the 'd' + 'dEfGhI' + + Note that the true iterator must be fully consumed + before the remainder iterator can generate valid results. + """ + it = iter(it) + transition = [] + + def true_iterator(): + for elem in it: + if predicate(elem): + yield elem + else: + transition.append(elem) + return + + return true_iterator(), chain(transition, it) .. doctest:: :hide: @@ -1669,3 +1763,10 @@ The following recipes have a more mathematical flavor: >>> combos = list(combinations(iterable, r)) >>> all(nth_combination(iterable, r, i) == comb for i, comb in enumerate(combos)) True + + >>> it = iter('ABCdEfGhI') + >>> all_upper, remainder = before_and_after(str.isupper, it) + >>> ''.join(all_upper) + 'ABC' + >>> ''.join(remainder) + 'dEfGhI' diff --git a/Doc/library/json.rst b/Doc/library/json.rst index 0ce4b697145cb3e..c82ff9dc325b4c8 100644 --- a/Doc/library/json.rst +++ b/Doc/library/json.rst @@ -106,7 +106,7 @@ Extending :class:`JSONEncoder`:: ... if isinstance(obj, complex): ... return [obj.real, obj.imag] ... # Let the base class default method raise the TypeError - ... return json.JSONEncoder.default(self, obj) + ... return super().default(obj) ... >>> json.dumps(2 + 1j, cls=ComplexEncoder) '[2.0, 1.0]' @@ -504,7 +504,7 @@ Encoders and Decoders else: return list(iterable) # Let the base class default method raise the TypeError - return json.JSONEncoder.default(self, o) + return super().default(o) .. method:: encode(o) diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index 39eb41ce1f16708..4e7d18bda8bf7dc 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -30,13 +30,53 @@ is that all Python modules can participate in logging, so your application log can include your own messages integrated with messages from third-party modules. -The simplest example: +Here's a simple example of idiomatic usage: :: + + # myapp.py + import logging + import mylib + logger = logging.getLogger(__name__) + + def main(): + logging.basicConfig(filename='myapp.log', level=logging.INFO) + logger.info('Started') + mylib.do_something() + logger.info('Finished') + + if __name__ == '__main__': + main() + +:: + + # mylib.py + import logging + logger = logging.getLogger(__name__) + + def do_something(): + logger.info('Doing something') + +If you run *myapp.py*, you should see this in *myapp.log*: .. code-block:: none - >>> import logging - >>> logging.warning('Watch out!') - WARNING:root:Watch out! + INFO:__main__:Started + INFO:mylib:Doing something + INFO:__main__:Finished + +The key features of this idiomatic usage is that the majority of code is simply +creating a module level logger with ``getLogger(__name__)``, and using that +logger to do any needed logging. This is concise while allowing downstream code +fine grained control if needed. Logged messages to the module-level logger get +forwarded up to handlers of loggers in higher-level modules, all the way up to +the root logger; for this reason this approach is known as hierarchical logging. + +For logging to be useful, it needs to be configured: setting the levels and +destinations for each logger, potentially changing how specific modules log, +often based on command-line arguments or application configuration. In most +cases, like the one above, only the root logger needs to be so configured, since +all the lower level loggers at module level eventually forward their messages to +its handlers. :func:`~logging.basicConfig` provides a quick way to configure +the root logger that handles many use cases. The module provides a lot of functionality and flexibility. If you are unfamiliar with logging, the best way to get to grips with it is to view the @@ -77,6 +117,27 @@ is the module's name in the Python package namespace. .. class:: Logger + .. attribute:: Logger.name + + This is the logger's name, and is the value that was passed to :func:`getLogger` + to obtain the logger. + + .. note:: This attribute should be treated as read-only. + + .. attribute:: Logger.level + + The threshold of this logger, as set by the :meth:`setLevel` method. + + .. note:: Do not set this attribute directly - always use :meth:`setLevel`, + which has checks for the level passed to it. + + .. attribute:: Logger.parent + + The parent logger of this logger. It may change based on later instantiation + of loggers which are higher up in the namespace hierarchy. + + .. note:: This value should be treated as read-only. + .. attribute:: Logger.propagate If this attribute evaluates to true, events logged to this logger will be @@ -108,6 +169,21 @@ is the module's name in the Python package namespace. scenario is to attach handlers only to the root logger, and to let propagation take care of the rest. + .. attribute:: Logger.handlers + + The list of handlers directly attached to this logger instance. + + .. note:: This attribute should be treated as read-only; it is normally changed via + the :meth:`addHandler` and :meth:`removeHandler` methods, which use locks to ensure + thread-safe operation. + + .. attribute:: Logger.disabled + + This attribute disables handling of any events. It is set to ``False`` in the + initializer, and only changed by logging configuration code. + + .. note:: This attribute should be treated as read-only. + .. method:: Logger.setLevel(level) Sets the threshold for this logger to *level*. Logging messages which are less @@ -1115,89 +1191,31 @@ functions. .. function:: debug(msg, *args, **kwargs) - Logs a message with level :const:`DEBUG` on the root logger. The *msg* is the - message format string, and the *args* are the arguments which are merged into - *msg* using the string formatting operator. (Note that this means that you can - use keywords in the format string, together with a single dictionary argument.) - - There are three keyword arguments in *kwargs* which are inspected: *exc_info* - which, if it does not evaluate as false, causes exception information to be - added to the logging message. If an exception tuple (in the format returned by - :func:`sys.exc_info`) or an exception instance is provided, it is used; - otherwise, :func:`sys.exc_info` is called to get the exception information. - - The second optional keyword argument is *stack_info*, which defaults to - ``False``. If true, stack information is added to the logging - message, including the actual logging call. Note that this is not the same - stack information as that displayed through specifying *exc_info*: The - former is stack frames from the bottom of the stack up to the logging call - in the current thread, whereas the latter is information about stack frames - which have been unwound, following an exception, while searching for - exception handlers. - - You can specify *stack_info* independently of *exc_info*, e.g. to just show - how you got to a certain point in your code, even when no exceptions were - raised. The stack frames are printed following a header line which says: - - .. code-block:: none - - Stack (most recent call last): + This is a convenience function that calls :meth:`Logger.debug`, on the root + logger. The handling of the arguments is in every way identical + to what is described in that method. - This mimics the ``Traceback (most recent call last):`` which is used when - displaying exception frames. + The only difference is that if the root logger has no handlers, then + :func:`basicConfig` is called, prior to calling ``debug`` on the root logger. - The third optional keyword argument is *extra* which can be used to pass a - dictionary which is used to populate the __dict__ of the LogRecord created for - the logging event with user-defined attributes. These custom attributes can then - be used as you like. For example, they could be incorporated into logged - messages. For example:: + For very short scripts or quick demonstrations of ``logging`` facilities, + ``debug`` and the other module-level functions may be convenient. However, + most programs will want to carefully and explicitly control the logging + configuration, and should therefore prefer creating a module-level logger and + calling :meth:`Logger.debug` (or other level-specific methods) on it, as + described at the beginnning of this documentation. - FORMAT = '%(asctime)s %(clientip)-15s %(user)-8s %(message)s' - logging.basicConfig(format=FORMAT) - d = {'clientip': '192.168.0.1', 'user': 'fbloggs'} - logging.warning('Protocol problem: %s', 'connection reset', extra=d) - - would print something like: - - .. code-block:: none - - 2006-02-08 22:20:02,165 192.168.0.1 fbloggs Protocol problem: connection reset - - The keys in the dictionary passed in *extra* should not clash with the keys used - by the logging system. (See the :class:`Formatter` documentation for more - information on which keys are used by the logging system.) - - If you choose to use these attributes in logged messages, you need to exercise - some care. In the above example, for instance, the :class:`Formatter` has been - set up with a format string which expects 'clientip' and 'user' in the attribute - dictionary of the LogRecord. If these are missing, the message will not be - logged because a string formatting exception will occur. So in this case, you - always need to pass the *extra* dictionary with these keys. - - While this might be annoying, this feature is intended for use in specialized - circumstances, such as multi-threaded servers where the same code executes in - many contexts, and interesting conditions which arise are dependent on this - context (such as remote client IP address and authenticated user name, in the - above example). In such circumstances, it is likely that specialized - :class:`Formatter`\ s would be used with particular :class:`Handler`\ s. - - This function (as well as :func:`info`, :func:`warning`, :func:`error` and - :func:`critical`) will call :func:`basicConfig` if the root logger doesn't - have any handler attached. - - .. versionchanged:: 3.2 - The *stack_info* parameter was added. .. function:: info(msg, *args, **kwargs) - Logs a message with level :const:`INFO` on the root logger. The arguments are - interpreted as for :func:`debug`. + Logs a message with level :const:`INFO` on the root logger. The arguments and behavior + are otherwise the same as for :func:`debug`. .. function:: warning(msg, *args, **kwargs) - Logs a message with level :const:`WARNING` on the root logger. The arguments - are interpreted as for :func:`debug`. + Logs a message with level :const:`WARNING` on the root logger. The arguments and behavior + are otherwise the same as for :func:`debug`. .. note:: There is an obsolete function ``warn`` which is functionally identical to ``warning``. As ``warn`` is deprecated, please do not use @@ -1210,26 +1228,26 @@ functions. .. function:: error(msg, *args, **kwargs) - Logs a message with level :const:`ERROR` on the root logger. The arguments are - interpreted as for :func:`debug`. + Logs a message with level :const:`ERROR` on the root logger. The arguments and behavior + are otherwise the same as for :func:`debug`. .. function:: critical(msg, *args, **kwargs) - Logs a message with level :const:`CRITICAL` on the root logger. The arguments - are interpreted as for :func:`debug`. + Logs a message with level :const:`CRITICAL` on the root logger. The arguments and behavior + are otherwise the same as for :func:`debug`. .. function:: exception(msg, *args, **kwargs) - Logs a message with level :const:`ERROR` on the root logger. The arguments are - interpreted as for :func:`debug`. Exception info is added to the logging + Logs a message with level :const:`ERROR` on the root logger. The arguments and behavior + are otherwise the same as for :func:`debug`. Exception info is added to the logging message. This function should only be called from an exception handler. .. function:: log(level, msg, *args, **kwargs) - Logs a message with level *level* on the root logger. The other arguments are - interpreted as for :func:`debug`. + Logs a message with level *level* on the root logger. The arguments and behavior + are otherwise the same as for :func:`debug`. .. function:: disable(level=CRITICAL) diff --git a/Doc/library/math.rst b/Doc/library/math.rst index 3c850317f608582..1475d26486de5fb 100644 --- a/Doc/library/math.rst +++ b/Doc/library/math.rst @@ -82,6 +82,22 @@ Number-theoretic and representation functions should return an :class:`~numbers.Integral` value. +.. function:: fma(x, y, z) + + Fused multiply-add operation. Return ``(x * y) + z``, computed as though with + infinite precision and range followed by a single round to the ``float`` + format. This operation often provides better accuracy than the direct + expression ``(x * y) + z``. + + This function follows the specification of the fusedMultiplyAdd operation + described in the IEEE 754 standard. The standard leaves one case + implementation-defined, namely the result of ``fma(0, inf, nan)`` + and ``fma(inf, 0, nan)``. In these cases, ``math.fma`` returns a NaN, + and does not raise any exception. + + .. versionadded:: 3.13 + + .. function:: fmod(x, y) Return ``fmod(x, y)``, as defined by the platform C library. Note that the @@ -592,7 +608,7 @@ Special functions The :func:`erf` function can be used to compute traditional statistical functions such as the `cumulative standard normal distribution - `_:: + `_:: def phi(x): 'Cumulative distribution function for the standard normal distribution' diff --git a/Doc/library/numbers.rst b/Doc/library/numbers.rst index 17d1a275f04c9bb..306bdd94aaca130 100644 --- a/Doc/library/numbers.rst +++ b/Doc/library/numbers.rst @@ -166,7 +166,7 @@ Complex``. I'll consider ``a + b``: 2. If ``A`` falls back to the boilerplate code, and it were to return a value from :meth:`~object.__add__`, we'd miss the possibility that ``B`` defines a more intelligent :meth:`~object.__radd__`, so the - boilerplate should return :const:`NotImplemented` from + boilerplate should return :data:`NotImplemented` from :meth:`!__add__`. (Or ``A`` may not implement :meth:`!__add__` at all.) 3. Then ``B``'s :meth:`~object.__radd__` gets a chance. If it accepts diff --git a/Doc/library/os.rst b/Doc/library/os.rst index cc9f3e75a80c517..09d8228f986e477 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -2614,7 +2614,6 @@ features: .. versionchanged:: 3.8 Accepts a :term:`path-like object` and a bytes object on Windows. - .. versionchanged:: 3.8 Added support for directory junctions, and changed to return the substitution path (which typically includes ``\\?\`` prefix) rather than the optional "print name" field that was previously returned. @@ -3115,21 +3114,21 @@ features: Time of most recent access expressed in nanoseconds as an integer. - .. versionadded: 3.3 + .. versionadded:: 3.3 .. attribute:: st_mtime_ns Time of most recent content modification expressed in nanoseconds as an integer. - .. versionadded: 3.3 + .. versionadded:: 3.3 .. attribute:: st_ctime_ns Time of most recent metadata change expressed in nanoseconds as an integer. - .. versionadded: 3.3 + .. versionadded:: 3.3 .. versionchanged:: 3.12 ``st_ctime_ns`` is deprecated on Windows. Use ``st_birthtime_ns`` @@ -3257,10 +3256,10 @@ features: Windows now returns the file index as :attr:`st_ino` when available. - .. versionadded:: 3.7 + .. versionchanged:: 3.7 Added the :attr:`st_fstype` member to Solaris/derivatives. - .. versionadded:: 3.8 + .. versionchanged:: 3.8 Added the :attr:`st_reparse_tag` member on Windows. .. versionchanged:: 3.8 @@ -3274,16 +3273,13 @@ features: platforms, but for now still contains creation time. Use :attr:`st_birthtime` for the creation time. - .. versionchanged:: 3.12 On Windows, :attr:`st_ino` may now be up to 128 bits, depending on the file system. Previously it would not be above 64 bits, and larger file identifiers would be arbitrarily packed. - .. versionchanged:: 3.12 On Windows, :attr:`st_rdev` no longer returns a value. Previously it would contain the same as :attr:`st_dev`, which was incorrect. - .. versionadded:: 3.12 Added the :attr:`st_birthtime` member on Windows. @@ -4496,15 +4492,15 @@ written in Python, such as a mail server's external command delivery program. On macOS the use of this function is unsafe when mixed with using higher-level system APIs, and that includes using :mod:`urllib.request`. + .. versionchanged:: 3.8 + Calling ``forkpty()`` in a subinterpreter is no longer supported + (:exc:`RuntimeError` is raised). + .. versionchanged:: 3.12 If Python is able to detect that your process has multiple threads, this now raises a :exc:`DeprecationWarning`. See the longer explanation on :func:`os.fork`. - .. versionchanged:: 3.8 - Calling ``forkpty()`` in a subinterpreter is no longer supported - (:exc:`RuntimeError` is raised). - .. availability:: Unix, not Emscripten, not WASI. @@ -4719,8 +4715,6 @@ written in Python, such as a mail server's external command delivery program. .. versionchanged:: 3.13 *env* parameter accepts ``None``. - - .. versionchanged:: 3.13 ``os.POSIX_SPAWN_CLOSEFROM`` is available on platforms where :c:func:`!posix_spawn_file_actions_addclosefrom_np` exists. @@ -5708,20 +5702,20 @@ Random numbers easy-to-use interface to the random number generator provided by your platform, please see :class:`random.SystemRandom`. - .. versionchanged:: 3.6.0 - On Linux, ``getrandom()`` is now used in blocking mode to increase the - security. - - .. versionchanged:: 3.5.2 - On Linux, if the ``getrandom()`` syscall blocks (the urandom entropy pool - is not initialized yet), fall back on reading ``/dev/urandom``. - .. versionchanged:: 3.5 On Linux 3.17 and newer, the ``getrandom()`` syscall is now used when available. On OpenBSD 5.6 and newer, the C ``getentropy()`` function is now used. These functions avoid the usage of an internal file descriptor. + .. versionchanged:: 3.5.2 + On Linux, if the ``getrandom()`` syscall blocks (the urandom entropy pool + is not initialized yet), fall back on reading ``/dev/urandom``. + + .. versionchanged:: 3.6 + On Linux, ``getrandom()`` is now used in blocking mode to increase the + security. + .. versionchanged:: 3.11 On Windows, ``BCryptGenRandom()`` is used instead of ``CryptGenRandom()`` which is deprecated. diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index f94b6fb38056847..3ff2631d73c0b2c 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -7,7 +7,7 @@ .. versionadded:: 3.4 -**Source code:** :source:`Lib/pathlib.py` +**Source code:** :source:`Lib/pathlib/` .. index:: single: path; operations @@ -572,6 +572,9 @@ Pure paths provide the following methods and properties: >>> PurePath('/a/b/c.py').full_match('**/*.py') True + .. seealso:: + :ref:`pathlib-pattern-language` documentation. + As with other methods, case-sensitivity follows platform defaults:: >>> PurePosixPath('b.py').full_match('*.PY') @@ -991,11 +994,6 @@ call fails (for example because the path doesn't exist). [PosixPath('pathlib.py'), PosixPath('setup.py'), PosixPath('test_pathlib.py')] >>> sorted(Path('.').glob('*/*.py')) [PosixPath('docs/conf.py')] - - Patterns are the same as for :mod:`fnmatch`, with the addition of "``**``" - which means "this directory and all subdirectories, recursively". In other - words, it enables recursive globbing:: - >>> sorted(Path('.').glob('**/*.py')) [PosixPath('build/lib/pathlib.py'), PosixPath('docs/conf.py'), @@ -1003,13 +1001,8 @@ call fails (for example because the path doesn't exist). PosixPath('setup.py'), PosixPath('test_pathlib.py')] - .. note:: - Using the "``**``" pattern in large directory trees may consume - an inordinate amount of time. - - .. tip:: - Set *follow_symlinks* to ``True`` or ``False`` to improve performance - of recursive globbing. + .. seealso:: + :ref:`pathlib-pattern-language` documentation. This method calls :meth:`Path.is_dir` on the top-level directory and propagates any :exc:`OSError` exception that is raised. Subsequent @@ -1025,11 +1018,11 @@ call fails (for example because the path doesn't exist). wildcards. Set *follow_symlinks* to ``True`` to always follow symlinks, or ``False`` to treat all symlinks as files. - .. audit-event:: pathlib.Path.glob self,pattern pathlib.Path.glob + .. tip:: + Set *follow_symlinks* to ``True`` or ``False`` to improve performance + of recursive globbing. - .. versionchanged:: 3.11 - Return only directories if *pattern* ends with a pathname components - separator (:data:`~os.sep` or :data:`~os.altsep`). + .. audit-event:: pathlib.Path.glob self,pattern pathlib.Path.glob .. versionchanged:: 3.12 The *case_sensitive* parameter was added. @@ -1038,12 +1031,29 @@ call fails (for example because the path doesn't exist). The *follow_symlinks* parameter was added. .. versionchanged:: 3.13 - Return files and directories if *pattern* ends with "``**``". In - previous versions, only directories were returned. + The *pattern* parameter accepts a :term:`path-like object`. + + +.. method:: Path.rglob(pattern, *, case_sensitive=None, follow_symlinks=None) + + Glob the given relative *pattern* recursively. This is like calling + :func:`Path.glob` with "``**/``" added in front of the *pattern*. + + .. seealso:: + :ref:`pathlib-pattern-language` and :meth:`Path.glob` documentation. + + .. audit-event:: pathlib.Path.rglob self,pattern pathlib.Path.rglob + + .. versionchanged:: 3.12 + The *case_sensitive* parameter was added. + + .. versionchanged:: 3.13 + The *follow_symlinks* parameter was added. .. versionchanged:: 3.13 The *pattern* parameter accepts a :term:`path-like object`. + .. method:: Path.group(*, follow_symlinks=True) Return the name of the group owning the file. :exc:`KeyError` is raised @@ -1471,44 +1481,6 @@ call fails (for example because the path doesn't exist). strict mode, and no exception is raised in non-strict mode. In previous versions, :exc:`RuntimeError` is raised no matter the value of *strict*. -.. method:: Path.rglob(pattern, *, case_sensitive=None, follow_symlinks=None) - - Glob the given relative *pattern* recursively. This is like calling - :func:`Path.glob` with "``**/``" added in front of the *pattern*, where - *patterns* are the same as for :mod:`fnmatch`:: - - >>> sorted(Path().rglob("*.py")) - [PosixPath('build/lib/pathlib.py'), - PosixPath('docs/conf.py'), - PosixPath('pathlib.py'), - PosixPath('setup.py'), - PosixPath('test_pathlib.py')] - - By default, or when the *case_sensitive* keyword-only argument is set to - ``None``, this method matches paths using platform-specific casing rules: - typically, case-sensitive on POSIX, and case-insensitive on Windows. - Set *case_sensitive* to ``True`` or ``False`` to override this behaviour. - - By default, or when the *follow_symlinks* keyword-only argument is set to - ``None``, this method follows symlinks except when expanding "``**``" - wildcards. Set *follow_symlinks* to ``True`` to always follow symlinks, or - ``False`` to treat all symlinks as files. - - .. audit-event:: pathlib.Path.rglob self,pattern pathlib.Path.rglob - - .. versionchanged:: 3.11 - Return only directories if *pattern* ends with a pathname components - separator (:data:`~os.sep` or :data:`~os.altsep`). - - .. versionchanged:: 3.12 - The *case_sensitive* parameter was added. - - .. versionchanged:: 3.13 - The *follow_symlinks* parameter was added. - - .. versionchanged:: 3.13 - The *pattern* parameter accepts a :term:`path-like object`. - .. method:: Path.rmdir() Remove this directory. The directory must be empty. @@ -1639,23 +1611,128 @@ call fails (for example because the path doesn't exist). .. versionchanged:: 3.10 The *newline* parameter was added. -Correspondence to tools in the :mod:`os` module ------------------------------------------------ -Below is a table mapping various :mod:`os` functions to their corresponding -:class:`PurePath`/:class:`Path` equivalent. +.. _pathlib-pattern-language: + +Pattern language +---------------- + +The following wildcards are supported in patterns for +:meth:`~PurePath.full_match`, :meth:`~Path.glob` and :meth:`~Path.rglob`: + +``**`` (entire segment) + Matches any number of file or directory segments, including zero. +``*`` (entire segment) + Matches one file or directory segment. +``*`` (part of a segment) + Matches any number of non-separator characters, including zero. +``?`` + Matches one non-separator character. +``[seq]`` + Matches one character in *seq*. +``[!seq]`` + Matches one character not in *seq*. + +For a literal match, wrap the meta-characters in brackets. +For example, ``"[?]"`` matches the character ``"?"``. + +The "``**``" wildcard enables recursive globbing. A few examples: + +========================= =========================================== +Pattern Meaning +========================= =========================================== +"``**/*``" Any path with at least one segment. +"``**/*.py``" Any path with a final segment ending "``.py``". +"``assets/**``" Any path starting with "``assets/``". +"``assets/**/*``" Any path starting with "``assets/``", excluding "``assets/``" itself. +========================= =========================================== .. note:: + Globbing with the "``**``" wildcard visits every directory in the tree. + Large directory trees may take a long time to search. - Not all pairs of functions/methods below are equivalent. Some of them, - despite having some overlapping use-cases, have different semantics. They - include :func:`os.path.abspath` and :meth:`Path.absolute`, - :func:`os.path.relpath` and :meth:`PurePath.relative_to`. +.. versionchanged:: 3.13 + Globbing with a pattern that ends with "``**``" returns both files and + directories. In previous versions, only directories were returned. + +In :meth:`Path.glob` and :meth:`~Path.rglob`, a trailing slash may be added to +the pattern to match only directories. + +.. versionchanged:: 3.11 + Globbing with a pattern that ends with a pathname components separator + (:data:`~os.sep` or :data:`~os.altsep`) returns only directories. + + +Comparison to the :mod:`glob` module +------------------------------------ + +The patterns accepted and results generated by :meth:`Path.glob` and +:meth:`Path.rglob` differ slightly from those by the :mod:`glob` module: + +1. Files beginning with a dot are not special in pathlib. This is + like passing ``include_hidden=True`` to :func:`glob.glob`. +2. "``**``" pattern components are always recursive in pathlib. This is like + passing ``recursive=True`` to :func:`glob.glob`. +3. "``**``" pattern components do not follow symlinks by default in pathlib. + This behaviour has no equivalent in :func:`glob.glob`, but you can pass + ``follow_symlinks=True`` to :meth:`Path.glob` for compatible behaviour. +4. Like all :class:`PurePath` and :class:`Path` objects, the values returned + from :meth:`Path.glob` and :meth:`Path.rglob` don't include trailing + slashes. +5. The values returned from pathlib's ``path.glob()`` and ``path.rglob()`` + include the *path* as a prefix, unlike the results of + ``glob.glob(root_dir=path)``. + + +Comparison to the :mod:`os` and :mod:`os.path` modules +------------------------------------------------------ + +pathlib implements path operations using :class:`PurePath` and :class:`Path` +objects, and so it's said to be *object-oriented*. On the other hand, the +:mod:`os` and :mod:`os.path` modules supply functions that work with low-level +``str`` and ``bytes`` objects, which is a more *procedural* approach. Some +users consider the object-oriented style to be more readable. + +Many functions in :mod:`os` and :mod:`os.path` support ``bytes`` paths and +:ref:`paths relative to directory descriptors `. These features aren't +available in pathlib. + +Python's ``str`` and ``bytes`` types, and portions of the :mod:`os` and +:mod:`os.path` modules, are written in C and are very speedy. pathlib is +written in pure Python and is often slower, but rarely slow enough to matter. + +pathlib's path normalization is slightly more opinionated and consistent than +:mod:`os.path`. For example, whereas :func:`os.path.abspath` eliminates +"``..``" segments from a path, which may change its meaning if symlinks are +involved, :meth:`Path.absolute` preserves these segments for greater safety. + +pathlib's path normalization may render it unsuitable for some applications: + +1. pathlib normalizes ``Path("my_folder/")`` to ``Path("my_folder")``, which + changes a path's meaning when supplied to various operating system APIs and + command-line utilities. Specifically, the absence of a trailing separator + may allow the path to be resolved as either a file or directory, rather + than a directory only. +2. pathlib normalizes ``Path("./my_program")`` to ``Path("my_program")``, + which changes a path's meaning when used as an executable search path, such + as in a shell or when spawning a child process. Specifically, the absence + of a separator in the path may force it to be looked up in :envvar:`PATH` + rather than the current directory. + +As a consequence of these differences, pathlib is not a drop-in replacement +for :mod:`os.path`. + + +Corresponding tools +^^^^^^^^^^^^^^^^^^^ + +Below is a table mapping various :mod:`os` functions to their corresponding +:class:`PurePath`/:class:`Path` equivalent. ==================================== ============================== :mod:`os` and :mod:`os.path` :mod:`pathlib` ==================================== ============================== -:func:`os.path.abspath` :meth:`Path.absolute` [#]_ +:func:`os.path.abspath` :meth:`Path.absolute` :func:`os.path.realpath` :meth:`Path.resolve` :func:`os.chmod` :meth:`Path.chmod` :func:`os.mkdir` :meth:`Path.mkdir` @@ -1676,7 +1753,7 @@ Below is a table mapping various :mod:`os` functions to their corresponding :func:`os.link` :meth:`Path.hardlink_to` :func:`os.symlink` :meth:`Path.symlink_to` :func:`os.readlink` :meth:`Path.readlink` -:func:`os.path.relpath` :meth:`PurePath.relative_to` [#]_ +:func:`os.path.relpath` :meth:`PurePath.relative_to` :func:`os.stat` :meth:`Path.stat`, :meth:`Path.owner`, :meth:`Path.group` @@ -1688,8 +1765,3 @@ Below is a table mapping various :mod:`os` functions to their corresponding :func:`os.path.splitext` :attr:`PurePath.stem` and :attr:`PurePath.suffix` ==================================== ============================== - -.. rubric:: Footnotes - -.. [#] :func:`os.path.abspath` normalizes the resulting path, which may change its meaning in the presence of symlinks, while :meth:`Path.absolute` does not. -.. [#] :meth:`PurePath.relative_to` requires ``self`` to be the subpath of the argument, but :func:`os.path.relpath` does not. diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index cb17acfb367619c..ac3007f70c3534d 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -288,19 +288,20 @@ There are three preset *convenience variables*: If a file :file:`.pdbrc` exists in the user's home directory or in the current directory, it is read with ``'utf-8'`` encoding and executed as if it had been -typed at the debugger prompt. This is particularly useful for aliases. If both +typed at the debugger prompt, with the exception that empty lines and lines +starting with ``#`` are ignored. This is particularly useful for aliases. If both files exist, the one in the home directory is read first and aliases defined there can be overridden by the local file. -.. versionchanged:: 3.11 - :file:`.pdbrc` is now read with ``'utf-8'`` encoding. Previously, it was read - with the system locale encoding. - .. versionchanged:: 3.2 :file:`.pdbrc` can now contain commands that continue debugging, such as :pdbcmd:`continue` or :pdbcmd:`next`. Previously, these commands had no effect. +.. versionchanged:: 3.11 + :file:`.pdbrc` is now read with ``'utf-8'`` encoding. Previously, it was read + with the system locale encoding. + .. pdbcommand:: h(elp) [command] diff --git a/Doc/library/pickle.rst b/Doc/library/pickle.rst index cb517681fa81b96..223c27237e4d34c 100644 --- a/Doc/library/pickle.rst +++ b/Doc/library/pickle.rst @@ -377,7 +377,7 @@ The :mod:`pickle` module exports three classes, :class:`Pickler`, Special reducer that can be defined in :class:`Pickler` subclasses. This method has priority over any reducer in the :attr:`dispatch_table`. It should conform to the same interface as a :meth:`~object.__reduce__` method, and - can optionally return ``NotImplemented`` to fallback on + can optionally return :data:`NotImplemented` to fallback on :attr:`dispatch_table`-registered reducers to pickle ``obj``. For a detailed example, see :ref:`reducer_override`. @@ -503,7 +503,7 @@ What can be pickled and unpickled? The following types can be pickled: * built-in constants (``None``, ``True``, ``False``, ``Ellipsis``, and - ``NotImplemented``); + :data:`NotImplemented`); * integers, floating-point numbers, complex numbers; @@ -905,7 +905,7 @@ functions and classes. For those cases, it is possible to subclass from the :class:`Pickler` class and implement a :meth:`~Pickler.reducer_override` method. This method can return an arbitrary reduction tuple (see :meth:`~object.__reduce__`). It can alternatively return -``NotImplemented`` to fallback to the traditional behavior. +:data:`NotImplemented` to fallback to the traditional behavior. If both the :attr:`~Pickler.dispatch_table` and :meth:`~Pickler.reducer_override` are defined, then diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst index ec2a7ebd5d6e0b1..4bc3956449b930d 100644 --- a/Doc/library/platform.rst +++ b/Doc/library/platform.rst @@ -196,6 +196,10 @@ Java Platform ``(os_name, os_version, os_arch)``. Values which cannot be determined are set to the defaults given as parameters (which all default to ``''``). + .. deprecated-removed:: 3.13 3.15 + It was largely untested, had a confusing API, + and was only useful for Jython support. + Windows Platform ---------------- diff --git a/Doc/library/pprint.rst b/Doc/library/pprint.rst index e883acd67d6c72f..32e1351b7ffeebd 100644 --- a/Doc/library/pprint.rst +++ b/Doc/library/pprint.rst @@ -31,7 +31,93 @@ Dictionaries are sorted by key before the display is computed. .. versionchanged:: 3.10 Added support for pretty-printing :class:`dataclasses.dataclass`. -The :mod:`pprint` module defines one class: +.. _pprint-functions: + +Functions +--------- + +.. function:: pp(object, *args, sort_dicts=False, **kwargs) + + Prints the formatted representation of *object* followed by a newline. + If *sort_dicts* is false (the default), dictionaries will be displayed with + their keys in insertion order, otherwise the dict keys will be sorted. + *args* and *kwargs* will be passed to :func:`~pprint.pprint` as formatting + parameters. + + .. versionadded:: 3.8 + + +.. function:: pprint(object, stream=None, indent=1, width=80, depth=None, *, \ + compact=False, sort_dicts=True, underscore_numbers=False) + + Prints the formatted representation of *object* on *stream*, followed by a + newline. If *stream* is ``None``, :data:`sys.stdout` is used. This may be used + in the interactive interpreter instead of the :func:`print` function for + inspecting values (you can even reassign ``print = pprint.pprint`` for use + within a scope). + + The configuration parameters *stream*, *indent*, *width*, *depth*, + *compact*, *sort_dicts* and *underscore_numbers* are passed to the + :class:`PrettyPrinter` constructor and their meanings are as + described in its documentation below. + + >>> import pprint + >>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni'] + >>> stuff.insert(0, stuff) + >>> pprint.pprint(stuff) + [, + 'spam', + 'eggs', + 'lumberjack', + 'knights', + 'ni'] + +.. function:: pformat(object, indent=1, width=80, depth=None, *, \ + compact=False, sort_dicts=True, underscore_numbers=False) + + Return the formatted representation of *object* as a string. *indent*, + *width*, *depth*, *compact*, *sort_dicts* and *underscore_numbers* are + passed to the :class:`PrettyPrinter` constructor as formatting parameters + and their meanings are as described in its documentation below. + + +.. function:: isreadable(object) + + .. index:: pair: built-in function; eval + + Determine if the formatted representation of *object* is "readable", or can be + used to reconstruct the value using :func:`eval`. This always returns ``False`` + for recursive objects. + + >>> pprint.isreadable(stuff) + False + + +.. function:: isrecursive(object) + + Determine if *object* requires a recursive representation. This function is + subject to the same limitations as noted in :func:`saferepr` below and may raise an + :exc:`RecursionError` if it fails to detect a recursive object. + + +.. function:: saferepr(object) + + Return a string representation of *object*, protected against recursion in + some common data structures, namely instances of :class:`dict`, :class:`list` + and :class:`tuple` or subclasses whose ``__repr__`` has not been overridden. If the + representation of object exposes a recursive entry, the recursive reference + will be represented as ````. The + representation is not otherwise formatted. + + >>> pprint.saferepr(stuff) + "[, 'spam', 'eggs', 'lumberjack', 'knights', 'ni']" + +.. _prettyprinter-objects: + +PrettyPrinter Objects +--------------------- + +This module defines one class: .. First the implementation class: @@ -44,9 +130,9 @@ The :mod:`pprint` module defines one class: Construct a :class:`PrettyPrinter` instance. This constructor understands several keyword parameters. - *stream* (default ``sys.stdout``) is a :term:`file-like object` to + *stream* (default :data:`!sys.stdout`) is a :term:`file-like object` to which the output will be written by calling its :meth:`!write` method. - If both *stream* and ``sys.stdout`` are ``None``, then + If both *stream* and :data:`!sys.stdout` are ``None``, then :meth:`~PrettyPrinter.pprint` silently returns. Other values configure the manner in which nesting of complex data @@ -87,7 +173,7 @@ The :mod:`pprint` module defines one class: Added the *underscore_numbers* parameter. .. versionchanged:: 3.11 - No longer attempts to write to ``sys.stdout`` if it is ``None``. + No longer attempts to write to :data:`!sys.stdout` if it is ``None``. >>> import pprint >>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni'] @@ -112,89 +198,6 @@ The :mod:`pprint` module defines one class: >>> pp.pprint(tup) ('spam', ('eggs', ('lumberjack', ('knights', ('ni', ('dead', (...))))))) -.. function:: pformat(object, indent=1, width=80, depth=None, *, \ - compact=False, sort_dicts=True, underscore_numbers=False) - - Return the formatted representation of *object* as a string. *indent*, - *width*, *depth*, *compact*, *sort_dicts* and *underscore_numbers* are - passed to the :class:`PrettyPrinter` constructor as formatting parameters - and their meanings are as described in its documentation above. - - -.. function:: pp(object, *args, sort_dicts=False, **kwargs) - - Prints the formatted representation of *object* followed by a newline. - If *sort_dicts* is false (the default), dictionaries will be displayed with - their keys in insertion order, otherwise the dict keys will be sorted. - *args* and *kwargs* will be passed to :func:`pprint` as formatting - parameters. - - .. versionadded:: 3.8 - - -.. function:: pprint(object, stream=None, indent=1, width=80, depth=None, *, \ - compact=False, sort_dicts=True, underscore_numbers=False) - - Prints the formatted representation of *object* on *stream*, followed by a - newline. If *stream* is ``None``, ``sys.stdout`` is used. This may be used - in the interactive interpreter instead of the :func:`print` function for - inspecting values (you can even reassign ``print = pprint.pprint`` for use - within a scope). - - The configuration parameters *stream*, *indent*, *width*, *depth*, - *compact*, *sort_dicts* and *underscore_numbers* are passed to the - :class:`PrettyPrinter` constructor and their meanings are as - described in its documentation above. - - >>> import pprint - >>> stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni'] - >>> stuff.insert(0, stuff) - >>> pprint.pprint(stuff) - [, - 'spam', - 'eggs', - 'lumberjack', - 'knights', - 'ni'] - -.. function:: isreadable(object) - - .. index:: pair: built-in function; eval - - Determine if the formatted representation of *object* is "readable", or can be - used to reconstruct the value using :func:`eval`. This always returns ``False`` - for recursive objects. - - >>> pprint.isreadable(stuff) - False - - -.. function:: isrecursive(object) - - Determine if *object* requires a recursive representation. This function is - subject to the same limitations as noted in :func:`saferepr` below and may raise an - :exc:`RecursionError` if it fails to detect a recursive object. - - -One more support function is also defined: - -.. function:: saferepr(object) - - Return a string representation of *object*, protected against recursion in - some common data structures, namely instances of :class:`dict`, :class:`list` - and :class:`tuple` or subclasses whose ``__repr__`` has not been overridden. If the - representation of object exposes a recursive entry, the recursive reference - will be represented as ````. The - representation is not otherwise formatted. - - >>> pprint.saferepr(stuff) - "[, 'spam', 'eggs', 'lumberjack', 'knights', 'ni']" - - -.. _prettyprinter-objects: - -PrettyPrinter Objects ---------------------- :class:`PrettyPrinter` instances have the following methods: @@ -258,7 +261,7 @@ are converted to strings. The default implementation uses the internals of the Example ------- -To demonstrate several uses of the :func:`pprint` function and its parameters, +To demonstrate several uses of the :func:`~pprint.pprint` function and its parameters, let's fetch information about a project from `PyPI `_:: >>> import json @@ -267,7 +270,7 @@ let's fetch information about a project from `PyPI `_:: >>> with urlopen('https://pypi.org/pypi/sampleproject/json') as resp: ... project_info = json.load(resp)['info'] -In its basic form, :func:`pprint` shows the whole object:: +In its basic form, :func:`~pprint.pprint` shows the whole object:: >>> pprint.pprint(project_info) {'author': 'The Python Packaging Authority', diff --git a/Doc/library/pyexpat.rst b/Doc/library/pyexpat.rst index 935e872480efda7..c4b4e6319277af9 100644 --- a/Doc/library/pyexpat.rst +++ b/Doc/library/pyexpat.rst @@ -196,6 +196,42 @@ XMLParser Objects :exc:`ExpatError` to be raised with the :attr:`code` attribute set to ``errors.codes[errors.XML_ERROR_CANT_CHANGE_FEATURE_ONCE_PARSING]``. +.. method:: xmlparser.SetReparseDeferralEnabled(enabled) + + .. warning:: + + Calling ``SetReparseDeferralEnabled(False)`` has security implications, + as detailed below; please make sure to understand these consequences + prior to using the ``SetReparseDeferralEnabled`` method. + + Expat 2.6.0 introduced a security mechanism called "reparse deferral" + where instead of causing denial of service through quadratic runtime + from reparsing large tokens, reparsing of unfinished tokens is now delayed + by default until a sufficient amount of input is reached. + Due to this delay, registered handlers may — depending of the sizing of + input chunks pushed to Expat — no longer be called right after pushing new + input to the parser. Where immediate feedback and taking over responsiblity + of protecting against denial of service from large tokens are both wanted, + calling ``SetReparseDeferralEnabled(False)`` disables reparse deferral + for the current Expat parser instance, temporarily or altogether. + Calling ``SetReparseDeferralEnabled(True)`` allows re-enabling reparse + deferral. + + Note that :meth:`SetReparseDeferralEnabled` has been backported to some + prior releases of CPython as a security fix. Check for availability of + :meth:`SetReparseDeferralEnabled` using :func:`hasattr` if used in code + running across a variety of Python versions. + + .. versionadded:: 3.13 + +.. method:: xmlparser.GetReparseDeferralEnabled() + + Returns whether reparse deferral is currently enabled for the given + Expat parser instance. + + .. versionadded:: 3.13 + + :class:`xmlparser` objects have the following attributes: @@ -214,7 +250,8 @@ XMLParser Objects :meth:`CharacterDataHandler` callback whenever possible. This can improve performance substantially since Expat normally breaks character data into chunks at every line ending. This attribute is false by default, and may be changed at - any time. + any time. Note that when it is false, data that does not contain newlines + may be chunked too. .. attribute:: xmlparser.buffer_used @@ -372,7 +409,10 @@ otherwise stated. marked content, and ignorable whitespace. Applications which must distinguish these cases can use the :attr:`StartCdataSectionHandler`, :attr:`EndCdataSectionHandler`, and :attr:`ElementDeclHandler` callbacks to - collect the required information. + collect the required information. Note that the character data may be + chunked even if it is short and so you may receive more than one call to + :meth:`CharacterDataHandler`. Set the :attr:`buffer_text` instance attribute + to ``True`` to avoid that. .. method:: xmlparser.UnparsedEntityDeclHandler(entityName, base, systemId, publicId, notationName) diff --git a/Doc/library/queue.rst b/Doc/library/queue.rst index 1421fc2e552f0e3..f2a6dbf589fd872 100644 --- a/Doc/library/queue.rst +++ b/Doc/library/queue.rst @@ -187,11 +187,12 @@ fully processed by daemon consumer threads. processed (meaning that a :meth:`task_done` call was received for every item that had been :meth:`put` into the queue). + ``shutdown(immediate=True)`` calls :meth:`task_done` for each remaining item + in the queue. + Raises a :exc:`ValueError` if called more times than there were items placed in the queue. - Raises :exc:`ShutDown` if the queue has been shut down immediately. - .. method:: Queue.join() @@ -202,8 +203,6 @@ fully processed by daemon consumer threads. indicate that the item was retrieved and all work on it is complete. When the count of unfinished tasks drops to zero, :meth:`join` unblocks. - Raises :exc:`ShutDown` if the queue has been shut down immediately. - Example of how to wait for enqueued tasks to be completed:: diff --git a/Doc/library/random.rst b/Doc/library/random.rst index d0ced2416c9578d..8fbce18c56f17c9 100644 --- a/Doc/library/random.rst +++ b/Doc/library/random.rst @@ -301,7 +301,8 @@ be found in any statistics text. ``a <= b`` and ``b <= N <= a`` for ``b < a``. The end-point value ``b`` may or may not be included in the range - depending on floating-point rounding in the equation ``a + (b-a) * random()``. + depending on floating-point rounding in the expression + ``a + (b-a) * random()``. .. function:: triangular(low, high, mode) diff --git a/Doc/library/resource.rst b/Doc/library/resource.rst index 4e58b043f1da315..389a63f089d8502 100644 --- a/Doc/library/resource.rst +++ b/Doc/library/resource.rst @@ -177,6 +177,8 @@ platform. The largest area of mapped memory which the process may occupy. + .. availability:: FreeBSD >= 11. + .. data:: RLIMIT_AS diff --git a/Doc/library/sched.rst b/Doc/library/sched.rst index 01bac5afd0b9b38..4c980dd97f9394f 100644 --- a/Doc/library/sched.rst +++ b/Doc/library/sched.rst @@ -36,7 +36,7 @@ scheduler: Example:: >>> import sched, time - >>> s = sched.scheduler(time.monotonic, time.sleep) + >>> s = sched.scheduler(time.time, time.sleep) >>> def print_time(a='default'): ... print("From print_time", time.time(), a) ... diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 87d5ef1e42ca3ac..e76dc91bf2d875f 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1135,7 +1135,7 @@ Connection objects .. versionchanged:: 3.12 Added the *entrypoint* parameter. - .. _Loading an Extension: https://www.sqlite.org/loadext.html#loading_an_extension_ + .. _Loading an Extension: https://www.sqlite.org/loadext.html#loading_an_extension .. method:: iterdump(*, filter=None) diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index fa81c3f208cff7e..3564855594c32d4 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -151,6 +151,12 @@ purposes. variable :envvar:`SSLKEYLOGFILE` is set, :func:`create_default_context` enables key logging. + The default settings for this context include + :data:`VERIFY_X509_PARTIAL_CHAIN` and :data:`VERIFY_X509_STRICT`. + These make the underlying OpenSSL implementation behave more like + a conforming implementation of :rfc:`5280`, in exchange for a small + amount of incompatibility with older X.509 certificates. + .. note:: The protocol, options, cipher and other settings may change to more restrictive values anytime without prior deprecation. The values @@ -172,6 +178,15 @@ purposes. ctx = ssl.create_default_context(Purpose.CLIENT_AUTH) ctx.options &= ~ssl.OP_NO_SSLv3 + .. note:: + This context enables :data:`VERIFY_X509_STRICT` by default, which + may reject pre-:rfc:`5280` or malformed certificates that the + underlying OpenSSL implementation otherwise would accept. While disabling + this is not recommended, you can do so using:: + + ctx = ssl.create_default_context() + ctx.verify_flags &= ~ssl.VERIFY_X509_STRICT + .. versionadded:: 3.4 .. versionchanged:: 3.4.4 @@ -194,6 +209,11 @@ purposes. :data:`PROTOCOL_TLS_SERVER` protocol instead of generic :data:`PROTOCOL_TLS`. + .. versionchanged:: 3.13 + + The context now uses :data:`VERIFY_X509_PARTIAL_CHAIN` and + :data:`VERIFY_X509_STRICT` in its default verify flags. + Exceptions ^^^^^^^^^^ @@ -737,11 +757,11 @@ Constants When Python has been compiled against an older version of OpenSSL, the flag defaults to *0*. - .. versionadded:: 3.7 + .. versionadded:: 3.6.3 .. deprecated:: 3.7 - The option is deprecated since OpenSSL 1.1.0. It was added to 2.7.15, - 3.6.3 and 3.7.0 for backwards compatibility with OpenSSL 1.0.2. + The option is deprecated since OpenSSL 1.1.0. It was added to 2.7.15 and + 3.6.3 for backwards compatibility with OpenSSL 1.0.2. .. data:: OP_NO_RENEGOTIATION @@ -1790,6 +1810,9 @@ to speed up repeated connections from the same clients. *session*, see :attr:`~SSLSocket.session`. + To wrap an :class:`SSLSocket` in another :class:`SSLSocket`, use + :meth:`SSLContext.wrap_bio`. + .. versionchanged:: 3.5 Always allow a server_hostname to be passed, even if OpenSSL does not have SNI. diff --git a/Doc/library/stat.rst b/Doc/library/stat.rst index c941d5557e31b5f..f7a3b7b16fe5c39 100644 --- a/Doc/library/stat.rst +++ b/Doc/library/stat.rst @@ -354,7 +354,7 @@ The following flags can be used in the *flags* argument of :func:`os.chflags`: All user settable flags. - .. versionadded: 3.13 + .. versionadded:: 3.13 .. data:: UF_NODUMP @@ -384,13 +384,13 @@ The following flags can be used in the *flags* argument of :func:`os.chflags`: Used for handling document IDs (macOS) - .. versionadded: 3.13 + .. versionadded:: 3.13 .. data:: UF_DATAVAULT The file needs an entitlement for reading or writing (macOS 10.13+) - .. versionadded: 3.13 + .. versionadded:: 3.13 .. data:: UF_HIDDEN @@ -400,7 +400,7 @@ The following flags can be used in the *flags* argument of :func:`os.chflags`: All super-user changeable flags - .. versionadded: 3.13 + .. versionadded:: 3.13 .. data:: SF_SUPPORTED @@ -408,7 +408,7 @@ The following flags can be used in the *flags* argument of :func:`os.chflags`: .. availability:: macOS - .. versionadded: 3.13 + .. versionadded:: 3.13 .. data:: SF_SYNTHETIC @@ -416,7 +416,7 @@ The following flags can be used in the *flags* argument of :func:`os.chflags`: .. availability:: macOS - .. versionadded: 3.13 + .. versionadded:: 3.13 .. data:: SF_ARCHIVED @@ -434,7 +434,7 @@ The following flags can be used in the *flags* argument of :func:`os.chflags`: The file needs an entitlement to write to (macOS 10.13+) - .. versionadded: 3.13 + .. versionadded:: 3.13 .. data:: SF_NOUNLINK @@ -448,13 +448,13 @@ The following flags can be used in the *flags* argument of :func:`os.chflags`: The file is a firmlink (macOS 10.15+) - .. versionadded: 3.13 + .. versionadded:: 3.13 .. data:: SF_DATALESS The file is a dataless object (macOS 10.15+) - .. versionadded: 3.13 + .. versionadded:: 3.13 See the \*BSD or macOS systems man page :manpage:`chflags(2)` for more information. diff --git a/Doc/library/statistics.rst b/Doc/library/statistics.rst index 0417b3f38a98079..1785c6bcc212b74 100644 --- a/Doc/library/statistics.rst +++ b/Doc/library/statistics.rst @@ -76,6 +76,7 @@ or sample. :func:`fmean` Fast, floating point arithmetic mean, with optional weighting. :func:`geometric_mean` Geometric mean of data. :func:`harmonic_mean` Harmonic mean of data. +:func:`kde` Estimate the probability density distribution of the data. :func:`median` Median (middle value) of data. :func:`median_low` Low median of data. :func:`median_high` High median of data. @@ -259,6 +260,54 @@ However, for reading convenience, most of the examples show sorted sequences. .. versionchanged:: 3.10 Added support for *weights*. + +.. function:: kde(data, h, kernel='normal') + + `Kernel Density Estimation (KDE) + `_: + Create a continuous probability density function from discrete samples. + + The basic idea is to smooth the data using `a kernel function + `_. + to help draw inferences about a population from a sample. + + The degree of smoothing is controlled by the scaling parameter *h* + which is called the bandwidth. Smaller values emphasize local + features while larger values give smoother results. + + The *kernel* determines the relative weights of the sample data + points. Generally, the choice of kernel shape does not matter + as much as the more influential bandwidth smoothing parameter. + + Kernels that give some weight to every sample point include + *normal* or *gauss*, *logistic*, and *sigmoid*. + + Kernels that only give weight to sample points within the bandwidth + include *rectangular* or *uniform*, *triangular*, *parabolic* or + *epanechnikov*, *quartic* or *biweight*, *triweight*, and *cosine*. + + A :exc:`StatisticsError` will be raised if the *data* sequence is empty. + + `Wikipedia has an example + `_ + where we can use :func:`kde` to generate and plot a probability + density function estimated from a small sample: + + .. doctest:: + + >>> sample = [-2.1, -1.3, -0.4, 1.9, 5.1, 6.2] + >>> f_hat = kde(sample, h=1.5) + >>> xarr = [i/100 for i in range(-750, 1100)] + >>> yarr = [f_hat(x) for x in xarr] + + The points in ``xarr`` and ``yarr`` can be used to make a PDF plot: + + .. image:: kde_example.png + :alt: Scatter plot of the estimated probability density function. + + .. versionadded:: 3.13 + + .. function:: median(data) Return the median (middle value) of numeric data, using the common "mean of @@ -1095,46 +1144,6 @@ The final prediction goes to the largest posterior. This is known as the 'female' -Kernel density estimation -************************* - -It is possible to estimate a continuous probability density function -from a fixed number of discrete samples. - -The basic idea is to smooth the data using `a kernel function such as a -normal distribution, triangular distribution, or uniform distribution -`_. -The degree of smoothing is controlled by a scaling parameter, ``h``, -which is called the *bandwidth*. - -.. testcode:: - - def kde_normal(sample, h): - "Create a continuous probability density function from a sample." - # Smooth the sample with a normal distribution kernel scaled by h. - kernel_h = NormalDist(0.0, h).pdf - n = len(sample) - def pdf(x): - return sum(kernel_h(x - x_i) for x_i in sample) / n - return pdf - -`Wikipedia has an example -`_ -where we can use the ``kde_normal()`` recipe to generate and plot -a probability density function estimated from a small sample: - -.. doctest:: - - >>> sample = [-2.1, -1.3, -0.4, 1.9, 5.1, 6.2] - >>> f_hat = kde_normal(sample, h=1.5) - >>> xarr = [i/100 for i in range(-750, 1100)] - >>> yarr = [f_hat(x) for x in xarr] - -The points in ``xarr`` and ``yarr`` can be used to make a PDF plot: - -.. image:: kde_example.png - :alt: Scatter plot of the estimated probability density function. - .. # This modelines must appear within the last ten lines of the file. kate: indent-width 3; remove-trailing-space on; replace-tabs on; encoding utf-8; diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 1a4c12590c1018d..c963519f164dd22 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -1491,8 +1491,7 @@ objects that compare equal might have different :attr:`~range.start`, sequence of values they define (instead of comparing based on object identity). -.. versionadded:: 3.3 - The :attr:`~range.start`, :attr:`~range.stop` and :attr:`~range.step` + Added the :attr:`~range.start`, :attr:`~range.stop` and :attr:`~range.step` attributes. .. seealso:: @@ -5450,10 +5449,10 @@ The NotImplemented Object This object is returned from comparisons and binary operations when they are asked to operate on types they don't support. See :ref:`comparisons` for more -information. There is exactly one ``NotImplemented`` object. -``type(NotImplemented)()`` produces the singleton instance. +information. There is exactly one :data:`NotImplemented` object. +:code:`type(NotImplemented)()` produces the singleton instance. -It is written as ``NotImplemented``. +It is written as :code:`NotImplemented`. .. _typesinternal: diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 380ba1090b39b3b..087a3454c33272a 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -1367,47 +1367,42 @@ always available. .. data:: platform - This string contains a platform identifier that can be used to append - platform-specific components to :data:`sys.path`, for instance. - - For Unix systems, except on Linux and AIX, this is the lowercased OS name as - returned by ``uname -s`` with the first part of the version as returned by - ``uname -r`` appended, e.g. ``'sunos5'`` or ``'freebsd8'``, *at the time - when Python was built*. Unless you want to test for a specific system - version, it is therefore recommended to use the following idiom:: - - if sys.platform.startswith('freebsd'): - # FreeBSD-specific code here... - elif sys.platform.startswith('linux'): - # Linux-specific code here... - elif sys.platform.startswith('aix'): - # AIX-specific code here... - - For other systems, the values are: + A string containing a platform identifier. Known values are: ================ =========================== System ``platform`` value ================ =========================== AIX ``'aix'`` + Android ``'android'`` Emscripten ``'emscripten'`` + iOS ``'ios'`` Linux ``'linux'`` - WASI ``'wasi'`` + macOS ``'darwin'`` Windows ``'win32'`` Windows/Cygwin ``'cygwin'`` - macOS ``'darwin'`` + WASI ``'wasi'`` ================ =========================== + On Unix systems not listed in the table, the value is the lowercased OS name + as returned by ``uname -s``, with the first part of the version as returned by + ``uname -r`` appended, e.g. ``'sunos5'`` or ``'freebsd8'``, *at the time + when Python was built*. Unless you want to test for a specific system + version, it is therefore recommended to use the following idiom:: + + if sys.platform.startswith('freebsd'): + # FreeBSD-specific code here... + .. versionchanged:: 3.3 On Linux, :data:`sys.platform` doesn't contain the major version anymore. - It is always ``'linux'``, instead of ``'linux2'`` or ``'linux3'``. Since - older Python versions include the version number, it is recommended to - always use the ``startswith`` idiom presented above. + It is always ``'linux'``, instead of ``'linux2'`` or ``'linux3'``. .. versionchanged:: 3.8 On AIX, :data:`sys.platform` doesn't contain the major version anymore. - It is always ``'aix'``, instead of ``'aix5'`` or ``'aix7'``. Since - older Python versions include the version number, it is recommended to - always use the ``startswith`` idiom presented above. + It is always ``'aix'``, instead of ``'aix5'`` or ``'aix7'``. + + .. versionchanged:: 3.13 + On Android, :data:`sys.platform` now returns ``'android'`` rather than + ``'linux'``. .. seealso:: diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index 5fbf9379b8202c0..4cf98a49e11442c 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -363,12 +363,12 @@ since it is impossible to detect the termination of alien threads. base class constructor (``Thread.__init__()``) before doing anything else to the thread. + .. versionchanged:: 3.3 + Added the *daemon* parameter. + .. versionchanged:: 3.10 Use the *target* name if *name* argument is omitted. - .. versionchanged:: 3.3 - Added the *daemon* argument. - .. method:: start() Start the thread's activity. @@ -987,18 +987,15 @@ method. The :meth:`~Event.wait` method blocks until the flag is true. .. method:: wait(timeout=None) - Block until the internal flag is true. If the internal flag is true on - entry, return immediately. Otherwise, block until another thread calls - :meth:`.set` to set the flag to true, or until the optional timeout occurs. + Block as long as the internal flag is false and the timeout, if given, + has not expired. The return value represents the + reason that this blocking method returned; ``True`` if returning because + the internal flag is set to true, or ``False`` if a timeout is given and + the the internal flag did not become true within the given wait time. When the timeout argument is present and not ``None``, it should be a - floating point number specifying a timeout for the operation in seconds - (or fractions thereof). - - This method returns ``True`` if and only if the internal flag has been set to - true, either before the wait call or after the wait starts, so it will - always return ``True`` except if a timeout is given and the operation - times out. + floating point number specifying a timeout for the operation in seconds, + or fractions thereof. .. versionchanged:: 3.1 Previously, the method always returned ``None``. diff --git a/Doc/library/time.rst b/Doc/library/time.rst index 2782a961363666b..d79ca6e12081070 100644 --- a/Doc/library/time.rst +++ b/Doc/library/time.rst @@ -287,6 +287,15 @@ Functions The reference point of the returned value is undefined, so that only the difference between the results of two calls is valid. + Clock: + + * On Windows, call ``QueryPerformanceCounter()`` and + ``QueryPerformanceFrequency()``. + * On macOS, call ``mach_absolute_time()`` and ``mach_timebase_info()``. + * On HP-UX, call ``gethrtime()``. + * Call ``clock_gettime(CLOCK_HIGHRES)`` if available. + * Otherwise, call ``clock_gettime(CLOCK_MONOTONIC)``. + Use :func:`monotonic_ns` to avoid the precision loss caused by the :class:`float` type. @@ -316,6 +325,11 @@ Functions point of the returned value is undefined, so that only the difference between the results of two calls is valid. + .. impl-detail:: + + On CPython, use the same clock than :func:`time.monotonic()` and is a + monotonic clock, i.e. a clock that cannot go backwards. + Use :func:`perf_counter_ns` to avoid the precision loss caused by the :class:`float` type. @@ -324,6 +338,10 @@ Functions .. versionchanged:: 3.10 On Windows, the function is now system-wide. + .. versionchanged:: 3.13 + Use the same clock than :func:`time.monotonic()`. + + .. function:: perf_counter_ns() -> int Similar to :func:`perf_counter`, but return time as nanoseconds. @@ -383,15 +401,14 @@ Functions .. audit-event:: time.sleep secs - .. versionchanged:: 3.11 - On Unix, the ``clock_nanosleep()`` and ``nanosleep()`` functions are now - used if available. On Windows, a waitable timer is now used. - .. versionchanged:: 3.5 The function now sleeps at least *secs* even if the sleep is interrupted by a signal, except if the signal handler raises an exception (see :pep:`475` for the rationale). + .. versionchanged:: 3.11 + On Unix, the ``clock_nanosleep()`` and ``nanosleep()`` functions are now + used if available. On Windows, a waitable timer is now used. .. versionchanged:: 3.13 Raises an auditing event. @@ -433,6 +450,10 @@ Functions | ``%d`` | Day of the month as a decimal number [01,31]. | | | | | | +-----------+------------------------------------------------+-------+ + | ``%f`` | Microseconds as a decimal number | \(1) | + | | [000000,999999]. | | + | | | | + +-----------+------------------------------------------------+-------+ | ``%H`` | Hour (24-hour clock) as a decimal number | | | | [00,23]. | | +-----------+------------------------------------------------+-------+ @@ -448,13 +469,13 @@ Functions | ``%M`` | Minute as a decimal number [00,59]. | | | | | | +-----------+------------------------------------------------+-------+ - | ``%p`` | Locale's equivalent of either AM or PM. | \(1) | + | ``%p`` | Locale's equivalent of either AM or PM. | \(2) | | | | | +-----------+------------------------------------------------+-------+ - | ``%S`` | Second as a decimal number [00,61]. | \(2) | + | ``%S`` | Second as a decimal number [00,61]. | \(3) | | | | | +-----------+------------------------------------------------+-------+ - | ``%U`` | Week number of the year (Sunday as the first | \(3) | + | ``%U`` | Week number of the year (Sunday as the first | \(4) | | | day of the week) as a decimal number [00,53]. | | | | All days in a new year preceding the first | | | | Sunday are considered to be in week 0. | | @@ -465,7 +486,7 @@ Functions | ``%w`` | Weekday as a decimal number [0(Sunday),6]. | | | | | | +-----------+------------------------------------------------+-------+ - | ``%W`` | Week number of the year (Monday as the first | \(3) | + | ``%W`` | Week number of the year (Monday as the first | \(4) | | | day of the week) as a decimal number [00,53]. | | | | All days in a new year preceding the first | | | | Monday are considered to be in week 0. | | @@ -500,17 +521,23 @@ Functions Notes: (1) + The ``%f`` format directive only applies to :func:`strptime`, + not to :func:`strftime`. However, see also :meth:`datetime.datetime.strptime` and + :meth:`datetime.datetime.strftime` where the ``%f`` format directive + :ref:`applies to microseconds `. + + (2) When used with the :func:`strptime` function, the ``%p`` directive only affects the output hour field if the ``%I`` directive is used to parse the hour. .. _leap-second: - (2) + (3) The range really is ``0`` to ``61``; value ``60`` is valid in timestamps representing `leap seconds`_ and value ``61`` is supported for historical reasons. - (3) + (4) When used with the :func:`strptime` function, ``%U`` and ``%W`` are only used in calculations when the day of the week and the year are specified. @@ -657,6 +684,12 @@ Functions :class:`struct_time` object is returned, from which the components of the calendar date may be accessed as attributes. + Clock: + + * On Windows, call ``GetSystemTimeAsFileTime()``. + * Call ``clock_gettime(CLOCK_REALTIME)`` if available. + * Otherwise, call ``gettimeofday()``. + Use :func:`time_ns` to avoid the precision loss caused by the :class:`float` type. diff --git a/Doc/library/turtle.rst b/Doc/library/turtle.rst index 88b1f09eb3c8b78..2941201332a715e 100644 --- a/Doc/library/turtle.rst +++ b/Doc/library/turtle.rst @@ -606,7 +606,7 @@ Turtle motion >>> turtle.pos() (20.00,30.00) - .. versionadded: 3.12 + .. versionadded:: 3.12 .. function:: setx(x) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 63bd62d1f6679b7..713ad1c83546d16 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -954,7 +954,6 @@ using ``[]``. be used for this concept instead. Type checkers should treat the two equivalently. - .. versionadded:: 3.5.4 .. versionadded:: 3.6.2 .. data:: Self @@ -1234,6 +1233,10 @@ These can be used as types in annotations. They all support subscription using .. versionadded:: 3.5.3 + .. versionchanged:: 3.13 + + :data:`ClassVar` can now be nested in :data:`Final` and vice versa. + .. data:: Final Special typing construct to indicate final names to type checkers. @@ -1257,6 +1260,10 @@ These can be used as types in annotations. They all support subscription using .. versionadded:: 3.8 + .. versionchanged:: 3.13 + + :data:`Final` can now be nested in :data:`ClassVar` and vice versa. + .. data:: Required Special typing construct to mark a :class:`TypedDict` key as required. @@ -1275,6 +1282,26 @@ These can be used as types in annotations. They all support subscription using .. versionadded:: 3.11 +.. data:: ReadOnly + + A special typing construct to mark an item of a :class:`TypedDict` as read-only. + + For example:: + + class Movie(TypedDict): + title: ReadOnly[str] + year: int + + def mutate_movie(m: Movie) -> None: + m["year"] = 1992 # allowed + m["title"] = "The Matrix" # typechecker error + + There is no runtime checking for this property. + + See :class:`TypedDict` and :pep:`705` for more details. + + .. versionadded:: 3.13 + .. data:: Annotated Special typing form to add context-specific metadata to an annotation. @@ -2455,6 +2482,22 @@ types. ``__required_keys__`` and ``__optional_keys__`` rely on may not work properly, and the values of the attributes may be incorrect. + Support for :data:`ReadOnly` is reflected in the following attributes:: + + .. attribute:: __readonly_keys__ + + A :class:`frozenset` containing the names of all read-only keys. Keys + are read-only if they carry the :data:`ReadOnly` qualifier. + + .. versionadded:: 3.13 + + .. attribute:: __mutable_keys__ + + A :class:`frozenset` containing the names of all mutable keys. Keys + are mutable if they do not carry the :data:`ReadOnly` qualifier. + + .. versionadded:: 3.13 + See :pep:`589` for more examples and detailed rules of using ``TypedDict``. .. versionadded:: 3.8 @@ -2469,6 +2512,9 @@ types. .. versionchanged:: 3.13 Removed support for the keyword-argument method of creating ``TypedDict``\ s. + .. versionchanged:: 3.13 + Support for the :data:`ReadOnly` qualifier was added. + .. deprecated-removed:: 3.13 3.15 When using the functional syntax to create a TypedDict class, failing to pass a value to the 'fields' parameter (``TD = TypedDict("TD")``) is @@ -3292,7 +3338,6 @@ Aliases to types in :mod:`collections` Deprecated alias to :class:`collections.ChainMap`. - .. versionadded:: 3.5.4 .. versionadded:: 3.6.1 .. deprecated:: 3.9 @@ -3303,7 +3348,6 @@ Aliases to types in :mod:`collections` Deprecated alias to :class:`collections.Counter`. - .. versionadded:: 3.5.4 .. versionadded:: 3.6.1 .. deprecated:: 3.9 @@ -3314,7 +3358,6 @@ Aliases to types in :mod:`collections` Deprecated alias to :class:`collections.deque`. - .. versionadded:: 3.5.4 .. versionadded:: 3.6.1 .. deprecated:: 3.9 @@ -3389,7 +3432,7 @@ Aliases to container ABCs in :mod:`collections.abc` Deprecated alias to :class:`collections.abc.Collection`. - .. versionadded:: 3.6.0 + .. versionadded:: 3.6 .. deprecated:: 3.9 :class:`collections.abc.Collection` now supports subscripting (``[]``). @@ -3681,7 +3724,6 @@ Aliases to :mod:`contextlib` ABCs Deprecated alias to :class:`contextlib.AbstractContextManager`. .. versionadded:: 3.5.4 - .. versionadded:: 3.6.0 .. deprecated:: 3.9 :class:`contextlib.AbstractContextManager` @@ -3692,7 +3734,6 @@ Aliases to :mod:`contextlib` ABCs Deprecated alias to :class:`contextlib.AbstractAsyncContextManager`. - .. versionadded:: 3.5.4 .. versionadded:: 3.6.2 .. deprecated:: 3.9 diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index b0a5d96c38d375b..d1f2a96df667c66 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -2143,10 +2143,10 @@ to change the default. Methods and their defaults: -* ``__lt__``: ``NotImplemented`` -* ``__gt__``: ``NotImplemented`` -* ``__le__``: ``NotImplemented`` -* ``__ge__``: ``NotImplemented`` +* ``__lt__``: :data:`NotImplemented` +* ``__gt__``: :data:`!NotImplemented` +* ``__le__``: :data:`!NotImplemented` +* ``__ge__``: :data:`!NotImplemented` * ``__int__``: ``1`` * ``__contains__``: ``False`` * ``__len__``: ``0`` @@ -2831,3 +2831,123 @@ Sealing mocks >>> mock.not_submock.attribute2 # This won't raise. .. versionadded:: 3.7 + + +Order of precedence of :attr:`side_effect`, :attr:`return_value` and *wraps* +---------------------------------------------------------------------------- + +The order of their precedence is: + +1. :attr:`~Mock.side_effect` +2. :attr:`~Mock.return_value` +3. *wraps* + +If all three are set, mock will return the value from :attr:`~Mock.side_effect`, +ignoring :attr:`~Mock.return_value` and the wrapped object altogether. If any +two are set, the one with the higher precedence will return the value. +Regardless of the order of which was set first, the order of precedence +remains unchanged. + + >>> from unittest.mock import Mock + >>> class Order: + ... @staticmethod + ... def get_value(): + ... return "third" + ... + >>> order_mock = Mock(spec=Order, wraps=Order) + >>> order_mock.get_value.side_effect = ["first"] + >>> order_mock.get_value.return_value = "second" + >>> order_mock.get_value() + 'first' + +As ``None`` is the default value of :attr:`~Mock.side_effect`, if you reassign +its value back to ``None``, the order of precedence will be checked between +:attr:`~Mock.return_value` and the wrapped object, ignoring +:attr:`~Mock.side_effect`. + + >>> order_mock.get_value.side_effect = None + >>> order_mock.get_value() + 'second' + +If the value being returned by :attr:`~Mock.side_effect` is :data:`DEFAULT`, +it is ignored and the order of precedence moves to the successor to obtain the +value to return. + + >>> from unittest.mock import DEFAULT + >>> order_mock.get_value.side_effect = [DEFAULT] + >>> order_mock.get_value() + 'second' + +When :class:`Mock` wraps an object, the default value of +:attr:`~Mock.return_value` will be :data:`DEFAULT`. + + >>> order_mock = Mock(spec=Order, wraps=Order) + >>> order_mock.return_value + sentinel.DEFAULT + >>> order_mock.get_value.return_value + sentinel.DEFAULT + +The order of precedence will ignore this value and it will move to the last +successor which is the wrapped object. + +As the real call is being made to the wrapped object, creating an instance of +this mock will return the real instance of the class. The positional arguments, +if any, required by the wrapped object must be passed. + + >>> order_mock_instance = order_mock() + >>> isinstance(order_mock_instance, Order) + True + >>> order_mock_instance.get_value() + 'third' + + >>> order_mock.get_value.return_value = DEFAULT + >>> order_mock.get_value() + 'third' + + >>> order_mock.get_value.return_value = "second" + >>> order_mock.get_value() + 'second' + +But if you assign ``None`` to it, this will not be ignored as it is an +explicit assignment. So, the order of precedence will not move to the wrapped +object. + + >>> order_mock.get_value.return_value = None + >>> order_mock.get_value() is None + True + +Even if you set all three at once when initializing the mock, the order of +precedence remains the same: + + >>> order_mock = Mock(spec=Order, wraps=Order, + ... **{"get_value.side_effect": ["first"], + ... "get_value.return_value": "second"} + ... ) + ... + >>> order_mock.get_value() + 'first' + >>> order_mock.get_value.side_effect = None + >>> order_mock.get_value() + 'second' + >>> order_mock.get_value.return_value = DEFAULT + >>> order_mock.get_value() + 'third' + +If :attr:`~Mock.side_effect` is exhausted, the order of precedence will not +cause a value to be obtained from the successors. Instead, ``StopIteration`` +exception is raised. + + >>> order_mock = Mock(spec=Order, wraps=Order) + >>> order_mock.get_value.side_effect = ["first side effect value", + ... "another side effect value"] + >>> order_mock.get_value.return_value = "second" + + >>> order_mock.get_value() + 'first side effect value' + >>> order_mock.get_value() + 'another side effect value' + + >>> order_mock.get_value() + Traceback (most recent call last): + ... + StopIteration diff --git a/Doc/library/urllib.request.rst b/Doc/library/urllib.request.rst index affdce144cd5fcc..1a17c9cd7dec4e5 100644 --- a/Doc/library/urllib.request.rst +++ b/Doc/library/urllib.request.rst @@ -97,11 +97,9 @@ The :mod:`urllib.request` module defines the following functions: .. versionchanged:: 3.2 *cafile* and *capath* were added. - .. versionchanged:: 3.2 HTTPS virtual hosts are now supported if possible (that is, if :const:`ssl.HAS_SNI` is true). - .. versionadded:: 3.2 *data* can be an iterable object. .. versionchanged:: 3.3 diff --git a/Doc/library/venv.rst b/Doc/library/venv.rst index 2e7ff345a062342..a4273f97b7a8dbd 100644 --- a/Doc/library/venv.rst +++ b/Doc/library/venv.rst @@ -54,7 +54,7 @@ See :pep:`405` for more background on Python virtual environments. .. seealso:: `Python Packaging User Guide: Creating and using virtual environments - `__ + `__ .. include:: ../includes/wasm-notavail.rst diff --git a/Doc/library/xml.etree.elementtree.rst b/Doc/library/xml.etree.elementtree.rst index 75a7915c15240d2..5955647588fa3e0 100644 --- a/Doc/library/xml.etree.elementtree.rst +++ b/Doc/library/xml.etree.elementtree.rst @@ -166,6 +166,11 @@ data but would still like to have incremental parsing capabilities, take a look at :func:`iterparse`. It can be useful when you're reading a large XML document and don't want to hold it wholly in memory. +Where *immediate* feedback through events is wanted, calling method +:meth:`XMLPullParser.flush` can help reduce delay; +please make sure to study the related security notes. + + Finding interesting elements ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1387,6 +1392,24 @@ XMLParser Objects Feeds data to the parser. *data* is encoded data. + + .. method:: flush() + + Triggers parsing of any previously fed unparsed data, which can be + used to ensure more immediate feedback, in particular with Expat >=2.6.0. + The implementation of :meth:`flush` temporarily disables reparse deferral + with Expat (if currently enabled) and triggers a reparse. + Disabling reparse deferral has security consequences; please see + :meth:`xml.parsers.expat.xmlparser.SetReparseDeferralEnabled` for details. + + Note that :meth:`flush` has been backported to some prior releases of + CPython as a security fix. Check for availability of :meth:`flush` + using :func:`hasattr` if used in code running across a variety of Python + versions. + + .. versionadded:: 3.13 + + :meth:`XMLParser.feed` calls *target*\'s ``start(tag, attrs_dict)`` method for each opening tag, its ``end(tag)`` method for each closing tag, and data is processed by method ``data(data)``. For further supported callback @@ -1448,6 +1471,22 @@ XMLPullParser Objects Feed the given bytes data to the parser. + .. method:: flush() + + Triggers parsing of any previously fed unparsed data, which can be + used to ensure more immediate feedback, in particular with Expat >=2.6.0. + The implementation of :meth:`flush` temporarily disables reparse deferral + with Expat (if currently enabled) and triggers a reparse. + Disabling reparse deferral has security consequences; please see + :meth:`xml.parsers.expat.xmlparser.SetReparseDeferralEnabled` for details. + + Note that :meth:`flush` has been backported to some prior releases of + CPython as a security fix. Check for availability of :meth:`flush` + using :func:`hasattr` if used in code running across a variety of Python + versions. + + .. versionadded:: 3.13 + .. method:: close() Signal the parser that the data stream is terminated. Unlike diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index afeb6596fbb9787..75b656f385d34bb 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -34,7 +34,7 @@ represented by objects.) Every object has an identity, a type and a value. An object's *identity* never changes once it has been created; you may think of it as the object's address in -memory. The ':keyword:`is`' operator compares the identity of two objects; the +memory. The :keyword:`is` operator compares the identity of two objects; the :func:`id` function returns an integer representing its identity. .. impl-detail:: @@ -81,7 +81,7 @@ are still reachable. Note that the use of the implementation's tracing or debugging facilities may keep objects alive that would normally be collectable. Also note that catching -an exception with a ':keyword:`try`...\ :keyword:`except`' statement may keep +an exception with a :keyword:`try`...\ :keyword:`except` statement may keep objects alive. Some objects contain references to "external" resources such as open files or @@ -89,8 +89,8 @@ windows. It is understood that these resources are freed when the object is garbage-collected, but since garbage collection is not guaranteed to happen, such objects also provide an explicit way to release the external resource, usually a :meth:`!close` method. Programs are strongly recommended to explicitly -close such objects. The ':keyword:`try`...\ :keyword:`finally`' statement -and the ':keyword:`with`' statement provide convenient ways to do this. +close such objects. The :keyword:`try`...\ :keyword:`finally` statement +and the :keyword:`with` statement provide convenient ways to do this. .. index:: single: container @@ -159,7 +159,7 @@ NotImplemented .. index:: pair: object; NotImplemented This type has a single value. There is a single object with this value. This -object is accessed through the built-in name ``NotImplemented``. Numeric methods +object is accessed through the built-in name :data:`NotImplemented`. Numeric methods and rich comparison methods should return this value if they do not implement the operation for the operands provided. (The interpreter will then try the reflected operation, or some other fallback, depending on the operator.) It @@ -170,7 +170,7 @@ See for more details. .. versionchanged:: 3.9 - Evaluating ``NotImplemented`` in a boolean context is deprecated. While + Evaluating :data:`NotImplemented` in a boolean context is deprecated. While it currently evaluates as true, it will emit a :exc:`DeprecationWarning`. It will raise a :exc:`TypeError` in a future version of Python. @@ -1787,7 +1787,7 @@ Basic customization ``x.__ne__(y)``, ``x>y`` calls ``x.__gt__(y)``, and ``x>=y`` calls ``x.__ge__(y)``. - A rich comparison method may return the singleton ``NotImplemented`` if it does + A rich comparison method may return the singleton :data:`NotImplemented` if it does not implement the operation for a given pair of arguments. By convention, ``False`` and ``True`` are returned for a successful comparison. However, these methods can return any value, so if the comparison operator is used in a Boolean @@ -1795,10 +1795,10 @@ Basic customization :func:`bool` on the value to determine if the result is true or false. By default, ``object`` implements :meth:`__eq__` by using ``is``, returning - ``NotImplemented`` in the case of a false comparison: + :data:`NotImplemented` in the case of a false comparison: ``True if x is y else NotImplemented``. For :meth:`__ne__`, by default it delegates to :meth:`__eq__` and inverts the result unless it is - ``NotImplemented``. There are no other implied relationships among the + :data:`!NotImplemented`. There are no other implied relationships among the comparison operators or default implementations; for example, the truth of ``(x=`` 0. The return value may also be - :const:`NotImplemented`, which is treated the same as if the + :data:`NotImplemented`, which is treated the same as if the ``__length_hint__`` method didn't exist at all. This method is purely an optimization and is never required for correctness. @@ -2980,7 +2983,7 @@ left undefined. function is to be supported. If one of those methods does not support the operation with the supplied - arguments, it should return ``NotImplemented``. + arguments, it should return :data:`NotImplemented`. .. method:: object.__radd__(self, other) @@ -3010,7 +3013,7 @@ left undefined. types. [#]_ For instance, to evaluate the expression ``x - y``, where *y* is an instance of a class that has an :meth:`__rsub__` method, ``type(y).__rsub__(y, x)`` is called if ``type(x).__sub__(x, y)`` returns - *NotImplemented*. + :data:`NotImplemented`. .. index:: pair: built-in function; pow @@ -3044,10 +3047,12 @@ left undefined. (``+=``, ``-=``, ``*=``, ``@=``, ``/=``, ``//=``, ``%=``, ``**=``, ``<<=``, ``>>=``, ``&=``, ``^=``, ``|=``). These methods should attempt to do the operation in-place (modifying *self*) and return the result (which could be, - but does not have to be, *self*). If a specific method is not defined, the + but does not have to be, *self*). If a specific method is not defined, or if + that method returns :data:`NotImplemented`, the augmented assignment falls back to the normal methods. For instance, if *x* is an instance of a class with an :meth:`__iadd__` method, ``x += y`` is - equivalent to ``x = x.__iadd__(y)`` . Otherwise, ``x.__add__(y)`` and + equivalent to ``x = x.__iadd__(y)`` . If :meth:`__iadd__` does not exist, or if ``x.__iadd__(y)`` + returns :data:`!NotImplemented`, ``x.__add__(y)`` and ``y.__radd__(x)`` are considered, as with the evaluation of ``x + y``. In certain situations, augmented assignment can result in unexpected errors (see :ref:`faq-augmented-assignment-tuple-error`), but this behavior is in fact @@ -3503,7 +3508,7 @@ An example of an asynchronous context manager class:: the behavior that ``None`` is not callable. .. [#] "Does not support" here means that the class has no such method, or - the method returns ``NotImplemented``. Do not set the method to + the method returns :data:`NotImplemented`. Do not set the method to ``None`` if you want to force fallback to the right operand's reflected method—that will instead have the opposite effect of explicitly *blocking* such fallback. diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index 50e0f97a6534af2..00b57effd3e1c01 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -1539,7 +1539,7 @@ built-in types. ``x == x`` are all false, while ``x != x`` is true. This behavior is compliant with IEEE 754. -* ``None`` and ``NotImplemented`` are singletons. :PEP:`8` advises that +* ``None`` and :data:`NotImplemented` are singletons. :PEP:`8` advises that comparisons for singletons should always be done with ``is`` or ``is not``, never the equality operators. diff --git a/Doc/reference/lexical_analysis.rst b/Doc/reference/lexical_analysis.rst index 0adfb0365934e4a..f0b3d0a7458cbeb 100644 --- a/Doc/reference/lexical_analysis.rst +++ b/Doc/reference/lexical_analysis.rst @@ -514,7 +514,6 @@ is not supported. The ``'rb'`` prefix of raw bytes literals has been added as a synonym of ``'br'``. -.. versionadded:: 3.3 Support for the unicode legacy literal (``u'value'``) was reintroduced to simplify the maintenance of dual Python 2.x and 3.x codebases. See :pep:`414` for more information. @@ -734,7 +733,7 @@ for the contents of the string is: : ("," `conditional_expression` | "," "*" `or_expr`)* [","] : | `yield_expression` conversion: "s" | "r" | "a" - format_spec: (`literal_char` | NULL | `replacement_field`)* + format_spec: (`literal_char` | `replacement_field`)* literal_char: The parts of the string outside curly braces are treated literally, diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst index 04132c78ce77a67..810232e09dc5fad 100644 --- a/Doc/reference/simple_stmts.rst +++ b/Doc/reference/simple_stmts.rst @@ -664,8 +664,7 @@ and information about handling exceptions is in section :ref:`try`. .. versionchanged:: 3.3 :const:`None` is now permitted as ``Y`` in ``raise X from Y``. -.. versionadded:: 3.3 - The :attr:`~BaseException.__suppress_context__` attribute to suppress + Added the :attr:`~BaseException.__suppress_context__` attribute to suppress automatic display of the exception context. .. versionchanged:: 3.11 diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index eb45413d7cef787..5fbc24c6ee65a51 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -29,7 +29,6 @@ Doc/library/email.parser.rst Doc/library/email.policy.rst Doc/library/exceptions.rst Doc/library/faulthandler.rst -Doc/library/fcntl.rst Doc/library/functools.rst Doc/library/http.cookiejar.rst Doc/library/http.server.rst @@ -79,8 +78,6 @@ Doc/reference/compound_stmts.rst Doc/reference/datamodel.rst Doc/tutorial/datastructures.rst Doc/using/windows.rst -Doc/whatsnew/2.0.rst -Doc/whatsnew/2.1.rst Doc/whatsnew/2.4.rst Doc/whatsnew/2.5.rst Doc/whatsnew/2.6.rst diff --git a/Doc/tools/templates/indexcontent.html b/Doc/tools/templates/indexcontent.html index 1e3ab7cfe02feeb..5b3c174f9d17290 100644 --- a/Doc/tools/templates/indexcontent.html +++ b/Doc/tools/templates/indexcontent.html @@ -7,60 +7,60 @@

{{ docstitle|e }}

{% trans %}Welcome! This is the official documentation for Python {{ release }}.{% endtrans %}

-

{% trans %}Parts of the documentation:{% endtrans %}

+

{% trans %}Documentation sections:{% endtrans %}

+ {% trans whatsnew_index=pathto("whatsnew/index") %}Or all "What's new" documents since Python 2.0{% endtrans %}

- - - + {% trans %}Start here: a tour of Python's syntax and features{% endtrans %}

+ + + + {% trans %}In-depth topic manuals{% endtrans %}

- - - - + + + + + {% trans %}Frequently asked questions (with answers!){% endtrans %}

-

{% trans %}Indices and tables:{% endtrans %}

+

{% trans %}Indices, glossary, and search:{% endtrans %}

- - + + + {% trans %}Terms explained{% endtrans %}

- + {% trans %}Search this documentation{% endtrans %}

+
-

{% trans %}Meta information:{% endtrans %}

+

{% trans %}Project information:{% endtrans %}

diff --git a/Doc/tutorial/errors.rst b/Doc/tutorial/errors.rst index 4058ebe8efdb424..0b9acd00fdc6bd0 100644 --- a/Doc/tutorial/errors.rst +++ b/Doc/tutorial/errors.rst @@ -20,12 +20,12 @@ complaint you get while you are still learning Python:: >>> while True print('Hello world') File "", line 1 while True print('Hello world') - ^ + ^^^^^ SyntaxError: invalid syntax -The parser repeats the offending line and displays a little 'arrow' pointing at -the earliest point in the line where the error was detected. The error is -caused by (or at least detected at) the token *preceding* the arrow: in the +The parser repeats the offending line and displays little 'arrow's pointing +at the token in the line where the error was detected. The error may be +caused by the absence of a token *before* the indicated token. In the example, the error is detected at the function :func:`print`, since a colon (``':'``) is missing before it. File name and line number are printed so you know where to look in case the input came from a script. diff --git a/Doc/tutorial/introduction.rst b/Doc/tutorial/introduction.rst index 4536ab9486d39cf..0f16dae8b1418fd 100644 --- a/Doc/tutorial/introduction.rst +++ b/Doc/tutorial/introduction.rst @@ -405,13 +405,6 @@ indexed and sliced:: >>> squares[-3:] # slicing returns a new list [9, 16, 25] -All slice operations return a new list containing the requested elements. This -means that the following slice returns a -:ref:`shallow copy ` of the list:: - - >>> squares[:] - [1, 4, 9, 16, 25] - Lists also support operations like concatenation:: >>> squares + [36, 49, 64, 81, 100] @@ -435,6 +428,30 @@ the :meth:`!list.append` *method* (we will see more about methods later):: >>> cubes [1, 8, 27, 64, 125, 216, 343] +Simple assignment in Python never copies data. When you assign a list +to a variable, the variable refers to the *existing list*. +Any changes you make to the list through one variable will be seen +through all other variables that refer to it.:: + + >>> rgb = ["Red", "Green", "Blue"] + >>> rgba = rgb + >>> id(rgb) == id(rgba) # they reference the same object + True + >>> rgba.append("Alph") + >>> rgb + ["Red", "Green", "Blue", "Alph"] + +All slice operations return a new list containing the requested elements. This +means that the following slice returns a +:ref:`shallow copy ` of the list:: + + >>> correct_rgba = rgba[:] + >>> correct_rgba[-1] = "Alpha" + >>> correct_rgba + ["Red", "Green", "Blue", "Alpha"] + >>> rgba + ["Red", "Green", "Blue", "Alph"] + Assignment to slices is also possible, and this can even change the size of the list or clear it entirely:: diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index 53c95ca1a05c9b4..565d86cb1a0dd39 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -242,12 +242,13 @@ Miscellaneous options .. option:: -b - Issue a warning when comparing :class:`bytes` or :class:`bytearray` with - :class:`str` or :class:`bytes` with :class:`int`. Issue an error when the - option is given twice (:option:`!-bb`). + Issue a warning when converting :class:`bytes` or :class:`bytearray` to + :class:`str` without specifying encoding or comparing :class:`!bytes` or + :class:`!bytearray` with :class:`!str` or :class:`!bytes` with :class:`int`. + Issue an error when the option is given twice (:option:`!-bb`). .. versionchanged:: 3.5 - Affects comparisons of :class:`bytes` with :class:`int`. + Affects also comparisons of :class:`bytes` with :class:`int`. .. option:: -B @@ -375,17 +376,19 @@ Miscellaneous options :envvar:`PYTHONHASHSEED` allows you to set a fixed value for the hash seed secret. + .. versionadded:: 3.2.3 + .. versionchanged:: 3.7 The option is no longer ignored. - .. versionadded:: 3.2.3 - .. option:: -s Don't add the :data:`user site-packages directory ` to :data:`sys.path`. + See also :envvar:`PYTHONNOUSERSITE`. + .. seealso:: :pep:`370` -- Per user site-packages directory @@ -517,7 +520,7 @@ Miscellaneous options asyncio'``. See also :envvar:`PYTHONPROFILEIMPORTTIME`. * ``-X dev``: enable :ref:`Python Development Mode `, introducing additional runtime checks that are too expensive to be enabled by - default. + default. See also :envvar:`PYTHONDEVMODE`. * ``-X utf8`` enables the :ref:`Python UTF-8 Mode `. ``-X utf8=0`` explicitly disables :ref:`Python UTF-8 Mode ` (even when it would otherwise activate automatically). @@ -559,27 +562,29 @@ Miscellaneous options :mod:`__main__`. This can be used to execute code early during Python initialization. Python needs to be :ref:`built in debug mode ` for this option to exist. See also :envvar:`PYTHON_PRESITE`. + * :samp:`-X gil={0,1}` forces the GIL to be disabled or enabled, + respectively. Only available in builds configured with + :option:`--disable-gil`. See also :envvar:`PYTHON_GIL`. It also allows passing arbitrary values and retrieving them through the :data:`sys._xoptions` dictionary. - .. versionchanged:: 3.2 - The :option:`-X` option was added. + .. versionadded:: 3.2 - .. versionadded:: 3.3 - The ``-X faulthandler`` option. + .. versionchanged:: 3.3 + Added the ``-X faulthandler`` option. - .. versionadded:: 3.4 - The ``-X showrefcount`` and ``-X tracemalloc`` options. + .. versionchanged:: 3.4 + Added the ``-X showrefcount`` and ``-X tracemalloc`` options. - .. versionadded:: 3.6 - The ``-X showalloccount`` option. + .. versionchanged:: 3.6 + Added the ``-X showalloccount`` option. - .. versionadded:: 3.7 - The ``-X importtime``, ``-X dev`` and ``-X utf8`` options. + .. versionchanged:: 3.7 + Added the ``-X importtime``, ``-X dev`` and ``-X utf8`` options. - .. versionadded:: 3.8 - The ``-X pycache_prefix`` option. The ``-X dev`` option now logs + .. versionchanged:: 3.8 + Added the ``-X pycache_prefix`` option. The ``-X dev`` option now logs ``close()`` exceptions in :class:`io.IOBase` destructor. .. versionchanged:: 3.9 @@ -588,27 +593,22 @@ Miscellaneous options The ``-X showalloccount`` option has been removed. - .. versionadded:: 3.10 - The ``-X warn_default_encoding`` option. + .. versionchanged:: 3.10 + Added the ``-X warn_default_encoding`` option. Removed the ``-X oldparser`` option. - .. versionadded:: 3.11 - The ``-X no_debug_ranges`` option. - - .. versionadded:: 3.11 - The ``-X frozen_modules`` option. + .. versionchanged:: 3.11 + Added the ``-X no_debug_ranges``, ``-X frozen_modules`` and + ``-X int_max_str_digits`` options. - .. versionadded:: 3.11 - The ``-X int_max_str_digits`` option. + .. versionchanged:: 3.12 + Added the ``-X perf`` option. - .. versionadded:: 3.12 - The ``-X perf`` option. + .. versionchanged:: 3.13 + Added the ``-X cpu_count`` and ``-X presite`` options. - .. versionadded:: 3.13 - The ``-X cpu_count`` option. - - .. versionadded:: 3.13 - The ``-X presite`` option. + .. versionchanged:: 3.13 + Added the ``-X gil`` option. .. _using-on-controlling-color: @@ -955,11 +955,11 @@ conflict. * ``pymalloc_debug``: same as ``pymalloc`` but also install debug hooks. * ``mimalloc_debug``: same as ``mimalloc`` but also install debug hooks. + .. versionadded:: 3.6 + .. versionchanged:: 3.7 Added the ``"default"`` allocator. - .. versionadded:: 3.6 - .. envvar:: PYTHONMALLOCSTATS @@ -1147,6 +1147,18 @@ conflict. .. versionadded:: 3.13 +.. envvar:: PYTHON_GIL + + If this variable is set to ``1``, the global interpreter lock (GIL) will be + forced on. Setting it to ``0`` forces the GIL off. + + See also the :option:`-X gil <-X>` command-line option, which takes + precedence over this variable. + + Needs Python configured with the :option:`--disable-gil` build option. + + .. versionadded:: 3.13 + Debug-mode variables ~~~~~~~~~~~~~~~~~~~~ diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index aab9469b44828a0..3db309539d2368d 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst @@ -2,6 +2,8 @@ Configure Python **************** +.. highlight:: sh + Build Requirements ================== @@ -30,31 +32,31 @@ Features and minimum versions required to build CPython: * Autoconf 2.71 and aclocal 1.16.4 are required to regenerate the :file:`configure` script. -.. versionchanged:: 3.13: - Autoconf 2.71, aclocal 1.16.4 and SQLite 3.15.2 are now required. +.. versionchanged:: 3.1 + Tcl/Tk version 8.3.1 is now required. -.. versionchanged:: 3.11 - C11 compiler, IEEE 754 and NaN support are now required. - On Windows, Visual Studio 2017 or later is required. - Tcl/Tk version 8.5.12 is now required for the :mod:`tkinter` module. +.. versionchanged:: 3.5 + On Windows, Visual Studio 2015 or later is now required. + Tcl/Tk version 8.4 is now required. -.. versionchanged:: 3.10 - OpenSSL 1.1.1 is now required. - Require SQLite 3.7.15. +.. versionchanged:: 3.6 + Selected C99 features are now required, like ```` and ``static + inline`` functions. .. versionchanged:: 3.7 Thread support and OpenSSL 1.0.2 are now required. -.. versionchanged:: 3.6 - Selected C99 features are now required, like ```` and ``static - inline`` functions. +.. versionchanged:: 3.10 + OpenSSL 1.1.1 is now required. + Require SQLite 3.7.15. -.. versionchanged:: 3.5 - On Windows, Visual Studio 2015 or later is now required. - Tcl/Tk version 8.4 is now required. +.. versionchanged:: 3.11 + C11 compiler, IEEE 754 and NaN support are now required. + On Windows, Visual Studio 2017 or later is required. + Tcl/Tk version 8.5.12 is now required for the :mod:`tkinter` module. -.. versionchanged:: 3.1 - Tcl/Tk version 8.3.1 is now required. +.. versionchanged:: 3.13 + Autoconf 2.71, aclocal 1.16.4 and SQLite 3.15.2 are now required. See also :pep:`7` "Style Guide for C Code" and :pep:`11` "CPython platform support". @@ -275,7 +277,7 @@ General Options * to/from free lists; * dictionary materialized/dematerialized; * type cache; - * optimization attemps; + * optimization attempts; * optimization traces created/executed; * uops executed. @@ -694,12 +696,12 @@ Debug options :ref:`Statically allocated objects ` are not traced. + .. versionadded:: 3.8 + .. versionchanged:: 3.13 This build is now ABI compatible with release build and :ref:`debug build `. - .. versionadded:: 3.8 - .. option:: --with-assertions Build with C assertions enabled (default is no): ``assert(...);`` and @@ -941,7 +943,9 @@ the version of the cross compiled host Python. An environment variable that points to a file with configure overrides. - Example *config.site* file:: + Example *config.site* file: + + .. code-block:: ini # config.site-aarch64 ac_cv_buggy_getaddrinfo=no @@ -987,39 +991,108 @@ Main build steps Main Makefile targets --------------------- -* ``make``: Build Python with the standard library. -* ``make platform:``: build the ``python`` program, but don't build the - standard library extension modules. -* ``make profile-opt``: build Python using Profile Guided Optimization (PGO). - You can use the configure :option:`--enable-optimizations` option to make - this the default target of the ``make`` command (``make all`` or just - ``make``). - -* ``make test``: Build Python and run the Python test suite with ``--fast-ci`` - option. Variables: - - * ``TESTOPTS``: additional regrtest command line options. - * ``TESTPYTHONOPTS``: additional Python command line options. - * ``TESTTIMEOUT``: timeout in seconds (default: 20 minutes). - -* ``make buildbottest``: Similar to ``make test``, but use ``--slow-ci`` - option and default timeout of 20 minutes, instead of ``--fast-ci`` option - and a default timeout of 10 minutes. - -* ``make install``: Build and install Python. -* ``make regen-all``: Regenerate (almost) all generated files; - ``make regen-stdlib-module-names`` and ``autoconf`` must be run separately - for the remaining generated files. -* ``make clean``: Remove built files. -* ``make distclean``: Same than ``make clean``, but remove also files created - by the configure script. +make +^^^^ + +For the most part, when rebuilding after editing some code or +refreshing your checkout from upstream, all you need to do is execute +``make``, which (per Make's semantics) builds the default target, the +first one defined in the Makefile. By tradition (including in the +CPython project) this is usually the ``all`` target. The +``configure`` script expands an ``autoconf`` variable, +``@DEF_MAKE_ALL_RULE@`` to describe precisely which targets ``make +all`` will build. The three choices are: + +* ``profile-opt`` (configured with ``--enable-optimizations``) +* ``build_wasm`` (configured with ``--with-emscripten-target``) +* ``build_all`` (configured without explicitly using either of the others) + +Depending on the most recent source file changes, Make will rebuild +any targets (object files and executables) deemed out-of-date, +including running ``configure`` again if necessary. Source/target +dependencies are many and maintained manually however, so Make +sometimes doesn't have all the information necessary to correctly +detect all targets which need to be rebuilt. Depending on which +targets aren't rebuilt, you might experience a number of problems. If +you have build or test problems which you can't otherwise explain, +``make clean && make`` should work around most dependency problems, at +the expense of longer build times. + + +make platform +^^^^^^^^^^^^^ + +Build the ``python`` program, but don't build the standard library +extension modules. This generates a file named ``platform`` which +contains a single line describing the details of the build platform, +e.g., ``macosx-14.3-arm64-3.12`` or ``linux-x86_64-3.13``. + + +make profile-opt +^^^^^^^^^^^^^^^^ + +Build Python using profile-guided optimization (PGO). You can use the +configure :option:`--enable-optimizations` option to make this the +default target of the ``make`` command (``make all`` or just +``make``). + + + +make clean +^^^^^^^^^^ + +Remove built files. + + +make distclean +^^^^^^^^^^^^^^ + +In addition to the the work done by ``make clean``, remove files +created by the configure script. ``configure`` will have to be run +before building again. [#]_ + + +make install +^^^^^^^^^^^^ + +Build the ``all`` target and install Python. + + +make test +^^^^^^^^^ + +Build the ``all`` target and run the Python test suite with the +``--fast-ci`` option. Variables: + +* ``TESTOPTS``: additional regrtest command-line options. +* ``TESTPYTHONOPTS``: additional Python command-line options. +* ``TESTTIMEOUT``: timeout in seconds (default: 10 minutes). + + +make buildbottest +^^^^^^^^^^^^^^^^^ + +This is similar to ``make test``, but uses the ``--slow-ci`` +option and default timeout of 20 minutes, instead of ``--fast-ci`` option. + + +make regen-all +^^^^^^^^^^^^^^ + +Regenerate (almost) all generated files. These include (but are not +limited to) bytecode cases, and parser generator file. +``make regen-stdlib-module-names`` and ``autoconf`` must be run +separately for the remaining `generated files <#generated-files>`_. + C extensions ------------ Some C extensions are built as built-in modules, like the ``sys`` module. They are built with the ``Py_BUILD_CORE_BUILTIN`` macro defined. -Built-in modules have no ``__file__`` attribute:: +Built-in modules have no ``__file__`` attribute: + +.. code-block:: pycon >>> import sys >>> sys @@ -1031,7 +1104,9 @@ Built-in modules have no ``__file__`` attribute:: Other C extensions are built as dynamic libraries, like the ``_asyncio`` module. They are built with the ``Py_BUILD_CORE_MODULE`` macro defined. -Example on Linux x86-64:: +Example on Linux x86-64: + +.. code-block:: pycon >>> import _asyncio >>> _asyncio @@ -1303,3 +1378,14 @@ Linker flags Linker flags used for building the interpreter object files. .. versionadded:: 3.8 + + +.. rubric:: Footnotes + +.. [#] ``git clean -fdx`` is an even more extreme way to "clean" your + checkout. It removes all files not known to Git. + When bug hunting using ``git bisect``, this is + `recommended between probes `_ + to guarantee a completely clean build. **Use with care**, as it + will delete all files not checked into Git, including your + new, uncommitted work. diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst index bfcf1c6882f29d4..cc4db34b04d900b 100644 --- a/Doc/using/windows.rst +++ b/Doc/using/windows.rst @@ -14,7 +14,7 @@ know about when using Python on Microsoft Windows. Unlike most Unix systems and services, Windows does not include a system supported installation of Python. To make Python available, the CPython team -has compiled Windows installers (MSI packages) with every `release +has compiled Windows installers with every `release `_ for many years. These installers are primarily intended to add a per-user installation of Python, with the core interpreter and library being used by a single user. The installer is also diff --git a/Doc/whatsnew/2.0.rst b/Doc/whatsnew/2.0.rst index af8171487fbcfa2..1a949ec40358072 100644 --- a/Doc/whatsnew/2.0.rst +++ b/Doc/whatsnew/2.0.rst @@ -217,13 +217,13 @@ often use the ``codecs.lookup(encoding)`` function, which returns a was consumed. * *stream_reader* is a class that supports decoding input from a stream. - *stream_reader(file_obj)* returns an object that supports the :meth:`read`, - :meth:`readline`, and :meth:`readlines` methods. These methods will all + *stream_reader(file_obj)* returns an object that supports the :meth:`!read`, + :meth:`!readline`, and :meth:`!readlines` methods. These methods will all translate from the given encoding and return Unicode strings. * *stream_writer*, similarly, is a class that supports encoding output to a stream. *stream_writer(file_obj)* returns an object that supports the - :meth:`write` and :meth:`writelines` methods. These methods expect Unicode + :meth:`!write` and :meth:`!writelines` methods. These methods expect Unicode strings, translating them to the given encoding on output. For example, the following code writes a Unicode string into a file, encoding @@ -356,8 +356,8 @@ variable ``a`` by 2, equivalent to the slightly lengthier ``a = a + 2``. The full list of supported assignment operators is ``+=``, ``-=``, ``*=``, ``/=``, ``%=``, ``**=``, ``&=``, ``|=``, ``^=``, ``>>=``, and ``<<=``. Python classes can override the augmented assignment operators by defining methods -named :meth:`__iadd__`, :meth:`__isub__`, etc. For example, the following -:class:`Number` class stores a number and supports using += to create a new +named :meth:`!__iadd__`, :meth:`!__isub__`, etc. For example, the following +:class:`!Number` class stores a number and supports using += to create a new instance with an incremented value. .. The empty groups below prevent conversion to guillemets. @@ -374,7 +374,7 @@ instance with an incremented value. n += 3 print n.value -The :meth:`__iadd__` special method is called with the value of the increment, +The :meth:`!__iadd__` special method is called with the value of the increment, and should return a new instance with an appropriately modified value; this return value is bound as the new value of the variable on the left-hand side. @@ -390,10 +390,10 @@ String Methods ============== Until now string-manipulation functionality was in the :mod:`string` module, -which was usually a front-end for the :mod:`strop` module written in C. The -addition of Unicode posed a difficulty for the :mod:`strop` module, because the +which was usually a front-end for the :mod:`!strop` module written in C. The +addition of Unicode posed a difficulty for the :mod:`!strop` module, because the functions would all need to be rewritten in order to accept either 8-bit or -Unicode strings. For functions such as :func:`string.replace`, which takes 3 +Unicode strings. For functions such as :func:`!string.replace`, which takes 3 string arguments, that means eight possible permutations, and correspondingly complicated code. @@ -416,13 +416,13 @@ The old :mod:`string` module is still around for backwards compatibility, but it mostly acts as a front-end to the new string methods. Two methods which have no parallel in pre-2.0 versions, although they did exist -in JPython for quite some time, are :meth:`startswith` and :meth:`endswith`. +in JPython for quite some time, are :meth:`!startswith` and :meth:`!endswith`. ``s.startswith(t)`` is equivalent to ``s[:len(t)] == t``, while ``s.endswith(t)`` is equivalent to ``s[-len(t):] == t``. -One other method which deserves special mention is :meth:`join`. The -:meth:`join` method of a string receives one parameter, a sequence of strings, -and is equivalent to the :func:`string.join` function from the old :mod:`string` +One other method which deserves special mention is :meth:`!join`. The +:meth:`!join` method of a string receives one parameter, a sequence of strings, +and is equivalent to the :func:`!string.join` function from the old :mod:`string` module, with the arguments reversed. In other words, ``s.join(seq)`` is equivalent to the old ``string.join(seq, s)``. @@ -503,9 +503,9 @@ Minor Language Changes A new syntax makes it more convenient to call a given function with a tuple of arguments and/or a dictionary of keyword arguments. In Python 1.5 and earlier, -you'd use the :func:`apply` built-in function: ``apply(f, args, kw)`` calls the -function :func:`f` with the argument tuple *args* and the keyword arguments in -the dictionary *kw*. :func:`apply` is the same in 2.0, but thanks to a patch +you'd use the :func:`!apply` built-in function: ``apply(f, args, kw)`` calls the +function :func:`!f` with the argument tuple *args* and the keyword arguments in +the dictionary *kw*. :func:`!apply` is the same in 2.0, but thanks to a patch from Greg Ewing, ``f(*args, **kw)`` is a shorter and clearer way to achieve the same effect. This syntax is symmetrical with the syntax for defining functions:: @@ -518,7 +518,7 @@ functions:: The ``print`` statement can now have its output directed to a file-like object by following the ``print`` with ``>> file``, similar to the redirection operator in Unix shells. Previously you'd either have to use the -:meth:`write` method of the file-like object, which lacks the convenience and +:meth:`!write` method of the file-like object, which lacks the convenience and simplicity of ``print``, or you could assign a new value to ``sys.stdout`` and then restore the old value. For sending output to standard error, it's much easier to write this:: @@ -540,7 +540,7 @@ Previously there was no way to implement a class that overrode Python's built-in true if *obj* is present in the sequence *seq*; Python computes this by simply trying every index of the sequence until either *obj* is found or an :exc:`IndexError` is encountered. Moshe Zadka contributed a patch which adds a -:meth:`__contains__` magic method for providing a custom implementation for +:meth:`!__contains__` magic method for providing a custom implementation for :keyword:`!in`. Additionally, new built-in objects written in C can define what :keyword:`!in` means for them via a new slot in the sequence protocol. @@ -562,7 +562,7 @@ the python-dev mailing list for the discussion leading up to this implementation, and some useful relevant links. Note that comparisons can now also raise exceptions. In earlier versions of Python, a comparison operation such as ``cmp(a,b)`` would always produce an answer, even if a user-defined -:meth:`__cmp__` method encountered an error, since the resulting exception would +:meth:`!__cmp__` method encountered an error, since the resulting exception would simply be silently swallowed. .. Starting URL: @@ -607,7 +607,7 @@ seq1, seq2)`` is that :func:`map` pads the sequences with ``None`` if the sequences aren't all of the same length, while :func:`zip` truncates the returned list to the length of the shortest argument sequence. -The :func:`int` and :func:`long` functions now accept an optional "base" +The :func:`int` and :func:`!long` functions now accept an optional "base" parameter when the first argument is a string. ``int('123', 10)`` returns 123, while ``int('123', 16)`` returns 291. ``int(123, 16)`` raises a :exc:`TypeError` exception with the message "can't convert non-string with @@ -620,8 +620,8 @@ would be ``(2, 0, 1, 'beta', 1)``. *level* is a string such as ``"alpha"``, ``"beta"``, or ``"final"`` for a final release. Dictionaries have an odd new method, ``setdefault(key, default)``, which -behaves similarly to the existing :meth:`get` method. However, if the key is -missing, :meth:`setdefault` both returns the value of *default* as :meth:`get` +behaves similarly to the existing :meth:`!get` method. However, if the key is +missing, :meth:`!setdefault` both returns the value of *default* as :meth:`!get` would do, and also inserts it into the dictionary as the value for *key*. Thus, the following lines of code:: @@ -656,7 +656,7 @@ break. The change which will probably break the most code is tightening up the arguments accepted by some methods. Some methods would take multiple arguments and treat them as a tuple, particularly various list methods such as -:meth:`append` and :meth:`insert`. In earlier versions of Python, if ``L`` is +:meth:`!append` and :meth:`!insert`. In earlier versions of Python, if ``L`` is a list, ``L.append( 1,2 )`` appends the tuple ``(1,2)`` to the list. In Python 2.0 this causes a :exc:`TypeError` exception to be raised, with the message: 'append requires exactly 1 argument; 2 given'. The fix is to simply add an @@ -693,7 +693,7 @@ advantage of this fact will break in 2.0. Some work has been done to make integers and long integers a bit more interchangeable. In 1.5.2, large-file support was added for Solaris, to allow -reading files larger than 2 GiB; this made the :meth:`tell` method of file +reading files larger than 2 GiB; this made the :meth:`!tell` method of file objects return a long integer instead of a regular integer. Some code would subtract two file offsets and attempt to use the result to multiply a sequence or slice a string, but this raised a :exc:`TypeError`. In 2.0, long integers @@ -701,7 +701,7 @@ can be used to multiply or slice a sequence, and it'll behave as you'd intuitively expect it to; ``3L * 'abc'`` produces 'abcabcabc', and ``(0,1,2,3)[2L:4L]`` produces (2,3). Long integers can also be used in various contexts where previously only integers were accepted, such as in the -:meth:`seek` method of file objects, and in the formats supported by the ``%`` +:meth:`!seek` method of file objects, and in the formats supported by the ``%`` operator (``%d``, ``%i``, ``%x``, etc.). For example, ``"%d" % 2L**64`` will produce the string ``18446744073709551616``. @@ -715,7 +715,7 @@ digit. Taking the :func:`repr` of a float now uses a different formatting precision than :func:`str`. :func:`repr` uses ``%.17g`` format string for C's -:func:`sprintf`, while :func:`str` uses ``%.12g`` as before. The effect is that +:func:`!sprintf`, while :func:`str` uses ``%.12g`` as before. The effect is that :func:`repr` may occasionally show more decimal places than :func:`str`, for certain numbers. For example, the number 8.1 can't be represented exactly in binary, so ``repr(8.1)`` is ``'8.0999999999999996'``, while str(8.1) is @@ -723,7 +723,7 @@ binary, so ``repr(8.1)`` is ``'8.0999999999999996'``, while str(8.1) is The ``-X`` command-line option, which turned all standard exceptions into strings instead of classes, has been removed; the standard exceptions will now -always be classes. The :mod:`exceptions` module containing the standard +always be classes. The :mod:`!exceptions` module containing the standard exceptions was translated from Python to a built-in C module, written by Barry Warsaw and Fredrik Lundh. @@ -879,11 +879,11 @@ joins the basic set of Python documentation. XML Modules =========== -Python 1.5.2 included a simple XML parser in the form of the :mod:`xmllib` +Python 1.5.2 included a simple XML parser in the form of the :mod:`!xmllib` module, contributed by Sjoerd Mullender. Since 1.5.2's release, two different interfaces for processing XML have become common: SAX2 (version 2 of the Simple API for XML) provides an event-driven interface with some similarities to -:mod:`xmllib`, and the DOM (Document Object Model) provides a tree-based +:mod:`!xmllib`, and the DOM (Document Object Model) provides a tree-based interface, transforming an XML document into a tree of nodes that can be traversed and modified. Python 2.0 includes a SAX2 interface and a stripped-down DOM interface as part of the :mod:`xml` package. Here we will give a brief @@ -898,9 +898,9 @@ SAX2 Support SAX defines an event-driven interface for parsing XML. To use SAX, you must write a SAX handler class. Handler classes inherit from various classes provided by SAX, and override various methods that will then be called by the -XML parser. For example, the :meth:`startElement` and :meth:`endElement` +XML parser. For example, the :meth:`~xml.sax.handler.ContentHandler.startElement` and :meth:`~xml.sax.handler.ContentHandler.endElement` methods are called for every starting and end tag encountered by the parser, the -:meth:`characters` method is called for every chunk of character data, and so +:meth:`~xml.sax.handler.ContentHandler.characters` method is called for every chunk of character data, and so forth. The advantage of the event-driven approach is that the whole document doesn't @@ -940,8 +940,8 @@ DOM Support ----------- The Document Object Model is a tree-based representation for an XML document. A -top-level :class:`Document` instance is the root of the tree, and has a single -child which is the top-level :class:`Element` instance. This :class:`Element` +top-level :class:`!Document` instance is the root of the tree, and has a single +child which is the top-level :class:`!Element` instance. This :class:`!Element` has children nodes representing character data and any sub-elements, which may have further children of their own, and so forth. Using the DOM you can traverse the resulting tree any way you like, access element and attribute @@ -955,18 +955,18 @@ simply writing ````...\ ```` to a file. The DOM implementation included with Python lives in the :mod:`xml.dom.minidom` module. It's a lightweight implementation of the Level 1 DOM with support for -XML namespaces. The :func:`parse` and :func:`parseString` convenience +XML namespaces. The :func:`!parse` and :func:`!parseString` convenience functions are provided for generating a DOM tree:: from xml.dom import minidom doc = minidom.parse('hamlet.xml') -``doc`` is a :class:`Document` instance. :class:`Document`, like all the other -DOM classes such as :class:`Element` and :class:`Text`, is a subclass of the -:class:`Node` base class. All the nodes in a DOM tree therefore support certain -common methods, such as :meth:`toxml` which returns a string containing the XML +``doc`` is a :class:`!Document` instance. :class:`!Document`, like all the other +DOM classes such as :class:`!Element` and :class:`Text`, is a subclass of the +:class:`!Node` base class. All the nodes in a DOM tree therefore support certain +common methods, such as :meth:`!toxml` which returns a string containing the XML representation of the node and its children. Each class also has special -methods of its own; for example, :class:`Element` and :class:`Document` +methods of its own; for example, :class:`!Element` and :class:`!Document` instances have a method to find all child elements with a given tag name. Continuing from the previous 2-line example:: @@ -995,7 +995,7 @@ its children can be easily modified by deleting, adding, or removing nodes:: root.insertBefore( root.childNodes[0], root.childNodes[20] ) Again, I will refer you to the Python documentation for a complete listing of -the different :class:`Node` classes and their various methods. +the different :class:`!Node` classes and their various methods. Relationship to PyXML @@ -1020,7 +1020,7 @@ features in PyXML include: * The xmlproc validating parser, written by Lars Marius Garshol. -* The :mod:`sgmlop` parser accelerator module, written by Fredrik Lundh. +* The :mod:`!sgmlop` parser accelerator module, written by Fredrik Lundh. .. ====================================================================== @@ -1031,7 +1031,7 @@ Module changes Lots of improvements and bugfixes were made to Python's extensive standard library; some of the affected modules include :mod:`readline`, :mod:`ConfigParser `, :mod:`!cgi`, :mod:`calendar`, :mod:`posix`, :mod:`readline`, -:mod:`xmllib`, :mod:`!aifc`, :mod:`!chunk`, :mod:`wave`, :mod:`random`, :mod:`shelve`, +:mod:`!xmllib`, :mod:`!aifc`, :mod:`!chunk`, :mod:`wave`, :mod:`random`, :mod:`shelve`, and :mod:`!nntplib`. Consult the CVS logs for the exact patch-by-patch details. Brian Gallew contributed OpenSSL support for the :mod:`socket` module. OpenSSL @@ -1044,11 +1044,12 @@ were also changed to support ``https://`` URLs, though no one has implemented FTP or SMTP over SSL. The :mod:`httplib ` module has been rewritten by Greg Stein to support HTTP/1.1. + Backward compatibility with the 1.5 version of :mod:`!httplib` is provided, though using HTTP/1.1 features such as pipelining will require rewriting code to use a different set of interfaces. -The :mod:`Tkinter` module now supports Tcl/Tk version 8.1, 8.2, or 8.3, and +The :mod:`!Tkinter` module now supports Tcl/Tk version 8.1, 8.2, or 8.3, and support for the older 7.x versions has been dropped. The Tkinter module now supports displaying Unicode strings in Tk widgets. Also, Fredrik Lundh contributed an optimization which makes operations like ``create_line`` and @@ -1083,11 +1084,11 @@ module. calling :func:`atexit.register` with the function to be called on exit. (Contributed by Skip Montanaro.) -* :mod:`codecs`, :mod:`encodings`, :mod:`unicodedata`: Added as part of the new +* :mod:`codecs`, :mod:`!encodings`, :mod:`unicodedata`: Added as part of the new Unicode support. -* :mod:`filecmp`: Supersedes the old :mod:`cmp`, :mod:`cmpcache` and - :mod:`dircmp` modules, which have now become deprecated. (Contributed by Gordon +* :mod:`filecmp`: Supersedes the old :mod:`!cmp`, :mod:`!cmpcache` and + :mod:`!dircmp` modules, which have now become deprecated. (Contributed by Gordon MacMillan and Moshe Zadka.) * :mod:`gettext`: This module provides internationalization (I18N) and @@ -1105,7 +1106,7 @@ module. be passed to functions that expect ordinary strings, such as the :mod:`re` module. (Contributed by Sam Rushing, with some extensions by A.M. Kuchling.) -* :mod:`pyexpat`: An interface to the Expat XML parser. (Contributed by Paul +* :mod:`!pyexpat`: An interface to the Expat XML parser. (Contributed by Paul Prescod.) * :mod:`robotparser `: Parse a :file:`robots.txt` file, which is used for writing @@ -1117,7 +1118,7 @@ module. * :mod:`tabnanny`: A module/script to check Python source code for ambiguous indentation. (Contributed by Tim Peters.) -* :mod:`UserString`: A base class useful for deriving objects that behave like +* :mod:`!UserString`: A base class useful for deriving objects that behave like strings. * :mod:`webbrowser`: A module that provides a platform independent way to launch @@ -1184,13 +1185,13 @@ Deleted and Deprecated Modules ============================== A few modules have been dropped because they're obsolete, or because there are -now better ways to do the same thing. The :mod:`stdwin` module is gone; it was +now better ways to do the same thing. The :mod:`!stdwin` module is gone; it was for a platform-independent windowing toolkit that's no longer developed. A number of modules have been moved to the :file:`lib-old` subdirectory: -:mod:`cmp`, :mod:`cmpcache`, :mod:`dircmp`, :mod:`dump`, :mod:`find`, -:mod:`grep`, :mod:`packmail`, :mod:`poly`, :mod:`util`, :mod:`whatsound`, -:mod:`zmod`. If you have code which relies on a module that's been moved to +:mod:`!cmp`, :mod:`!cmpcache`, :mod:`!dircmp`, :mod:`!dump`, :mod:`!find`, +:mod:`!grep`, :mod:`!packmail`, :mod:`!poly`, :mod:`!util`, :mod:`!whatsound`, +:mod:`!zmod`. If you have code which relies on a module that's been moved to :file:`lib-old`, you can simply add that directory to ``sys.path`` to get them back, but you're encouraged to update any code that uses these modules. diff --git a/Doc/whatsnew/2.1.rst b/Doc/whatsnew/2.1.rst index 6d2d3cc02b87689..b4002f06e92adc2 100644 --- a/Doc/whatsnew/2.1.rst +++ b/Doc/whatsnew/2.1.rst @@ -48,7 +48,7 @@ nested recursive function definition doesn't work:: return g(value-1) + 1 ... -The function :func:`g` will always raise a :exc:`NameError` exception, because +The function :func:`!g` will always raise a :exc:`NameError` exception, because the binding of the name ``g`` isn't in either its local namespace or in the module-level namespace. This isn't much of a problem in practice (how often do you recursively define interior functions like this?), but this also made using @@ -104,7 +104,7 @@ To make the preceding explanation a bit clearer, here's an example:: Line 4 containing the ``exec`` statement is a syntax error, since ``exec`` would define a new local variable named ``x`` whose value should -be accessed by :func:`g`. +be accessed by :func:`!g`. This shouldn't be much of a limitation, since ``exec`` is rarely used in most Python code (and when it is used, it's often a sign of a poor design @@ -161,7 +161,7 @@ PEP 207: Rich Comparisons In earlier versions, Python's support for implementing comparisons on user-defined classes and extension types was quite simple. Classes could implement a -:meth:`__cmp__` method that was given two instances of a class, and could only +:meth:`!__cmp__` method that was given two instances of a class, and could only return 0 if they were equal or +1 or -1 if they weren't; the method couldn't raise an exception or return anything other than a Boolean value. Users of Numeric Python often found this model too weak and restrictive, because in the @@ -175,21 +175,21 @@ In Python 2.1, rich comparisons were added in order to support this need. Python classes can now individually overload each of the ``<``, ``<=``, ``>``, ``>=``, ``==``, and ``!=`` operations. The new magic method names are: -+-----------+----------------+ -| Operation | Method name | -+===========+================+ -| ``<`` | :meth:`__lt__` | -+-----------+----------------+ -| ``<=`` | :meth:`__le__` | -+-----------+----------------+ -| ``>`` | :meth:`__gt__` | -+-----------+----------------+ -| ``>=`` | :meth:`__ge__` | -+-----------+----------------+ -| ``==`` | :meth:`__eq__` | -+-----------+----------------+ -| ``!=`` | :meth:`__ne__` | -+-----------+----------------+ ++-----------+------------------------+ +| Operation | Method name | ++===========+========================+ +| ``<`` | :meth:`~object.__lt__` | ++-----------+------------------------+ +| ``<=`` | :meth:`~object.__le__` | ++-----------+------------------------+ +| ``>`` | :meth:`~object.__gt__` | ++-----------+------------------------+ +| ``>=`` | :meth:`~object.__ge__` | ++-----------+------------------------+ +| ``==`` | :meth:`~object.__eq__` | ++-----------+------------------------+ +| ``!=`` | :meth:`~object.__ne__` | ++-----------+------------------------+ (The magic methods are named after the corresponding Fortran operators ``.LT.``. ``.LE.``, &c. Numeric programmers are almost certainly quite familiar with @@ -208,7 +208,7 @@ The built-in ``cmp(A,B)`` function can use the rich comparison machinery, and now accepts an optional argument specifying which comparison operation to use; this is given as one of the strings ``"<"``, ``"<="``, ``">"``, ``">="``, ``"=="``, or ``"!="``. If called without the optional third argument, -:func:`cmp` will only return -1, 0, or +1 as in previous versions of Python; +:func:`!cmp` will only return -1, 0, or +1 as in previous versions of Python; otherwise it will call the appropriate method and can return any Python object. There are also corresponding changes of interest to C programmers; there's a new @@ -245,7 +245,7 @@ out warnings that you don't want to be displayed. Third-party modules can also use this framework to deprecate old features that they no longer wish to support. -For example, in Python 2.1 the :mod:`regex` module is deprecated, so importing +For example, in Python 2.1 the :mod:`!regex` module is deprecated, so importing it causes a warning to be printed:: >>> import regex @@ -262,7 +262,7 @@ can be used to specify a particular warning category. Filters can be added to disable certain warnings; a regular expression pattern can be applied to the message or to the module name in order to suppress a -warning. For example, you may have a program that uses the :mod:`regex` module +warning. For example, you may have a program that uses the :mod:`!regex` module and not want to spare the time to convert it to use the :mod:`re` module right now. The warning can be suppressed by calling :: @@ -274,7 +274,7 @@ now. The warning can be suppressed by calling :: This adds a filter that will apply only to warnings of the class :class:`DeprecationWarning` triggered in the :mod:`__main__` module, and applies -a regular expression to only match the message about the :mod:`regex` module +a regular expression to only match the message about the :mod:`!regex` module being deprecated, and will cause such warnings to be ignored. Warnings can also be printed only once, printed every time the offending code is executed, or turned into exceptions that will cause the program to stop (unless the @@ -368,7 +368,7 @@ dictionary:: This version works for simple things such as integers, but it has a side effect; the ``_cache`` dictionary holds a reference to the return values, so they'll never be deallocated until the Python process exits and cleans up. This isn't -very noticeable for integers, but if :func:`f` returns an object, or a data +very noticeable for integers, but if :func:`!f` returns an object, or a data structure that takes up a lot of memory, this can be a problem. Weak references provide a way to implement a cache that won't keep objects alive @@ -379,7 +379,7 @@ created by calling ``wr = weakref.ref(obj)``. The object being referred to is returned by calling the weak reference as if it were a function: ``wr()``. It will return the referenced object, or ``None`` if the object no longer exists. -This makes it possible to write a :func:`memoize` function whose cache doesn't +This makes it possible to write a :func:`!memoize` function whose cache doesn't keep objects alive, by storing weak references in the cache. :: _cache = {} @@ -402,7 +402,7 @@ weak references --- an object referenced only by proxy objects is deallocated -- but instead of requiring an explicit call to retrieve the object, the proxy transparently forwards all operations to the object as long as the object still exists. If the object is deallocated, attempting to use a proxy will cause a -:exc:`weakref.ReferenceError` exception to be raised. :: +:exc:`!weakref.ReferenceError` exception to be raised. :: proxy = weakref.proxy(obj) proxy.attr # Equivalent to obj.attr @@ -446,7 +446,7 @@ The dictionary containing attributes can be accessed as the function's :attr:`~object.__dict__`. Unlike the :attr:`~object.__dict__` attribute of class instances, in functions you can actually assign a new dictionary to :attr:`~object.__dict__`, though the new value is restricted to a regular Python dictionary; you *can't* be -tricky and set it to a :class:`UserDict` instance, or any other random object +tricky and set it to a :class:`!UserDict` instance, or any other random object that behaves like a mapping. @@ -584,11 +584,11 @@ available from the Distutils SIG at https://www.python.org/community/sigs/curren New and Improved Modules ======================== -* Ka-Ping Yee contributed two new modules: :mod:`inspect.py`, a module for - getting information about live Python code, and :mod:`pydoc.py`, a module for +* Ka-Ping Yee contributed two new modules: :mod:`!inspect.py`, a module for + getting information about live Python code, and :mod:`!pydoc.py`, a module for interactively converting docstrings to HTML or text. As a bonus, :file:`Tools/scripts/pydoc`, which is now automatically installed, uses - :mod:`pydoc.py` to display documentation given a Python module, package, or + :mod:`!pydoc.py` to display documentation given a Python module, package, or class name. For example, ``pydoc xml.dom`` displays the following:: Python Library Documentation: package xml.dom in xml @@ -617,7 +617,7 @@ New and Improved Modules Kent Beck's Smalltalk testing framework. See https://pyunit.sourceforge.net/ for more information about PyUnit. -* The :mod:`difflib` module contains a class, :class:`SequenceMatcher`, which +* The :mod:`difflib` module contains a class, :class:`~difflib.SequenceMatcher`, which compares two sequences and computes the changes required to transform one sequence into the other. For example, this module can be used to write a tool similar to the Unix :program:`diff` program, and in fact the sample program @@ -633,7 +633,7 @@ New and Improved Modules 2.1 includes an updated version of the :mod:`xml` package. Some of the noteworthy changes include support for Expat 1.2 and later versions, the ability for Expat parsers to handle files in any encoding supported by Python, and - various bugfixes for SAX, DOM, and the :mod:`minidom` module. + various bugfixes for SAX, DOM, and the :mod:`!minidom` module. * Ping also contributed another hook for handling uncaught exceptions. :func:`sys.excepthook` can be set to a callable object. When an exception isn't @@ -643,8 +643,8 @@ New and Improved Modules printing an extended traceback that not only lists the stack frames, but also lists the function arguments and the local variables for each frame. -* Various functions in the :mod:`time` module, such as :func:`asctime` and - :func:`localtime`, require a floating point argument containing the time in +* Various functions in the :mod:`time` module, such as :func:`~time.asctime` and + :func:`~time.localtime`, require a floating point argument containing the time in seconds since the epoch. The most common use of these functions is to work with the current time, so the floating point argument has been made optional; when a value isn't provided, the current time will be used. For example, log file @@ -724,10 +724,10 @@ of the more notable changes are: a discussion in comp.lang.python. A new module and method for file objects was also added, contributed by Jeff - Epler. The new method, :meth:`xreadlines`, is similar to the existing - :func:`xrange` built-in. :func:`xreadlines` returns an opaque sequence object + Epler. The new method, :meth:`!xreadlines`, is similar to the existing + :func:`!xrange` built-in. :func:`!xreadlines` returns an opaque sequence object that only supports being iterated over, reading a line on every iteration but - not reading the entire file into memory as the existing :meth:`readlines` method + not reading the entire file into memory as the existing :meth:`!readlines` method does. You'd use it like this:: for line in sys.stdin.xreadlines(): @@ -737,7 +737,7 @@ of the more notable changes are: For a fuller discussion of the line I/O changes, see the python-dev summary for January 1--15, 2001 at https://mail.python.org/pipermail/python-dev/2001-January/. -* A new method, :meth:`popitem`, was added to dictionaries to enable +* A new method, :meth:`~dict.popitem`, was added to dictionaries to enable destructively iterating through the contents of a dictionary; this can be faster for large dictionaries because there's no need to construct a list containing all the keys or values. ``D.popitem()`` removes a random ``(key, value)`` pair diff --git a/Doc/whatsnew/2.7.rst b/Doc/whatsnew/2.7.rst index 2a42664c02852c2..5c99fbc503ba659 100644 --- a/Doc/whatsnew/2.7.rst +++ b/Doc/whatsnew/2.7.rst @@ -1130,7 +1130,7 @@ changes, or look through the Subversion logs for all the details. (Added by Raymond Hettinger; :issue:`1818`.) Finally, the :class:`~collections.abc.Mapping` abstract base class now - returns :const:`NotImplemented` if a mapping is compared to + returns :data:`NotImplemented` if a mapping is compared to another type that isn't a :class:`Mapping`. (Fixed by Daniel Stutzbach; :issue:`8729`.) diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 9770b12c8af7110..e35179a2d8e513b 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -828,7 +828,7 @@ Other Language Changes :meth:`~object.__index__` method). (Contributed by Serhiy Storchaka in :issue:`37999`.) -* If :func:`object.__ipow__` returns :const:`NotImplemented`, the operator will +* If :func:`object.__ipow__` returns :data:`NotImplemented`, the operator will correctly fall back to :func:`object.__pow__` and :func:`object.__rpow__` as expected. (Contributed by Alex Shkop in :issue:`38302`.) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 7c6a2af28758bee..a48db949d8f4012 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -62,7 +62,6 @@ Summary -- Release highlights .. This section singles out the most important changes in Python 3.13. Brevity is key. - .. PEP-sized items next. Important deprecations, removals or restrictions: @@ -174,6 +173,38 @@ Other Language Changes (Contributed by Victor Stinner in :gh:`114570`.) +* Allow controlling Expat >=2.6.0 reparse deferral (CVE-2023-52425) + by adding five new methods: + + * :meth:`xml.etree.ElementTree.XMLParser.flush` + * :meth:`xml.etree.ElementTree.XMLPullParser.flush` + * :meth:`xml.parsers.expat.xmlparser.GetReparseDeferralEnabled` + * :meth:`xml.parsers.expat.xmlparser.SetReparseDeferralEnabled` + * :meth:`!xml.sax.expatreader.ExpatParser.flush` + + (Contributed by Sebastian Pipping in :gh:`115623`.) + +* When :func:`asyncio.TaskGroup.create_task` is called on an inactive + :class:`asyncio.TaskGroup`, the given coroutine will be closed (which + prevents a :exc:`RuntimeWarning` about the given coroutine being + never awaited). + + (Contributed by Arthur Tacca and Jason Zhang in :gh:`115957`.) + +* The :func:`ssl.create_default_context` API now includes + :data:`ssl.VERIFY_X509_PARTIAL_CHAIN` and :data:`ssl.VERIFY_X509_STRICT` + in its default flags. + + .. note:: + + :data:`ssl.VERIFY_X509_STRICT` may reject pre-:rfc:`5280` or malformed + certificates that the underlying OpenSSL implementation otherwise would + accept. While disabling this is not recommended, you can do so using:: + + ctx = ssl.create_default_context() + ctx.verify_flags &= ~ssl.VERIFY_X509_STRICT + + (Contributed by William Woodruff in :gh:`112389`.) New Modules =========== @@ -206,6 +237,21 @@ array ast --- +* The constructors of node types in the :mod:`ast` module are now stricter + in the arguments they accept, and have more intuitive behaviour when + arguments are omitted. + + If an optional field on an AST node is not included as an argument when + constructing an instance, the field will now be set to ``None``. Similarly, + if a list field is omitted, that field will now be set to an empty list. + (Previously, in both cases, the attribute would be missing on the newly + constructed AST node instance.) + + If other arguments are omitted, a :exc:`DeprecationWarning` is emitted. + This will cause an exception in Python 3.15. Similarly, passing a keyword + argument that does not map to a field on the AST node is now deprecated, + and will raise an exception in Python 3.15. + * :func:`ast.parse` now accepts an optional argument ``optimize`` which is passed on to the :func:`compile` built-in. This makes it possible to obtain an optimized ``AST``. @@ -224,6 +270,14 @@ asyncio the buffer size. (Contributed by Jamie Phan in :gh:`115199`.) +base64 +--- + +* Add :func:`base64.z85encode` and :func:`base64.z85decode` functions which allow encoding + and decoding z85 data. + See `Z85 specification `_ for more information. + (Contributed by Matan Perelman in :gh:`75299`.) + copy ---- @@ -313,6 +367,14 @@ ipaddress * Add the :attr:`ipaddress.IPv4Address.ipv6_mapped` property, which returns the IPv4-mapped IPv6 address. (Contributed by Charles Machalow in :gh:`109466`.) +itertools +--------- + +* Added a ``strict`` option to :func:`itertools.batched`. + This raises a :exc:`ValueError` if the final batch is shorter + than the specified batch size. + (Contributed by Raymond Hettinger in :gh:`113202`.) + marshal ------- @@ -321,6 +383,16 @@ marshal code objects which are incompatible between Python versions. (Contributed by Serhiy Storchaka in :gh:`113626`.) +math +---- + +A new function :func:`~math.fma` for fused multiply-add operations has been +added. This function computes ``x * y + z`` with only a single round, and so +avoids any intermediate loss of precision. It wraps the ``fma()`` function +provided by C99, and follows the specification of the IEEE 754 +"fusedMultiplyAdd" operation for special cases. +(Contributed by Mark Dickinson and Victor Stinner in :gh:`73468`.) + mmap ---- @@ -460,6 +532,14 @@ sqlite3 for filtering database objects to dump. (Contributed by Mariusz Felisiak in :gh:`91602`.) +statistics +---------- + +* Add :func:`statistics.kde` for kernel density estimation. + This makes it possible to estimate a continuous probability density function + from a fixed number of discrete samples. + (Contributed by Raymond Hettinger in :gh:`115863`.) + subprocess ---------- @@ -482,6 +562,15 @@ sys This function is not guaranteed to exist in all implementations of Python. (Contributed by Serhiy Storchaka in :gh:`78573`.) +time +---- + +* On Windows, :func:`time.monotonic()` now uses the + ``QueryPerformanceCounter()`` clock to have a resolution better than 1 us, + instead of the ``GetTickCount64()`` clock which has a resolution of 15.6 ms. + (Contributed by Victor Stinner in :gh:`88494`.) + + tkinter ------- @@ -532,6 +621,10 @@ typing check whether a class is a :class:`typing.Protocol`. (Contributed by Jelle Zijlstra in :gh:`104873`.) +* Add :data:`typing.ReadOnly`, a special typing construct to mark + an item of a :class:`typing.TypedDict` as read-only for type checkers. + See :pep:`705` for more details. + unicodedata ----------- @@ -741,6 +834,13 @@ Deprecated coroutine. (Contributed by Irit Katriel in :gh:`81137`.) +* The undocumented and unused ``tarfile`` attribute of :class:`tarfile.TarFile` + is deprecated and scheduled for removal in Python 3.16. + +* :func:`platform.java_ver` is deprecated and will be removed in 3.15. + It was largely untested, had a confusing API, + and was only useful for Jython support. + (Contributed by Nikita Sobolev in :gh:`116349`.) Pending Removal in Python 3.14 ------------------------------ @@ -900,6 +1000,11 @@ Pending Removal in Python 3.15 They will be removed in Python 3.15. (Contributed by Victor Stinner in :gh:`105096`.) +* :func:`platform.java_ver` is deprecated and will be removed in 3.15. + It was largely untested, had a confusing API, + and was only useful for Jython support. + (Contributed by Nikita Sobolev in :gh:`116349`.) + Pending Removal in Python 3.16 ------------------------------ @@ -1374,9 +1479,11 @@ Build Changes * Building CPython now requires a compiler with support for the C11 atomic library, GCC built-in atomic functions, or MSVC interlocked intrinsics. -* The ``errno``, ``md5``, ``resource``, ``winsound``, ``_ctypes_test``, - ``_multiprocessing.posixshmem``, ``_scproxy``, ``_stat``, - ``_testimportmultiple`` and ``_uuid`` C extensions are now built with the +* The ``errno``, ``fcntl``, ``grp``, ``md5``, ``pwd``, ``resource``, + ``termios``, ``winsound``, + ``_ctypes_test``, ``_multiprocessing.posixshmem``, ``_scproxy``, ``_stat``, + ``_statistics``, ``_testimportmultiple`` and ``_uuid`` + C extensions are now built with the :ref:`limited C API `. (Contributed by Victor Stinner in :gh:`85283`.) @@ -1563,6 +1670,22 @@ New Features between native integer types and Python :class:`int` objects. (Contributed by Steve Dower in :gh:`111140`.) +* Add :c:func:`PyType_GetFullyQualifiedName` function to get the type's fully + qualified name. Equivalent to ``f"{type.__module__}.{type.__qualname__}"``, + or ``type.__qualname__`` if ``type.__module__`` is not a string or is equal + to ``"builtins"``. + (Contributed by Victor Stinner in :gh:`111696`.) + +* Add :c:func:`PyType_GetModuleName` function to get the type's module name. + Equivalent to getting the ``type.__module__`` attribute. + (Contributed by Eric Snow and Victor Stinner in :gh:`111696`.) + +* Add support for ``%T``, ``%T#``, ``%N`` and ``%N#`` formats to + :c:func:`PyUnicode_FromFormat`: format the fully qualified name of an object + type and of a type: call :c:func:`PyType_GetModuleName`. See :pep:`737` for + more information. + (Contributed by Victor Stinner in :gh:`111696`.) + Porting to Python 3.13 ---------------------- diff --git a/Doc/whatsnew/3.4.rst b/Doc/whatsnew/3.4.rst index e07eda985d9bad4..3dd400c3771ed2f 100644 --- a/Doc/whatsnew/3.4.rst +++ b/Doc/whatsnew/3.4.rst @@ -872,7 +872,7 @@ multiple implementations of an operation that allows it to work with PEP written and implemented by Łukasz Langa. :func:`~functools.total_ordering` now supports a return value of -:const:`NotImplemented` from the underlying comparison function. (Contributed +:data:`NotImplemented` from the underlying comparison function. (Contributed by Katie Miller in :issue:`10042`.) A pure-python version of the :func:`~functools.partial` function is now in the diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index b24c13813be2209..49d926b0edcd0f8 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -1126,7 +1126,7 @@ Changes in the Python API ``logging.getLogger(__name__)`` in some top-level module called ``'root.py'``. (Contributed by Vinay Sajip in :issue:`37742`.) -* Division handling of :class:`~pathlib.PurePath` now returns ``NotImplemented`` +* Division handling of :class:`~pathlib.PurePath` now returns :data:`NotImplemented` instead of raising a :exc:`TypeError` when passed something other than an instance of ``str`` or :class:`~pathlib.PurePath`. This allows creating compatible classes that don't inherit from those mentioned types. diff --git a/Grammar/python.gram b/Grammar/python.gram index 174b4dbb6f78427..797c195a0a91ba7 100644 --- a/Grammar/python.gram +++ b/Grammar/python.gram @@ -393,7 +393,7 @@ for_stmt[stmt_ty]: with_stmt[stmt_ty]: | invalid_with_stmt_indent | 'with' '(' a[asdl_withitem_seq*]=','.with_item+ ','? ')' ':' tc=[TYPE_COMMENT] b=block { - CHECK_VERSION(stmt_ty, 9, "Parenthesized context managers are", _PyAST_With(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA)) } + _PyAST_With(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA) } | 'with' a[asdl_withitem_seq*]=','.with_item+ ':' tc=[TYPE_COMMENT] b=block { _PyAST_With(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA) } | 'async' 'with' '(' a[asdl_withitem_seq*]=','.with_item+ ','? ')' ':' b=block { diff --git a/Include/cpython/code.h b/Include/cpython/code.h index 1f47d99fb604434..4e7e212fd099db6 100644 --- a/Include/cpython/code.h +++ b/Include/cpython/code.h @@ -73,7 +73,7 @@ typedef struct { PyObject *_co_freevars; } _PyCoCached; -/* Ancilliary data structure used for instrumentation. +/* Ancillary data structure used for instrumentation. Line instrumentation creates an array of these. One entry per code unit.*/ typedef struct { diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h index 87c059c521cbc92..5da5ef9e5431b1f 100644 --- a/Include/cpython/initconfig.h +++ b/Include/cpython/initconfig.h @@ -181,6 +181,9 @@ typedef struct PyConfig { int int_max_str_digits; int cpu_count; +#ifdef Py_GIL_DISABLED + int enable_gil; +#endif /* --- Path configuration inputs ------------ */ int pathconfig_warnings; diff --git a/Include/cpython/longintrepr.h b/Include/cpython/longintrepr.h index f5ccbb704e8bb8e..f037c7bb90deda5 100644 --- a/Include/cpython/longintrepr.h +++ b/Include/cpython/longintrepr.h @@ -62,21 +62,32 @@ typedef long stwodigits; /* signed variant of twodigits */ #define PyLong_MASK ((digit)(PyLong_BASE - 1)) /* Long integer representation. + + Long integers are made up of a number of 30- or 15-bit digits, depending on + the platform. The number of digits (ndigits) is stored in the high bits of + the lv_tag field (lvtag >> _PyLong_NON_SIZE_BITS). + The absolute value of a number is equal to - SUM(for i=0 through abs(ob_size)-1) ob_digit[i] * 2**(SHIFT*i) - Negative numbers are represented with ob_size < 0; - zero is represented by ob_size == 0. - In a normalized number, ob_digit[abs(ob_size)-1] (the most significant + SUM(for i=0 through ndigits-1) ob_digit[i] * 2**(PyLong_SHIFT*i) + + The sign of the value is stored in the lower 2 bits of lv_tag. + + - 0: Positive + - 1: Zero + - 2: Negative + + The third lowest bit of lv_tag is reserved for an immortality flag, but is + not currently used. + + In a normalized number, ob_digit[ndigits-1] (the most significant digit) is never zero. Also, in all cases, for all valid i, - 0 <= ob_digit[i] <= MASK. + 0 <= ob_digit[i] <= PyLong_MASK. + The allocation function takes care of allocating extra memory - so that ob_digit[0] ... ob_digit[abs(ob_size)-1] are actually available. + so that ob_digit[0] ... ob_digit[ndigits-1] are actually available. We always allocate memory for at least one digit, so accessing ob_digit[0] - is always safe. However, in the case ob_size == 0, the contents of + is always safe. However, in the case ndigits == 0, the contents of ob_digit[0] may be undefined. - - CAUTION: Generic code manipulating subtypes of PyVarObject has to - aware that ints abuse ob_size's sign bit. */ typedef struct _PyLongValue { diff --git a/Include/cpython/optimizer.h b/Include/cpython/optimizer.h index fe54d1ddfe61293..6d7b8bc3c1433aa 100644 --- a/Include/cpython/optimizer.h +++ b/Include/cpython/optimizer.h @@ -92,16 +92,13 @@ PyAPI_FUNC(_PyOptimizerObject *) PyUnstable_GetOptimizer(void); PyAPI_FUNC(_PyExecutorObject *) PyUnstable_GetExecutor(PyCodeObject *code, int offset); -int -_PyOptimizer_Optimize(struct _PyInterpreterFrame *frame, _Py_CODEUNIT *start, PyObject **stack_pointer, _PyExecutorObject **exec_ptr); - void _Py_ExecutorInit(_PyExecutorObject *, const _PyBloomFilter *); void _Py_ExecutorClear(_PyExecutorObject *); void _Py_BloomFilter_Init(_PyBloomFilter *); void _Py_BloomFilter_Add(_PyBloomFilter *bloom, void *obj); PyAPI_FUNC(void) _Py_Executor_DependsOn(_PyExecutorObject *executor, void *obj); -PyAPI_FUNC(void) _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj); -extern void _Py_Executors_InvalidateAll(PyInterpreterState *interp); +PyAPI_FUNC(void) _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is_invalidation); +extern void _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation); /* For testing */ PyAPI_FUNC(PyObject *)PyUnstable_Optimizer_NewCounter(void); diff --git a/Include/cpython/pyerrors.h b/Include/cpython/pyerrors.h index 32c5884cd21341c..42b4b03b10ca20f 100644 --- a/Include/cpython/pyerrors.h +++ b/Include/cpython/pyerrors.h @@ -88,6 +88,10 @@ typedef PyOSErrorObject PyEnvironmentErrorObject; typedef PyOSErrorObject PyWindowsErrorObject; #endif +/* Context manipulation (PEP 3134) */ + +PyAPI_FUNC(void) _PyErr_ChainExceptions1(PyObject *); + /* In exceptions.c */ PyAPI_FUNC(PyObject*) PyUnstable_Exc_PrepReraiseStar( diff --git a/Include/cpython/pyhash.h b/Include/cpython/pyhash.h index 396c208e1b106a9..b476c3f357de920 100644 --- a/Include/cpython/pyhash.h +++ b/Include/cpython/pyhash.h @@ -10,14 +10,20 @@ reduction modulo the prime 2**_PyHASH_BITS - 1. */ #if SIZEOF_VOID_P >= 8 -# define _PyHASH_BITS 61 +# define PyHASH_BITS 61 #else -# define _PyHASH_BITS 31 +# define PyHASH_BITS 31 #endif -#define _PyHASH_MODULUS (((size_t)1 << _PyHASH_BITS) - 1) -#define _PyHASH_INF 314159 -#define _PyHASH_IMAG _PyHASH_MULTIPLIER +#define PyHASH_MODULUS (((size_t)1 << _PyHASH_BITS) - 1) +#define PyHASH_INF 314159 +#define PyHASH_IMAG _PyHASH_MULTIPLIER + +/* Aliases kept for backward compatibility with Python 3.12 */ +#define _PyHASH_BITS PyHASH_BITS +#define _PyHASH_MODULUS PyHASH_MODULUS +#define _PyHASH_INF PyHASH_INF +#define _PyHASH_IMAG PyHASH_IMAG /* Helpers for hash functions */ PyAPI_FUNC(Py_hash_t) _Py_HashDouble(PyObject *, double); diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index ac7ff83748dbfc8..38d0897ea131611 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -161,32 +161,6 @@ struct _ts { */ uintptr_t critical_section; - /* Called when a thread state is deleted normally, but not when it - * is destroyed after fork(). - * Pain: to prevent rare but fatal shutdown errors (issue 18808), - * Thread.join() must wait for the join'ed thread's tstate to be unlinked - * from the tstate chain. That happens at the end of a thread's life, - * in pystate.c. - * The obvious way doesn't quite work: create a lock which the tstate - * unlinking code releases, and have Thread.join() wait to acquire that - * lock. The problem is that we _are_ at the end of the thread's life: - * if the thread holds the last reference to the lock, decref'ing the - * lock will delete the lock, and that may trigger arbitrary Python code - * if there's a weakref, with a callback, to the lock. But by this time - * _PyRuntime.gilstate.tstate_current is already NULL, so only the simplest - * of C code can be allowed to run (in particular it must not be possible to - * release the GIL). - * So instead of holding the lock directly, the tstate holds a weakref to - * the lock: that's the value of on_delete_data below. Decref'ing a - * weakref is harmless. - * on_delete points to _threadmodule.c's static release_sentinel() function. - * After the tstate is unlinked, release_sentinel is called with the - * weakref-to-lock (on_delete_data) argument, and release_sentinel releases - * the indirectly held lock. - */ - void (*on_delete)(void *); - void *on_delete_data; - int coroutine_origin_tracking_depth; PyObject *async_gen_firstiter; diff --git a/Include/cpython/pystats.h b/Include/cpython/pystats.h index db9aaedec950e45..887fbbedf88502b 100644 --- a/Include/cpython/pystats.h +++ b/Include/cpython/pystats.h @@ -115,6 +115,7 @@ typedef struct _optimization_stats { uint64_t inner_loop; uint64_t recursive_call; uint64_t low_confidence; + uint64_t executors_invalidated; UOpStats opcode[512]; uint64_t unsupported_opcode[256]; uint64_t trace_length_hist[_Py_UOP_HIST_SIZE]; diff --git a/Include/internal/mimalloc/mimalloc/types.h b/Include/internal/mimalloc/mimalloc/types.h index ed93e45062c2dbd..17e440848ecae5c 100644 --- a/Include/internal/mimalloc/mimalloc/types.h +++ b/Include/internal/mimalloc/mimalloc/types.h @@ -311,6 +311,7 @@ typedef struct mi_page_s { uint32_t slice_offset; // distance from the actual page data slice (0 if a page) uint8_t is_committed : 1; // `true` if the page virtual memory is committed uint8_t is_zero_init : 1; // `true` if the page was initially zero initialized + uint8_t use_qsbr : 1; // delay page freeing using qsbr uint8_t tag : 4; // tag from the owning heap uint8_t debug_offset; // number of bytes to preserve when filling freed or uninitialized memory @@ -336,8 +337,13 @@ typedef struct mi_page_s { struct mi_page_s* next; // next page owned by this thread with the same `block_size` struct mi_page_s* prev; // previous page owned by this thread with the same `block_size` +#ifdef Py_GIL_DISABLED + struct llist_node qsbr_node; + uint64_t qsbr_goal; +#endif + // 64-bit 9 words, 32-bit 12 words, (+2 for secure) - #if MI_INTPTR_SIZE==8 + #if MI_INTPTR_SIZE==8 && !defined(Py_GIL_DISABLED) uintptr_t padding[1]; #endif } mi_page_t; @@ -555,6 +561,7 @@ struct mi_heap_s { bool no_reclaim; // `true` if this heap should not reclaim abandoned pages uint8_t tag; // custom identifier for this heap uint8_t debug_offset; // number of bytes to preserve when filling freed or uninitialized memory + bool page_use_qsbr; // should freeing pages be delayed using QSBR }; diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index bf77526cf75cc14..946f82ae3c20e38 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -138,12 +138,12 @@ extern void _PyEval_DeactivateOpCache(void); /* With USE_STACKCHECK macro defined, trigger stack checks in _Py_CheckRecursiveCall() on every 64th call to _Py_EnterRecursiveCall. */ static inline int _Py_MakeRecCheck(PyThreadState *tstate) { - return (tstate->c_recursion_remaining-- <= 0 + return (tstate->c_recursion_remaining-- < 0 || (tstate->c_recursion_remaining & 63) == 0); } #else static inline int _Py_MakeRecCheck(PyThreadState *tstate) { - return tstate->c_recursion_remaining-- <= 0; + return tstate->c_recursion_remaining-- < 0; } #endif @@ -161,6 +161,11 @@ static inline int _Py_EnterRecursiveCallTstate(PyThreadState *tstate, return (_Py_MakeRecCheck(tstate) && _Py_CheckRecursiveCall(tstate, where)); } +static inline void _Py_EnterRecursiveCallTstateUnchecked(PyThreadState *tstate) { + assert(tstate->c_recursion_remaining > 0); + tstate->c_recursion_remaining--; +} + static inline int _Py_EnterRecursiveCall(const char *where) { PyThreadState *tstate = _PyThreadState_GET(); return _Py_EnterRecursiveCallTstate(tstate, where); @@ -181,22 +186,26 @@ extern PyObject* _Py_MakeCoro(PyFunctionObject *func); /* Handle signals, pending calls, GIL drop request and asynchronous exception */ -extern int _Py_HandlePending(PyThreadState *tstate); +PyAPI_FUNC(int) _Py_HandlePending(PyThreadState *tstate); extern PyObject * _PyEval_GetFrameLocals(void); -extern const binaryfunc _PyEval_BinaryOps[]; -int _PyEval_CheckExceptStarTypeValid(PyThreadState *tstate, PyObject* right); -int _PyEval_CheckExceptTypeValid(PyThreadState *tstate, PyObject* right); -int _PyEval_ExceptionGroupMatch(PyObject* exc_value, PyObject *match_type, PyObject **match, PyObject **rest); -void _PyEval_FormatAwaitableError(PyThreadState *tstate, PyTypeObject *type, int oparg); -void _PyEval_FormatExcCheckArg(PyThreadState *tstate, PyObject *exc, const char *format_str, PyObject *obj); -void _PyEval_FormatExcUnbound(PyThreadState *tstate, PyCodeObject *co, int oparg); -void _PyEval_FormatKwargsError(PyThreadState *tstate, PyObject *func, PyObject *kwargs); -PyObject *_PyEval_MatchClass(PyThreadState *tstate, PyObject *subject, PyObject *type, Py_ssize_t nargs, PyObject *kwargs); -PyObject *_PyEval_MatchKeys(PyThreadState *tstate, PyObject *map, PyObject *keys); -int _PyEval_UnpackIterable(PyThreadState *tstate, PyObject *v, int argcnt, int argcntafter, PyObject **sp); -void _PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame); +typedef PyObject *(*conversion_func)(PyObject *); + +PyAPI_DATA(const binaryfunc) _PyEval_BinaryOps[]; +PyAPI_DATA(const conversion_func) _PyEval_ConversionFuncs[]; + +PyAPI_FUNC(int) _PyEval_CheckExceptStarTypeValid(PyThreadState *tstate, PyObject* right); +PyAPI_FUNC(int) _PyEval_CheckExceptTypeValid(PyThreadState *tstate, PyObject* right); +PyAPI_FUNC(int) _PyEval_ExceptionGroupMatch(PyObject* exc_value, PyObject *match_type, PyObject **match, PyObject **rest); +PyAPI_FUNC(void) _PyEval_FormatAwaitableError(PyThreadState *tstate, PyTypeObject *type, int oparg); +PyAPI_FUNC(void) _PyEval_FormatExcCheckArg(PyThreadState *tstate, PyObject *exc, const char *format_str, PyObject *obj); +PyAPI_FUNC(void) _PyEval_FormatExcUnbound(PyThreadState *tstate, PyCodeObject *co, int oparg); +PyAPI_FUNC(void) _PyEval_FormatKwargsError(PyThreadState *tstate, PyObject *func, PyObject *kwargs); +PyAPI_FUNC(PyObject *)_PyEval_MatchClass(PyThreadState *tstate, PyObject *subject, PyObject *type, Py_ssize_t nargs, PyObject *kwargs); +PyAPI_FUNC(PyObject *)_PyEval_MatchKeys(PyThreadState *tstate, PyObject *map, PyObject *keys); +PyAPI_FUNC(int) _PyEval_UnpackIterable(PyThreadState *tstate, PyObject *v, int argcnt, int argcntafter, PyObject **sp); +PyAPI_FUNC(void) _PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame); /* Bits that can be set in PyThreadState.eval_breaker */ diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h index 855361621320729..8eabd49a18afa91 100644 --- a/Include/internal/pycore_code.h +++ b/Include/internal/pycore_code.h @@ -121,6 +121,12 @@ typedef struct { #define INLINE_CACHE_ENTRIES_TO_BOOL CACHE_ENTRIES(_PyToBoolCache) +typedef struct { + uint16_t counter; +} _PyContainsOpCache; + +#define INLINE_CACHE_ENTRIES_CONTAINS_OP CACHE_ENTRIES(_PyContainsOpCache) + // Borrowed references to common callables: struct callable_cache { PyObject *isinstance; @@ -245,7 +251,12 @@ extern int _PyLineTable_PreviousAddressRange(PyCodeAddressRange *range); /** API for executors */ extern void _PyCode_Clear_Executors(PyCodeObject *code); +#ifdef Py_GIL_DISABLED +// gh-115999 tracks progress on addressing this. +#define ENABLE_SPECIALIZATION 0 +#else #define ENABLE_SPECIALIZATION 1 +#endif /* Specialization functions */ @@ -272,6 +283,7 @@ extern void _Py_Specialize_UnpackSequence(PyObject *seq, _Py_CODEUNIT *instr, extern void _Py_Specialize_ForIter(PyObject *iter, _Py_CODEUNIT *instr, int oparg); extern void _Py_Specialize_Send(PyObject *receiver, _Py_CODEUNIT *instr); extern void _Py_Specialize_ToBool(PyObject *value, _Py_CODEUNIT *instr); +extern void _Py_Specialize_ContainsOp(PyObject *value, _Py_CODEUNIT *instr); /* Finalizer function for static codeobjects used in deepfreeze.py */ extern void _PyStaticCode_Fini(PyCodeObject *co); diff --git a/Include/internal/pycore_compile.h b/Include/internal/pycore_compile.h index e5870759ba74f16..0f446a00b4df22c 100644 --- a/Include/internal/pycore_compile.h +++ b/Include/internal/pycore_compile.h @@ -103,7 +103,7 @@ int _PyCompile_EnsureArrayLargeEnough( int _PyCompile_ConstCacheMergeOne(PyObject *const_cache, PyObject **obj); -// Export for '_opcode' extention module +// Export for '_opcode' extension module PyAPI_FUNC(int) _PyCompile_OpcodeIsValid(int opcode); PyAPI_FUNC(int) _PyCompile_OpcodeHasArg(int opcode); PyAPI_FUNC(int) _PyCompile_OpcodeHasConst(int opcode); diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index d1a0010b9a81dd7..ef59960dbab0717 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -52,7 +52,7 @@ PyAPI_FUNC(Py_ssize_t) _PyDict_SizeOf(PyDictObject *); of a key wins, if override is 2, a KeyError with conflicting key as argument is raised. */ -extern int _PyDict_MergeEx(PyObject *mp, PyObject *other, int override); +PyAPI_FUNC(int) _PyDict_MergeEx(PyObject *mp, PyObject *other, int override); extern void _PyDict_DebugMallocStats(FILE *out); @@ -100,10 +100,10 @@ extern Py_ssize_t _Py_dict_lookup(PyDictObject *mp, PyObject *key, Py_hash_t has extern Py_ssize_t _PyDict_LookupIndex(PyDictObject *, PyObject *); extern Py_ssize_t _PyDictKeys_StringLookup(PyDictKeysObject* dictkeys, PyObject *key); -extern PyObject *_PyDict_LoadGlobal(PyDictObject *, PyDictObject *, PyObject *); +PyAPI_FUNC(PyObject *)_PyDict_LoadGlobal(PyDictObject *, PyDictObject *, PyObject *); /* Consumes references to key and value */ -extern int _PyDict_SetItem_Take2(PyDictObject *op, PyObject *key, PyObject *value); +PyAPI_FUNC(int) _PyDict_SetItem_Take2(PyDictObject *op, PyObject *key, PyObject *value); extern int _PyObjectDict_SetItem(PyTypeObject *tp, PyObject **dictptr, PyObject *name, PyObject *value); extern int _PyDict_Pop_KnownHash( @@ -167,7 +167,7 @@ struct _dictkeysobject { char dk_indices[]; /* char is required to avoid strict aliasing. */ /* "PyDictKeyEntry or PyDictUnicodeEntry dk_entries[USABLE_FRACTION(DK_SIZE(dk))];" array follows: - see the DK_ENTRIES() macro */ + see the DK_ENTRIES() / DK_UNICODE_ENTRIES() functions below */ }; /* This must be no more than 250, for the prefix size to fit in one byte. */ @@ -247,8 +247,8 @@ _PyDict_NotifyEvent(PyInterpreterState *interp, } extern PyObject *_PyObject_MakeDictFromInstanceAttributes(PyObject *obj, PyDictValues *values); -extern bool _PyObject_MakeInstanceAttributesFromDict(PyObject *obj, PyDictOrValues *dorv); -extern PyObject *_PyDict_FromItems( +PyAPI_FUNC(bool) _PyObject_MakeInstanceAttributesFromDict(PyObject *obj, PyDictOrValues *dorv); +PyAPI_FUNC(PyObject *)_PyDict_FromItems( PyObject *const *keys, Py_ssize_t keys_offset, PyObject *const *values, Py_ssize_t values_offset, Py_ssize_t length); diff --git a/Include/internal/pycore_floatobject.h b/Include/internal/pycore_floatobject.h index 3767df5506d43fb..f984df695696c3e 100644 --- a/Include/internal/pycore_floatobject.h +++ b/Include/internal/pycore_floatobject.h @@ -34,7 +34,7 @@ struct _Py_float_runtime_state { -void _PyFloat_ExactDealloc(PyObject *op); +PyAPI_FUNC(void) _PyFloat_ExactDealloc(PyObject *op); extern void _PyFloat_DebugMallocStats(FILE* out); diff --git a/Include/internal/pycore_function.h b/Include/internal/pycore_function.h index 3f3da8a44b77e4c..dad6a89af77dec9 100644 --- a/Include/internal/pycore_function.h +++ b/Include/internal/pycore_function.h @@ -29,7 +29,7 @@ struct _py_func_state { extern PyFunctionObject* _PyFunction_FromConstructor(PyFrameConstructor *constr); extern uint32_t _PyFunction_GetVersionForCurrentState(PyFunctionObject *func); -extern void _PyFunction_SetVersion(PyFunctionObject *func, uint32_t version); +PyAPI_FUNC(void) _PyFunction_SetVersion(PyFunctionObject *func, uint32_t version); PyFunctionObject *_PyFunction_LookupByVersion(uint32_t version); extern PyObject *_Py_set_function_type_params( diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index 40414a868518bb6..4a7191a562cc100 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -44,6 +44,7 @@ static inline PyObject* _Py_FROM_GC(PyGC_Head *gc) { # define _PyGC_BITS_UNREACHABLE (4) # define _PyGC_BITS_FROZEN (8) # define _PyGC_BITS_SHARED (16) +# define _PyGC_BITS_SHARED_INLINE (32) #endif /* True if the object is currently tracked by the GC. */ @@ -71,9 +72,12 @@ static inline int _PyObject_GC_MAY_BE_TRACKED(PyObject *obj) { #ifdef Py_GIL_DISABLED -/* True if an object is shared between multiple threads and - * needs special purpose when freeing to do the possibility - * of in-flight lock-free reads occuring */ +/* True if memory the object references is shared between + * multiple threads and needs special purpose when freeing + * those references due to the possibility of in-flight + * lock-free reads occurring. The object is responsible + * for calling _PyMem_FreeDelayed on the referenced + * memory. */ static inline int _PyObject_GC_IS_SHARED(PyObject *op) { return (op->ob_gc_bits & _PyGC_BITS_SHARED) != 0; } @@ -84,6 +88,23 @@ static inline void _PyObject_GC_SET_SHARED(PyObject *op) { } #define _PyObject_GC_SET_SHARED(op) _PyObject_GC_SET_SHARED(_Py_CAST(PyObject*, op)) +/* True if the memory of the object is shared between multiple + * threads and needs special purpose when freeing due to + * the possibility of in-flight lock-free reads occurring. + * Objects with this bit that are GC objects will automatically + * delay-freed by PyObject_GC_Del. */ +static inline int _PyObject_GC_IS_SHARED_INLINE(PyObject *op) { + return (op->ob_gc_bits & _PyGC_BITS_SHARED_INLINE) != 0; +} +#define _PyObject_GC_IS_SHARED_INLINE(op) \ + _PyObject_GC_IS_SHARED_INLINE(_Py_CAST(PyObject*, op)) + +static inline void _PyObject_GC_SET_SHARED_INLINE(PyObject *op) { + op->ob_gc_bits |= _PyGC_BITS_SHARED_INLINE; +} +#define _PyObject_GC_SET_SHARED_INLINE(op) \ + _PyObject_GC_SET_SHARED_INLINE(_Py_CAST(PyObject*, op)) + #endif /* Bit flags for _gc_prev */ diff --git a/Include/internal/pycore_genobject.h b/Include/internal/pycore_genobject.h index b2aa017598409f7..9463c822ad86698 100644 --- a/Include/internal/pycore_genobject.h +++ b/Include/internal/pycore_genobject.h @@ -10,7 +10,7 @@ extern "C" { #include "pycore_freelist.h" -extern PyObject *_PyGen_yf(PyGenObject *); +PyAPI_FUNC(PyObject *)_PyGen_yf(PyGenObject *); extern void _PyGen_Finalize(PyObject *self); // Export for '_asyncio' shared extension @@ -19,7 +19,7 @@ PyAPI_FUNC(int) _PyGen_SetStopIterationValue(PyObject *); // Export for '_asyncio' shared extension PyAPI_FUNC(int) _PyGen_FetchStopIterationValue(PyObject **); -extern PyObject *_PyCoro_GetAwaitableIter(PyObject *o); +PyAPI_FUNC(PyObject *)_PyCoro_GetAwaitableIter(PyObject *o); extern PyObject *_PyAsyncGenValueWrapperNew(PyThreadState *state, PyObject *); extern PyTypeObject _PyCoroWrapper_Type; diff --git a/Include/internal/pycore_gil.h b/Include/internal/pycore_gil.h index 19b0d23a68568a2..d36b4c0db010b2e 100644 --- a/Include/internal/pycore_gil.h +++ b/Include/internal/pycore_gil.h @@ -20,6 +20,11 @@ extern "C" { #define FORCE_SWITCHING struct _gil_runtime_state { +#ifdef Py_GIL_DISABLED + /* Whether or not this GIL is being used. Can change from 0 to 1 at runtime + if, for example, a module that requires the GIL is loaded. */ + int enabled; +#endif /* microseconds (the Python API uses seconds, though) */ unsigned long interval; /* Last PyThreadState holding / having held the GIL. This helps us diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 3253b5271a9b7c8..d22876871814507 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -753,6 +753,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_check_retval_)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_dealloc_warn)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_feature_version)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_field_types)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_fields_)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_finalizing)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_find_and_load)); @@ -982,7 +983,6 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hi)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hook)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(hour)); - _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(id)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ident)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(identity_hint)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ignore)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 8780f7ef9491651..fb9ec44d3f52aae 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -242,6 +242,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(_check_retval_) STRUCT_FOR_ID(_dealloc_warn) STRUCT_FOR_ID(_feature_version) + STRUCT_FOR_ID(_field_types) STRUCT_FOR_ID(_fields_) STRUCT_FOR_ID(_finalizing) STRUCT_FOR_ID(_find_and_load) @@ -471,7 +472,6 @@ struct _Py_global_strings { STRUCT_FOR_ID(hi) STRUCT_FOR_ID(hook) STRUCT_FOR_ID(hour) - STRUCT_FOR_ID(id) STRUCT_FOR_ID(ident) STRUCT_FOR_ID(identity_hint) STRUCT_FOR_ID(ignore) diff --git a/Include/internal/pycore_initconfig.h b/Include/internal/pycore_initconfig.h index c86988234f6a050..1c68161341860aa 100644 --- a/Include/internal/pycore_initconfig.h +++ b/Include/internal/pycore_initconfig.h @@ -153,6 +153,18 @@ typedef enum { _PyConfig_INIT_ISOLATED = 3 } _PyConfigInitEnum; +typedef enum { + /* For now, this means the GIL is enabled. + + gh-116329: This will eventually change to "the GIL is disabled but can + be reenabled by loading an incompatible extension module." */ + _PyConfig_GIL_DEFAULT = -1, + + /* The GIL has been forced off or on, and will not be affected by module loading. */ + _PyConfig_GIL_DISABLE = 0, + _PyConfig_GIL_ENABLE = 1, +} _PyConfigGILEnum; + // Export for '_testembed' program PyAPI_FUNC(void) _PyConfig_InitCompatConfig(PyConfig *config); diff --git a/Include/internal/pycore_instruments.h b/Include/internal/pycore_instruments.h index eae8371ef7f9b8f..7f84d4a763bbcf6 100644 --- a/Include/internal/pycore_instruments.h +++ b/Include/internal/pycore_instruments.h @@ -39,7 +39,7 @@ extern "C" { #define PY_MONITORING_EVENT_RERAISE 14 -/* Ancilliary events */ +/* Ancillary events */ #define PY_MONITORING_EVENT_C_RETURN 15 #define PY_MONITORING_EVENT_C_RAISE 16 diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 6a00aafea73779a..d79fd3b6039ef53 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -85,7 +85,7 @@ typedef struct _rare_events { */ struct _is { - /* This struct countains the eval_breaker, + /* This struct contains the eval_breaker, * which is by far the hottest field in this struct * and should be placed at the beginning. */ struct _ceval_state ceval; diff --git a/Include/internal/pycore_intrinsics.h b/Include/internal/pycore_intrinsics.h index 3a8dd95cff8e5db..8fa88ea3f74caa8 100644 --- a/Include/internal/pycore_intrinsics.h +++ b/Include/internal/pycore_intrinsics.h @@ -44,7 +44,7 @@ typedef struct { const char *name; } intrinsic_func2_info; -extern const intrinsic_func1_info _PyIntrinsics_UnaryFunctions[]; -extern const intrinsic_func2_info _PyIntrinsics_BinaryFunctions[]; +PyAPI_DATA(const intrinsic_func1_info) _PyIntrinsics_UnaryFunctions[]; +PyAPI_DATA(const intrinsic_func2_info) _PyIntrinsics_BinaryFunctions[]; #endif // !Py_INTERNAL_INTRINSIC_H diff --git a/Include/internal/pycore_list.h b/Include/internal/pycore_list.h index 50dc13c4da4487c..2a82912e41d5571 100644 --- a/Include/internal/pycore_list.h +++ b/Include/internal/pycore_list.h @@ -10,12 +10,12 @@ extern "C" { #include "pycore_freelist.h" // _PyFreeListState -extern PyObject* _PyList_Extend(PyListObject *, PyObject *); +PyAPI_FUNC(PyObject*) _PyList_Extend(PyListObject *, PyObject *); extern void _PyList_DebugMallocStats(FILE *out); #define _PyList_ITEMS(op) _Py_RVALUE(_PyList_CAST(op)->ob_item) -extern int +PyAPI_FUNC(int) _PyList_AppendTakeRefListResize(PyListObject *self, PyObject *newitem); // In free-threaded build: self should be locked by the caller, if it should be thread-safe. @@ -54,7 +54,7 @@ typedef struct { PyListObject *it_seq; /* Set to NULL when iterator is exhausted */ } _PyListIterObject; -extern PyObject *_PyList_FromArraySteal(PyObject *const *src, Py_ssize_t n); +PyAPI_FUNC(PyObject *)_PyList_FromArraySteal(PyObject *const *src, Py_ssize_t n); #ifdef __cplusplus } diff --git a/Include/internal/pycore_lock.h b/Include/internal/pycore_lock.h index c89159b55e130f6..f993c95ecbf75ae 100644 --- a/Include/internal/pycore_lock.h +++ b/Include/internal/pycore_lock.h @@ -136,6 +136,10 @@ typedef struct { uint8_t v; } PyEvent; +// Check if the event is set without blocking. Returns 1 if the event is set or +// 0 otherwise. +PyAPI_FUNC(int) _PyEvent_IsSet(PyEvent *evt); + // Set the event and notify any waiting threads. // Export for '_testinternalcapi' shared extension PyAPI_FUNC(void) _PyEvent_Notify(PyEvent *evt); @@ -149,7 +153,6 @@ PyAPI_FUNC(void) PyEvent_Wait(PyEvent *evt); // and 0 if the timeout expired or thread was interrupted. PyAPI_FUNC(int) PyEvent_WaitTimed(PyEvent *evt, PyTime_t timeout_ns); - // _PyRawMutex implements a word-sized mutex that that does not depend on the // parking lot API, and therefore can be used in the parking lot // implementation. @@ -258,7 +261,7 @@ PyAPI_FUNC(void) _PyRWMutex_Unlock(_PyRWMutex *rwmutex); // underlying data and then read the sequence number again after reading the data. If the // sequence has not changed the data is valid. // -// Differs a little bit in that we use CAS on sequence as the lock, instead of a seperate spin lock. +// Differs a little bit in that we use CAS on sequence as the lock, instead of a separate spin lock. // The writer can also detect that the undelering data has not changed and abandon the write // and restore the previous sequence. typedef struct { @@ -271,7 +274,7 @@ PyAPI_FUNC(void) _PySeqLock_LockWrite(_PySeqLock *seqlock); // Unlock the sequence lock and move to the next sequence number. PyAPI_FUNC(void) _PySeqLock_UnlockWrite(_PySeqLock *seqlock); -// Abandon the current update indicating that no mutations have occured +// Abandon the current update indicating that no mutations have occurred // and restore the previous sequence value. PyAPI_FUNC(void) _PySeqLock_AbandonWrite(_PySeqLock *seqlock); diff --git a/Include/internal/pycore_long.h b/Include/internal/pycore_long.h index ec27df9e416c58c..f04f66d053bab9d 100644 --- a/Include/internal/pycore_long.h +++ b/Include/internal/pycore_long.h @@ -121,9 +121,9 @@ PyAPI_DATA(PyObject*) _PyLong_Rshift(PyObject *, size_t); // Export for 'math' shared extension PyAPI_DATA(PyObject*) _PyLong_Lshift(PyObject *, size_t); -extern PyObject* _PyLong_Add(PyLongObject *left, PyLongObject *right); -extern PyObject* _PyLong_Multiply(PyLongObject *left, PyLongObject *right); -extern PyObject* _PyLong_Subtract(PyLongObject *left, PyLongObject *right); +PyAPI_FUNC(PyObject*) _PyLong_Add(PyLongObject *left, PyLongObject *right); +PyAPI_FUNC(PyObject*) _PyLong_Multiply(PyLongObject *left, PyLongObject *right); +PyAPI_FUNC(PyObject*) _PyLong_Subtract(PyLongObject *left, PyLongObject *right); // Export for 'binascii' shared extension. PyAPI_DATA(unsigned char) _PyLong_DigitValue[256]; diff --git a/Include/internal/pycore_mimalloc.h b/Include/internal/pycore_mimalloc.h index 14c98108ec131e0..10d451398f14107 100644 --- a/Include/internal/pycore_mimalloc.h +++ b/Include/internal/pycore_mimalloc.h @@ -27,11 +27,15 @@ typedef enum { # define MI_DEBUG_FREED PYMEM_DEADBYTE # define MI_DEBUG_PADDING PYMEM_FORBIDDENBYTE #ifdef Py_DEBUG -# define MI_DEBUG 1 +# define MI_DEBUG 2 #else # define MI_DEBUG 0 #endif +#ifdef _Py_THREAD_SANITIZER +# define MI_TSAN 1 +#endif + #include "mimalloc.h" #include "mimalloc/types.h" #include "mimalloc/internal.h" @@ -48,6 +52,7 @@ struct _mimalloc_thread_state { mi_heap_t *current_object_heap; mi_heap_t heaps[_Py_MIMALLOC_HEAP_COUNT]; mi_tld_t tld; + struct llist_node page_list; }; #endif diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 34a83ea228e8b10..9809f5f2e0271ae 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -73,7 +73,7 @@ PyAPI_FUNC(int) _PyObject_IsFreed(PyObject *); .ob_size = size \ } -extern void _Py_NO_RETURN _Py_FatalRefcountErrorFunc( +PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalRefcountErrorFunc( const char *func, const char *message); @@ -684,7 +684,7 @@ PyAPI_FUNC(PyObject*) _PyObject_LookupSpecial(PyObject *, PyObject *); extern int _PyObject_IsAbstract(PyObject *); -extern int _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method); +PyAPI_FUNC(int) _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method); extern PyObject* _PyObject_NextNotImplemented(PyObject *); // Pickle support. diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index ab34366ab1066c4..f754de3706c8124 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -116,7 +116,7 @@ int _PyOpcode_num_popped(int opcode, int oparg) { case CALL_LEN: return 2 + oparg; case CALL_LIST_APPEND: - return 2 + oparg; + return 3; case CALL_METHOD_DESCRIPTOR_FAST: return 2 + oparg; case CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: @@ -130,11 +130,11 @@ int _PyOpcode_num_popped(int opcode, int oparg) { case CALL_PY_WITH_DEFAULTS: return 2 + oparg; case CALL_STR_1: - return 2 + oparg; + return 3; case CALL_TUPLE_1: - return 2 + oparg; + return 3; case CALL_TYPE_1: - return 2 + oparg; + return 3; case CHECK_EG_MATCH: return 2; case CHECK_EXC_MATCH: @@ -151,6 +151,10 @@ int _PyOpcode_num_popped(int opcode, int oparg) { return 2; case CONTAINS_OP: return 2; + case CONTAINS_OP_DICT: + return 2; + case CONTAINS_OP_SET: + return 2; case CONVERT_VALUE: return 1; case COPY: @@ -576,6 +580,10 @@ int _PyOpcode_num_pushed(int opcode, int oparg) { return 1; case CONTAINS_OP: return 1; + case CONTAINS_OP_DICT: + return 1; + case CONTAINS_OP_SET: + return 1; case CONVERT_VALUE: return 1; case COPY: @@ -871,7 +879,7 @@ int _PyOpcode_num_pushed(int opcode, int oparg) { case UNPACK_SEQUENCE_TUPLE: return oparg; case UNPACK_SEQUENCE_TWO_TUPLE: - return oparg; + return 2; case WITH_EXCEPT_START: return 5; case YIELD_VALUE: @@ -951,7 +959,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[268] = { [BINARY_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, [BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG }, - [BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG }, [BINARY_OP_INPLACE_ADD_UNICODE] = { true, INSTR_FMT_IXC, HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_MULTIPLY_FLOAT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, [BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG }, @@ -965,12 +973,12 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[268] = { [BINARY_SUBSCR_STR_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, [BINARY_SUBSCR_TUPLE_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG }, [BUILD_CONST_KEY_MAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BUILD_LIST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BUILD_LIST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, [BUILD_MAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BUILD_SET] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BUILD_SLICE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BUILD_STRING] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [BUILD_TUPLE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BUILD_SLICE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, + [BUILD_STRING] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, + [BUILD_TUPLE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, [CACHE] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, [CALL] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [CALL_ALLOC_AND_ENTER_INIT] = { true, INSTR_FMT_IBC00, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, @@ -1000,9 +1008,11 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[268] = { [CLEANUP_THROW] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [COMPARE_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [COMPARE_OP_FLOAT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, - [COMPARE_OP_INT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, - [COMPARE_OP_STR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, - [CONTAINS_OP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [COMPARE_OP_INT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, + [COMPARE_OP_STR] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, + [CONTAINS_OP] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CONTAINS_OP_DICT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [CONTAINS_OP_SET] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [CONVERT_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG }, [COPY] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_PURE_FLAG }, [COPY_FREE_VARS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, @@ -1025,7 +1035,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[268] = { [FOR_ITER] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [FOR_ITER_GEN] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [FOR_ITER_LIST] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG }, - [FOR_ITER_RANGE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [FOR_ITER_RANGE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG }, [FOR_ITER_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_DEOPT_FLAG }, [GET_AITER] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [GET_ANEXT] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, @@ -1065,9 +1075,9 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[268] = { [LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, - [LOAD_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_METHOD_NO_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_METHOD_WITH_VALUES] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, + [LOAD_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, + [LOAD_ATTR_METHOD_NO_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, + [LOAD_ATTR_METHOD_WITH_VALUES] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, [LOAD_ATTR_MODULE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, @@ -1122,7 +1132,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[268] = { [SET_FUNCTION_ATTRIBUTE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ESCAPES_FLAG }, [SET_UPDATE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [STORE_ATTR] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [STORE_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IXC000, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, + [STORE_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IXC000, HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, [STORE_ATTR_SLOT] = { true, INSTR_FMT_IXC000, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, [STORE_ATTR_WITH_HINT] = { true, INSTR_FMT_IBC000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [STORE_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ESCAPES_FLAG }, @@ -1202,21 +1212,21 @@ _PyOpcode_macro_expansion[256] = { [BUILD_STRING] = { .nuops = 1, .uops = { { _BUILD_STRING, 0, 0 } } }, [BUILD_TUPLE] = { .nuops = 1, .uops = { { _BUILD_TUPLE, 0, 0 } } }, [CALL_BOUND_METHOD_EXACT_ARGS] = { .nuops = 8, .uops = { { _CHECK_PEP_523, 0, 0 }, { _CHECK_CALL_BOUND_METHOD_EXACT_ARGS, 0, 0 }, { _INIT_CALL_BOUND_METHOD_EXACT_ARGS, 0, 0 }, { _CHECK_FUNCTION_EXACT_ARGS, 2, 1 }, { _CHECK_STACK_SPACE, 0, 0 }, { _INIT_CALL_PY_EXACT_ARGS, 0, 0 }, { _SAVE_RETURN_OFFSET, 7, 3 }, { _PUSH_FRAME, 0, 0 } } }, - [CALL_BUILTIN_CLASS] = { .nuops = 1, .uops = { { _CALL_BUILTIN_CLASS, 0, 0 } } }, - [CALL_BUILTIN_FAST] = { .nuops = 1, .uops = { { _CALL_BUILTIN_FAST, 0, 0 } } }, - [CALL_BUILTIN_FAST_WITH_KEYWORDS] = { .nuops = 1, .uops = { { _CALL_BUILTIN_FAST_WITH_KEYWORDS, 0, 0 } } }, - [CALL_BUILTIN_O] = { .nuops = 1, .uops = { { _CALL_BUILTIN_O, 0, 0 } } }, + [CALL_BUILTIN_CLASS] = { .nuops = 2, .uops = { { _CALL_BUILTIN_CLASS, 0, 0 }, { _CHECK_PERIODIC, 0, 0 } } }, + [CALL_BUILTIN_FAST] = { .nuops = 2, .uops = { { _CALL_BUILTIN_FAST, 0, 0 }, { _CHECK_PERIODIC, 0, 0 } } }, + [CALL_BUILTIN_FAST_WITH_KEYWORDS] = { .nuops = 2, .uops = { { _CALL_BUILTIN_FAST_WITH_KEYWORDS, 0, 0 }, { _CHECK_PERIODIC, 0, 0 } } }, + [CALL_BUILTIN_O] = { .nuops = 2, .uops = { { _CALL_BUILTIN_O, 0, 0 }, { _CHECK_PERIODIC, 0, 0 } } }, [CALL_INTRINSIC_1] = { .nuops = 1, .uops = { { _CALL_INTRINSIC_1, 0, 0 } } }, [CALL_INTRINSIC_2] = { .nuops = 1, .uops = { { _CALL_INTRINSIC_2, 0, 0 } } }, [CALL_ISINSTANCE] = { .nuops = 1, .uops = { { _CALL_ISINSTANCE, 0, 0 } } }, [CALL_LEN] = { .nuops = 1, .uops = { { _CALL_LEN, 0, 0 } } }, - [CALL_METHOD_DESCRIPTOR_FAST] = { .nuops = 1, .uops = { { _CALL_METHOD_DESCRIPTOR_FAST, 0, 0 } } }, - [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { .nuops = 1, .uops = { { _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS, 0, 0 } } }, - [CALL_METHOD_DESCRIPTOR_NOARGS] = { .nuops = 1, .uops = { { _CALL_METHOD_DESCRIPTOR_NOARGS, 0, 0 } } }, - [CALL_METHOD_DESCRIPTOR_O] = { .nuops = 1, .uops = { { _CALL_METHOD_DESCRIPTOR_O, 0, 0 } } }, + [CALL_METHOD_DESCRIPTOR_FAST] = { .nuops = 2, .uops = { { _CALL_METHOD_DESCRIPTOR_FAST, 0, 0 }, { _CHECK_PERIODIC, 0, 0 } } }, + [CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = { .nuops = 2, .uops = { { _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS, 0, 0 }, { _CHECK_PERIODIC, 0, 0 } } }, + [CALL_METHOD_DESCRIPTOR_NOARGS] = { .nuops = 2, .uops = { { _CALL_METHOD_DESCRIPTOR_NOARGS, 0, 0 }, { _CHECK_PERIODIC, 0, 0 } } }, + [CALL_METHOD_DESCRIPTOR_O] = { .nuops = 2, .uops = { { _CALL_METHOD_DESCRIPTOR_O, 0, 0 }, { _CHECK_PERIODIC, 0, 0 } } }, [CALL_PY_EXACT_ARGS] = { .nuops = 6, .uops = { { _CHECK_PEP_523, 0, 0 }, { _CHECK_FUNCTION_EXACT_ARGS, 2, 1 }, { _CHECK_STACK_SPACE, 0, 0 }, { _INIT_CALL_PY_EXACT_ARGS, 0, 0 }, { _SAVE_RETURN_OFFSET, 7, 3 }, { _PUSH_FRAME, 0, 0 } } }, - [CALL_STR_1] = { .nuops = 1, .uops = { { _CALL_STR_1, 0, 0 } } }, - [CALL_TUPLE_1] = { .nuops = 1, .uops = { { _CALL_TUPLE_1, 0, 0 } } }, + [CALL_STR_1] = { .nuops = 2, .uops = { { _CALL_STR_1, 0, 0 }, { _CHECK_PERIODIC, 0, 0 } } }, + [CALL_TUPLE_1] = { .nuops = 2, .uops = { { _CALL_TUPLE_1, 0, 0 }, { _CHECK_PERIODIC, 0, 0 } } }, [CALL_TYPE_1] = { .nuops = 1, .uops = { { _CALL_TYPE_1, 0, 0 } } }, [CHECK_EG_MATCH] = { .nuops = 1, .uops = { { _CHECK_EG_MATCH, 0, 0 } } }, [CHECK_EXC_MATCH] = { .nuops = 1, .uops = { { _CHECK_EXC_MATCH, 0, 0 } } }, @@ -1225,6 +1235,8 @@ _PyOpcode_macro_expansion[256] = { [COMPARE_OP_INT] = { .nuops = 2, .uops = { { _GUARD_BOTH_INT, 0, 0 }, { _COMPARE_OP_INT, 0, 0 } } }, [COMPARE_OP_STR] = { .nuops = 2, .uops = { { _GUARD_BOTH_UNICODE, 0, 0 }, { _COMPARE_OP_STR, 0, 0 } } }, [CONTAINS_OP] = { .nuops = 1, .uops = { { _CONTAINS_OP, 0, 0 } } }, + [CONTAINS_OP_DICT] = { .nuops = 1, .uops = { { _CONTAINS_OP_DICT, 0, 0 } } }, + [CONTAINS_OP_SET] = { .nuops = 1, .uops = { { _CONTAINS_OP_SET, 0, 0 } } }, [CONVERT_VALUE] = { .nuops = 1, .uops = { { _CONVERT_VALUE, 0, 0 } } }, [COPY] = { .nuops = 1, .uops = { { _COPY, 0, 0 } } }, [COPY_FREE_VARS] = { .nuops = 1, .uops = { { _COPY_FREE_VARS, 0, 0 } } }, @@ -1320,7 +1332,7 @@ _PyOpcode_macro_expansion[256] = { [STORE_SUBSCR_LIST_INT] = { .nuops = 1, .uops = { { _STORE_SUBSCR_LIST_INT, 0, 0 } } }, [SWAP] = { .nuops = 1, .uops = { { _SWAP, 0, 0 } } }, [TO_BOOL] = { .nuops = 1, .uops = { { _TO_BOOL, 0, 0 } } }, - [TO_BOOL_ALWAYS_TRUE] = { .nuops = 1, .uops = { { _TO_BOOL_ALWAYS_TRUE, 2, 1 } } }, + [TO_BOOL_ALWAYS_TRUE] = { .nuops = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _REPLACE_WITH_TRUE, 0, 0 } } }, [TO_BOOL_BOOL] = { .nuops = 1, .uops = { { _TO_BOOL_BOOL, 0, 0 } } }, [TO_BOOL_INT] = { .nuops = 1, .uops = { { _TO_BOOL_INT, 0, 0 } } }, [TO_BOOL_LIST] = { .nuops = 1, .uops = { { _TO_BOOL_LIST, 0, 0 } } }, @@ -1398,6 +1410,8 @@ const char *_PyOpcode_OpName[268] = { [COMPARE_OP_INT] = "COMPARE_OP_INT", [COMPARE_OP_STR] = "COMPARE_OP_STR", [CONTAINS_OP] = "CONTAINS_OP", + [CONTAINS_OP_DICT] = "CONTAINS_OP_DICT", + [CONTAINS_OP_SET] = "CONTAINS_OP_SET", [CONVERT_VALUE] = "CONVERT_VALUE", [COPY] = "COPY", [COPY_FREE_VARS] = "COPY_FREE_VARS", @@ -1578,6 +1592,7 @@ const uint8_t _PyOpcode_Caches[256] = { [LOAD_SUPER_ATTR] = 1, [LOAD_ATTR] = 9, [COMPARE_OP] = 1, + [CONTAINS_OP] = 1, [POP_JUMP_IF_TRUE] = 1, [POP_JUMP_IF_FALSE] = 1, [POP_JUMP_IF_NONE] = 1, @@ -1648,6 +1663,8 @@ const uint8_t _PyOpcode_Deopt[256] = { [COMPARE_OP_INT] = COMPARE_OP, [COMPARE_OP_STR] = COMPARE_OP, [CONTAINS_OP] = CONTAINS_OP, + [CONTAINS_OP_DICT] = CONTAINS_OP, + [CONTAINS_OP_SET] = CONTAINS_OP, [CONVERT_VALUE] = CONVERT_VALUE, [COPY] = COPY, [COPY_FREE_VARS] = COPY_FREE_VARS, @@ -1834,8 +1851,6 @@ const uint8_t _PyOpcode_Deopt[256] = { case 146: \ case 147: \ case 148: \ - case 219: \ - case 220: \ case 221: \ case 222: \ case 223: \ diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index eee71c700d4904a..fcead4d8714870b 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -9,6 +9,7 @@ extern "C" { #endif #include "pycore_uop_ids.h" +#include // This is the length of the trace we project initially. #define UOP_MAX_TRACE_LENGTH 512 @@ -25,6 +26,95 @@ extern PyTypeObject _PyDefaultOptimizer_Type; extern PyTypeObject _PyUOpExecutor_Type; extern PyTypeObject _PyUOpOptimizer_Type; +/* Symbols */ +/* See explanation in optimizer_symbols.c */ + +struct _Py_UopsSymbol { + int flags; // 0 bits: Top; 2 or more bits: Bottom + PyTypeObject *typ; // Borrowed reference + PyObject *const_val; // Owned reference (!) +}; + +// Holds locals, stack, locals, stack ... co_consts (in that order) +#define MAX_ABSTRACT_INTERP_SIZE 4096 + +#define TY_ARENA_SIZE (UOP_MAX_TRACE_LENGTH * 5) + +// Need extras for root frame and for overflow frame (see TRACE_STACK_PUSH()) +#define MAX_ABSTRACT_FRAME_DEPTH (TRACE_STACK_SIZE + 2) + +typedef struct _Py_UopsSymbol _Py_UopsSymbol; + +struct _Py_UOpsAbstractFrame { + // Max stacklen + int stack_len; + int locals_len; + + _Py_UopsSymbol **stack_pointer; + _Py_UopsSymbol **stack; + _Py_UopsSymbol **locals; +}; + +typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame; + +typedef struct ty_arena { + int ty_curr_number; + int ty_max_number; + _Py_UopsSymbol arena[TY_ARENA_SIZE]; +} ty_arena; + +struct _Py_UOpsContext { + PyObject_HEAD + // The current "executing" frame. + _Py_UOpsAbstractFrame *frame; + _Py_UOpsAbstractFrame frames[MAX_ABSTRACT_FRAME_DEPTH]; + int curr_frame_depth; + + // Arena for the symbolic types. + ty_arena t_arena; + + _Py_UopsSymbol **n_consumed; + _Py_UopsSymbol **limit; + _Py_UopsSymbol *locals_and_stack[MAX_ABSTRACT_INTERP_SIZE]; +}; + +typedef struct _Py_UOpsContext _Py_UOpsContext; + +extern bool _Py_uop_sym_is_null(_Py_UopsSymbol *sym); +extern bool _Py_uop_sym_is_not_null(_Py_UopsSymbol *sym); +extern bool _Py_uop_sym_is_const(_Py_UopsSymbol *sym); +extern PyObject *_Py_uop_sym_get_const(_Py_UopsSymbol *sym); +extern _Py_UopsSymbol *_Py_uop_sym_new_unknown(_Py_UOpsContext *ctx); +extern _Py_UopsSymbol *_Py_uop_sym_new_not_null(_Py_UOpsContext *ctx); +extern _Py_UopsSymbol *_Py_uop_sym_new_type( + _Py_UOpsContext *ctx, PyTypeObject *typ); +extern _Py_UopsSymbol *_Py_uop_sym_new_const(_Py_UOpsContext *ctx, PyObject *const_val); +extern _Py_UopsSymbol *_Py_uop_sym_new_null(_Py_UOpsContext *ctx); +extern bool _Py_uop_sym_has_type(_Py_UopsSymbol *sym); +extern bool _Py_uop_sym_matches_type(_Py_UopsSymbol *sym, PyTypeObject *typ); +extern bool _Py_uop_sym_set_null(_Py_UopsSymbol *sym); +extern bool _Py_uop_sym_set_non_null(_Py_UopsSymbol *sym); +extern bool _Py_uop_sym_set_type(_Py_UopsSymbol *sym, PyTypeObject *typ); +extern bool _Py_uop_sym_set_const(_Py_UopsSymbol *sym, PyObject *const_val); +extern bool _Py_uop_sym_is_bottom(_Py_UopsSymbol *sym); +extern int _Py_uop_sym_truthiness(_Py_UopsSymbol *sym); + + +extern int _Py_uop_abstractcontext_init(_Py_UOpsContext *ctx); +extern void _Py_uop_abstractcontext_fini(_Py_UOpsContext *ctx); + +extern _Py_UOpsAbstractFrame *_Py_uop_frame_new( + _Py_UOpsContext *ctx, + PyCodeObject *co, + _Py_UopsSymbol **localsplus_start, + int n_locals_already_filled, + int curr_stackentries); +extern int _Py_uop_frame_pop(_Py_UOpsContext *ctx); + +PyAPI_FUNC(PyObject *) _Py_uop_symbols_test(PyObject *self, PyObject *ignored); + +PyAPI_FUNC(int) _PyOptimizer_Optimize(_PyInterpreterFrame *frame, _Py_CODEUNIT *start, PyObject **stack_pointer, _PyExecutorObject **exec_ptr); + #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_pyerrors.h b/Include/internal/pycore_pyerrors.h index 0f16fb894d17e12..683d87a0d0b1292 100644 --- a/Include/internal/pycore_pyerrors.h +++ b/Include/internal/pycore_pyerrors.h @@ -95,7 +95,7 @@ extern void _PyErr_Fetch( extern PyObject* _PyErr_GetRaisedException(PyThreadState *tstate); -extern int _PyErr_ExceptionMatches( +PyAPI_FUNC(int) _PyErr_ExceptionMatches( PyThreadState *tstate, PyObject *exc); @@ -114,18 +114,18 @@ extern void _PyErr_SetObject( extern void _PyErr_ChainStackItem(void); -extern void _PyErr_Clear(PyThreadState *tstate); +PyAPI_FUNC(void) _PyErr_Clear(PyThreadState *tstate); extern void _PyErr_SetNone(PyThreadState *tstate, PyObject *exception); extern PyObject* _PyErr_NoMemory(PyThreadState *tstate); -extern void _PyErr_SetString( +PyAPI_FUNC(void) _PyErr_SetString( PyThreadState *tstate, PyObject *exception, const char *string); -extern PyObject* _PyErr_Format( +PyAPI_FUNC(PyObject*) _PyErr_Format( PyThreadState *tstate, PyObject *exception, const char *format, @@ -167,9 +167,6 @@ void _PyErr_FormatNote(const char *format, ...); Py_DEPRECATED(3.12) extern void _PyErr_ChainExceptions(PyObject *, PyObject *, PyObject *); -// Export for '_zoneinfo' shared extension -PyAPI_FUNC(void) _PyErr_ChainExceptions1(PyObject *); - #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_pymem.h b/Include/internal/pycore_pymem.h index 1aea91abc5d69f4..dd6b0762370c926 100644 --- a/Include/internal/pycore_pymem.h +++ b/Include/internal/pycore_pymem.h @@ -119,6 +119,9 @@ extern int _PyMem_DebugEnabled(void); // Enqueue a pointer to be freed possibly after some delay. extern void _PyMem_FreeDelayed(void *ptr); +// Enqueue an object to be freed possibly after some delay +extern void _PyObject_FreeDelayed(void *ptr); + // Periodically process delayed free requests. extern void _PyMem_ProcessDelayed(PyThreadState *tstate); diff --git a/Include/internal/pycore_pythread.h b/Include/internal/pycore_pythread.h index d017d4ff308aa8d..f032cb973886572 100644 --- a/Include/internal/pycore_pythread.h +++ b/Include/internal/pycore_pythread.h @@ -46,7 +46,8 @@ extern "C" { #if defined(HAVE_PTHREAD_STUBS) -#include // bool +#include "cpython/pthread_stubs.h" // PTHREAD_KEYS_MAX +#include // bool // pthread_key struct py_stub_tls_entry { @@ -77,7 +78,7 @@ struct _pythread_runtime_state { } stubs; #endif - // Linked list of ThreadHandleObjects + // Linked list of ThreadHandles struct llist_node handles; }; diff --git a/Include/internal/pycore_qsbr.h b/Include/internal/pycore_qsbr.h index 475f00deedc2265..c3680a205542f77 100644 --- a/Include/internal/pycore_qsbr.h +++ b/Include/internal/pycore_qsbr.h @@ -29,6 +29,12 @@ extern "C" { #define QSBR_INITIAL 1 #define QSBR_INCR 2 +// Wrap-around safe comparison. This is a holdover from the FreeBSD +// implementation, which uses 32-bit sequence numbers. We currently use 64-bit +// sequence numbers, so wrap-around is unlikely. +#define QSBR_LT(a, b) ((int64_t)((a)-(b)) < 0) +#define QSBR_LEQ(a, b) ((int64_t)((a)-(b)) <= 0) + struct _qsbr_shared; struct _PyThreadStateImpl; // forward declare to avoid circular dependency @@ -89,6 +95,15 @@ _Py_qsbr_quiescent_state(struct _qsbr_thread_state *qsbr) _Py_atomic_store_uint64_release(&qsbr->seq, seq); } +// Have the read sequences advanced to the given goal? Like `_Py_qsbr_poll()`, +// but does not perform a scan of threads. +static inline bool +_Py_qbsr_goal_reached(struct _qsbr_thread_state *qsbr, uint64_t goal) +{ + uint64_t rd_seq = _Py_atomic_load_uint64(&qsbr->shared->rd_seq); + return QSBR_LEQ(goal, rd_seq); +} + // Advance the write sequence and return the new goal. This should be called // after data is removed. The returned goal is used with `_Py_qsbr_poll()` to // determine when it is safe to reclaim (free) the memory. diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index 0c9c59e85b2fcfc..dc6f6f100f7a922 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -55,74 +55,81 @@ typedef struct _Py_DebugOffsets { uint64_t version; // Runtime state offset; struct _runtime_state { - off_t finalizing; - off_t interpreters_head; + uint64_t finalizing; + uint64_t interpreters_head; } runtime_state; // Interpreter state offset; struct _interpreter_state { - off_t next; - off_t threads_head; - off_t gc; - off_t imports_modules; - off_t sysdict; - off_t builtins; - off_t ceval_gil; - off_t gil_runtime_state_locked; - off_t gil_runtime_state_holder; + uint64_t next; + uint64_t threads_head; + uint64_t gc; + uint64_t imports_modules; + uint64_t sysdict; + uint64_t builtins; + uint64_t ceval_gil; + uint64_t gil_runtime_state_locked; + uint64_t gil_runtime_state_holder; } interpreter_state; // Thread state offset; struct _thread_state{ - off_t prev; - off_t next; - off_t interp; - off_t current_frame; - off_t thread_id; - off_t native_thread_id; + uint64_t prev; + uint64_t next; + uint64_t interp; + uint64_t current_frame; + uint64_t thread_id; + uint64_t native_thread_id; } thread_state; // InterpreterFrame offset; struct _interpreter_frame { - off_t previous; - off_t executable; - off_t instr_ptr; - off_t localsplus; - off_t owner; + uint64_t previous; + uint64_t executable; + uint64_t instr_ptr; + uint64_t localsplus; + uint64_t owner; } interpreter_frame; // CFrame offset; struct _cframe { - off_t current_frame; - off_t previous; + uint64_t current_frame; + uint64_t previous; } cframe; // Code object offset; struct _code_object { - off_t filename; - off_t name; - off_t linetable; - off_t firstlineno; - off_t argcount; - off_t localsplusnames; - off_t localspluskinds; - off_t co_code_adaptive; + uint64_t filename; + uint64_t name; + uint64_t linetable; + uint64_t firstlineno; + uint64_t argcount; + uint64_t localsplusnames; + uint64_t localspluskinds; + uint64_t co_code_adaptive; } code_object; // PyObject offset; struct _pyobject { - off_t ob_type; + uint64_t ob_type; } pyobject; // PyTypeObject object offset; struct _type_object { - off_t tp_name; + uint64_t tp_name; } type_object; // PyTuple object offset; struct _tuple_object { - off_t ob_item; + uint64_t ob_item; } tuple_object; + + // Unicode object offset; + struct _unicode_object { + uint64_t state; + uint64_t length; + size_t asciiobject_size; + } unicode_object; } _Py_DebugOffsets; /* Full Python runtime state */ diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index d093047d4bc09da..cc47b9a82e28792 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -83,6 +83,11 @@ extern PyTypeObject _PyExc_MemoryError; .tuple_object = { \ .ob_item = offsetof(PyTupleObject, ob_item), \ }, \ + .unicode_object = { \ + .state = offsetof(PyUnicodeObject, _base._base.state), \ + .length = offsetof(PyUnicodeObject, _base._base.length), \ + .asciiobject_size = sizeof(PyASCIIObject), \ + }, \ }, \ .allocators = { \ .standard = _pymem_allocators_standard_INIT(runtime), \ diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index a9d514856dab1ff..658bf8030f661d9 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -751,6 +751,7 @@ extern "C" { INIT_ID(_check_retval_), \ INIT_ID(_dealloc_warn), \ INIT_ID(_feature_version), \ + INIT_ID(_field_types), \ INIT_ID(_fields_), \ INIT_ID(_finalizing), \ INIT_ID(_find_and_load), \ @@ -980,7 +981,6 @@ extern "C" { INIT_ID(hi), \ INIT_ID(hook), \ INIT_ID(hour), \ - INIT_ID(id), \ INIT_ID(ident), \ INIT_ID(identity_hint), \ INIT_ID(ignore), \ diff --git a/Include/internal/pycore_setobject.h b/Include/internal/pycore_setobject.h index 34a00e6d45fe698..c4ec3ceb17eba6b 100644 --- a/Include/internal/pycore_setobject.h +++ b/Include/internal/pycore_setobject.h @@ -21,6 +21,8 @@ PyAPI_FUNC(int) _PySet_Update(PyObject *set, PyObject *iterable); // Export for the gdb plugin's (python-gdb.py) benefit PyAPI_DATA(PyObject *) _PySet_Dummy; +PyAPI_FUNC(int) _PySet_Contains(PySetObject *so, PyObject *key); + #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_sliceobject.h b/Include/internal/pycore_sliceobject.h index 89086f67683a2f5..ba8b1f1cb27dee3 100644 --- a/Include/internal/pycore_sliceobject.h +++ b/Include/internal/pycore_sliceobject.h @@ -11,7 +11,7 @@ extern "C" { /* runtime lifecycle */ -extern PyObject * +PyAPI_FUNC(PyObject *) _PyBuildSlice_ConsumeRefs(PyObject *start, PyObject *stop); #ifdef __cplusplus diff --git a/Include/internal/pycore_time.h b/Include/internal/pycore_time.h index 40b28e0ba221aee..138d60fdb698069 100644 --- a/Include/internal/pycore_time.h +++ b/Include/internal/pycore_time.h @@ -132,8 +132,10 @@ PyAPI_FUNC(int) _PyTime_ObjectToTimespec( PyAPI_FUNC(PyTime_t) _PyTime_FromSeconds(int seconds); // Create a timestamp from a number of seconds in double. -// Export for '_socket' shared extension. -PyAPI_FUNC(PyTime_t) _PyTime_FromSecondsDouble(double seconds, _PyTime_round_t round); +extern int _PyTime_FromSecondsDouble( + double seconds, + _PyTime_round_t round, + PyTime_t *result); // Macro to create a timestamp from a number of seconds, no integer overflow. // Only use the macro for small values, prefer _PyTime_FromSeconds(). diff --git a/Include/internal/pycore_tuple.h b/Include/internal/pycore_tuple.h index 4605f355ccbc38f..14a9e42c3a324cd 100644 --- a/Include/internal/pycore_tuple.h +++ b/Include/internal/pycore_tuple.h @@ -21,7 +21,7 @@ extern PyStatus _PyTuple_InitGlobalObjects(PyInterpreterState *); #define _PyTuple_ITEMS(op) _Py_RVALUE(_PyTuple_CAST(op)->ob_item) extern PyObject *_PyTuple_FromArray(PyObject *const *, Py_ssize_t); -extern PyObject *_PyTuple_FromArraySteal(PyObject *const *, Py_ssize_t); +PyAPI_FUNC(PyObject *)_PyTuple_FromArraySteal(PyObject *const *, Py_ssize_t); typedef struct { PyObject_HEAD diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 9134ab45cd00395..8a25935f308178f 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -147,12 +147,10 @@ extern PyObject* _Py_slot_tp_getattr_hook(PyObject *self, PyObject *name); extern PyTypeObject _PyBufferWrapper_Type; -extern PyObject* _PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj, +PyAPI_FUNC(PyObject*) _PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj, PyObject *name, int *meth_found); - -// This is exported for the _testinternalcapi module. -PyAPI_FUNC(PyObject *) _PyType_GetModuleName(PyTypeObject *); +extern PyObject* _PyType_GetFullyQualifiedName(PyTypeObject *type, char sep); #ifdef __cplusplus diff --git a/Include/internal/pycore_unicodeobject.h b/Include/internal/pycore_unicodeobject.h index 7ee540154b23d88..fea5ceea0954f42 100644 --- a/Include/internal/pycore_unicodeobject.h +++ b/Include/internal/pycore_unicodeobject.h @@ -31,7 +31,7 @@ PyAPI_FUNC(int) _PyUnicode_CheckConsistency( PyObject *op, int check_content); -extern void _PyUnicode_ExactDealloc(PyObject *op); +PyAPI_FUNC(void) _PyUnicode_ExactDealloc(PyObject *op); extern Py_ssize_t _PyUnicode_InternedSize(void); // Get a copy of a Unicode string. @@ -202,7 +202,7 @@ PyAPI_FUNC(PyObject*) _PyUnicode_TransformDecimalAndSpaceToASCII( /* --- Methods & Slots ---------------------------------------------------- */ -extern PyObject* _PyUnicode_JoinArray( +PyAPI_FUNC(PyObject*) _PyUnicode_JoinArray( PyObject *separator, PyObject *const *items, Py_ssize_t seqlen diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index f3b064e2a2cb258..d72353d56eae608 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -567,6 +567,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(_feature_version); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(_field_types); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(_fields_); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -1254,9 +1257,6 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(hour); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); - string = &_Py_ID(id); - assert(_PyUnicode_CheckConsistency(string, 1)); - _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(ident); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Include/internal/pycore_unionobject.h b/Include/internal/pycore_unionobject.h index 87264635b6e1cfe..6ece7134cdeca03 100644 --- a/Include/internal/pycore_unionobject.h +++ b/Include/internal/pycore_unionobject.h @@ -8,9 +8,11 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif -extern PyTypeObject _PyUnion_Type; +// For extensions created by test_peg_generator +PyAPI_DATA(PyTypeObject) _PyUnion_Type; +PyAPI_FUNC(PyObject *) _Py_union_type_or(PyObject *, PyObject *); + #define _PyUnion_Check(op) Py_IS_TYPE((op), &_PyUnion_Type) -extern PyObject *_Py_union_type_or(PyObject *, PyObject *); #define _PyGenericAlias_Check(op) PyObject_TypeCheck((op), &Py_GenericAliasType) extern PyObject *_Py_subs_parameters(PyObject *, PyObject *, PyObject *, PyObject *); diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index 3c133d97b2f03eb..b569b80c5f110ad 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -37,45 +37,47 @@ extern "C" { #define _BUILD_TUPLE BUILD_TUPLE #define _CALL 311 #define _CALL_ALLOC_AND_ENTER_INIT CALL_ALLOC_AND_ENTER_INIT -#define _CALL_BUILTIN_CLASS CALL_BUILTIN_CLASS -#define _CALL_BUILTIN_FAST CALL_BUILTIN_FAST -#define _CALL_BUILTIN_FAST_WITH_KEYWORDS CALL_BUILTIN_FAST_WITH_KEYWORDS -#define _CALL_BUILTIN_O CALL_BUILTIN_O +#define _CALL_BUILTIN_CLASS 312 +#define _CALL_BUILTIN_FAST 313 +#define _CALL_BUILTIN_FAST_WITH_KEYWORDS 314 +#define _CALL_BUILTIN_O 315 #define _CALL_FUNCTION_EX CALL_FUNCTION_EX #define _CALL_INTRINSIC_1 CALL_INTRINSIC_1 #define _CALL_INTRINSIC_2 CALL_INTRINSIC_2 #define _CALL_ISINSTANCE CALL_ISINSTANCE #define _CALL_KW CALL_KW #define _CALL_LEN CALL_LEN -#define _CALL_METHOD_DESCRIPTOR_FAST CALL_METHOD_DESCRIPTOR_FAST -#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS -#define _CALL_METHOD_DESCRIPTOR_NOARGS CALL_METHOD_DESCRIPTOR_NOARGS -#define _CALL_METHOD_DESCRIPTOR_O CALL_METHOD_DESCRIPTOR_O +#define _CALL_METHOD_DESCRIPTOR_FAST 316 +#define _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS 317 +#define _CALL_METHOD_DESCRIPTOR_NOARGS 318 +#define _CALL_METHOD_DESCRIPTOR_O 319 #define _CALL_PY_WITH_DEFAULTS CALL_PY_WITH_DEFAULTS -#define _CALL_STR_1 CALL_STR_1 -#define _CALL_TUPLE_1 CALL_TUPLE_1 +#define _CALL_STR_1 320 +#define _CALL_TUPLE_1 321 #define _CALL_TYPE_1 CALL_TYPE_1 -#define _CHECK_ATTR_CLASS 312 -#define _CHECK_ATTR_METHOD_LAZY_DICT 313 -#define _CHECK_ATTR_MODULE 314 -#define _CHECK_ATTR_WITH_HINT 315 -#define _CHECK_BUILTINS 316 -#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS 317 +#define _CHECK_ATTR_CLASS 322 +#define _CHECK_ATTR_METHOD_LAZY_DICT 323 +#define _CHECK_ATTR_MODULE 324 +#define _CHECK_ATTR_WITH_HINT 325 +#define _CHECK_CALL_BOUND_METHOD_EXACT_ARGS 326 #define _CHECK_EG_MATCH CHECK_EG_MATCH #define _CHECK_EXC_MATCH CHECK_EXC_MATCH -#define _CHECK_FUNCTION_EXACT_ARGS 318 -#define _CHECK_GLOBALS 319 -#define _CHECK_MANAGED_OBJECT_HAS_VALUES 320 -#define _CHECK_PEP_523 321 -#define _CHECK_STACK_SPACE 322 -#define _CHECK_VALIDITY 323 -#define _CHECK_VALIDITY_AND_SET_IP 324 -#define _COLD_EXIT 325 -#define _COMPARE_OP 326 -#define _COMPARE_OP_FLOAT 327 -#define _COMPARE_OP_INT 328 -#define _COMPARE_OP_STR 329 -#define _CONTAINS_OP CONTAINS_OP +#define _CHECK_FUNCTION 327 +#define _CHECK_FUNCTION_EXACT_ARGS 328 +#define _CHECK_MANAGED_OBJECT_HAS_VALUES 329 +#define _CHECK_PEP_523 330 +#define _CHECK_PERIODIC 331 +#define _CHECK_STACK_SPACE 332 +#define _CHECK_VALIDITY 333 +#define _CHECK_VALIDITY_AND_SET_IP 334 +#define _COLD_EXIT 335 +#define _COMPARE_OP 336 +#define _COMPARE_OP_FLOAT 337 +#define _COMPARE_OP_INT 338 +#define _COMPARE_OP_STR 339 +#define _CONTAINS_OP 340 +#define _CONTAINS_OP_DICT CONTAINS_OP_DICT +#define _CONTAINS_OP_SET CONTAINS_OP_SET #define _CONVERT_VALUE CONVERT_VALUE #define _COPY COPY #define _COPY_FREE_VARS COPY_FREE_VARS @@ -89,41 +91,41 @@ extern "C" { #define _DICT_UPDATE DICT_UPDATE #define _END_SEND END_SEND #define _EXIT_INIT_CHECK EXIT_INIT_CHECK -#define _FATAL_ERROR 330 +#define _FATAL_ERROR 341 #define _FORMAT_SIMPLE FORMAT_SIMPLE #define _FORMAT_WITH_SPEC FORMAT_WITH_SPEC -#define _FOR_ITER 331 +#define _FOR_ITER 342 #define _FOR_ITER_GEN FOR_ITER_GEN -#define _FOR_ITER_TIER_TWO 332 +#define _FOR_ITER_TIER_TWO 343 #define _GET_AITER GET_AITER #define _GET_ANEXT GET_ANEXT #define _GET_AWAITABLE GET_AWAITABLE #define _GET_ITER GET_ITER #define _GET_LEN GET_LEN #define _GET_YIELD_FROM_ITER GET_YIELD_FROM_ITER -#define _GUARD_BOTH_FLOAT 333 -#define _GUARD_BOTH_INT 334 -#define _GUARD_BOTH_UNICODE 335 -#define _GUARD_BUILTINS_VERSION 336 -#define _GUARD_DORV_VALUES 337 -#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT 338 -#define _GUARD_GLOBALS_VERSION 339 -#define _GUARD_IS_FALSE_POP 340 -#define _GUARD_IS_NONE_POP 341 -#define _GUARD_IS_NOT_NONE_POP 342 -#define _GUARD_IS_TRUE_POP 343 -#define _GUARD_KEYS_VERSION 344 -#define _GUARD_NOT_EXHAUSTED_LIST 345 -#define _GUARD_NOT_EXHAUSTED_RANGE 346 -#define _GUARD_NOT_EXHAUSTED_TUPLE 347 -#define _GUARD_TYPE_VERSION 348 -#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS 349 -#define _INIT_CALL_PY_EXACT_ARGS 350 -#define _INIT_CALL_PY_EXACT_ARGS_0 351 -#define _INIT_CALL_PY_EXACT_ARGS_1 352 -#define _INIT_CALL_PY_EXACT_ARGS_2 353 -#define _INIT_CALL_PY_EXACT_ARGS_3 354 -#define _INIT_CALL_PY_EXACT_ARGS_4 355 +#define _GUARD_BOTH_FLOAT 344 +#define _GUARD_BOTH_INT 345 +#define _GUARD_BOTH_UNICODE 346 +#define _GUARD_BUILTINS_VERSION 347 +#define _GUARD_DORV_VALUES 348 +#define _GUARD_DORV_VALUES_INST_ATTR_FROM_DICT 349 +#define _GUARD_GLOBALS_VERSION 350 +#define _GUARD_IS_FALSE_POP 351 +#define _GUARD_IS_NONE_POP 352 +#define _GUARD_IS_NOT_NONE_POP 353 +#define _GUARD_IS_TRUE_POP 354 +#define _GUARD_KEYS_VERSION 355 +#define _GUARD_NOT_EXHAUSTED_LIST 356 +#define _GUARD_NOT_EXHAUSTED_RANGE 357 +#define _GUARD_NOT_EXHAUSTED_TUPLE 358 +#define _GUARD_TYPE_VERSION 359 +#define _INIT_CALL_BOUND_METHOD_EXACT_ARGS 360 +#define _INIT_CALL_PY_EXACT_ARGS 361 +#define _INIT_CALL_PY_EXACT_ARGS_0 362 +#define _INIT_CALL_PY_EXACT_ARGS_1 363 +#define _INIT_CALL_PY_EXACT_ARGS_2 364 +#define _INIT_CALL_PY_EXACT_ARGS_3 365 +#define _INIT_CALL_PY_EXACT_ARGS_4 366 #define _INSTRUMENTED_CALL INSTRUMENTED_CALL #define _INSTRUMENTED_CALL_FUNCTION_EX INSTRUMENTED_CALL_FUNCTION_EX #define _INSTRUMENTED_CALL_KW INSTRUMENTED_CALL_KW @@ -140,65 +142,65 @@ extern "C" { #define _INSTRUMENTED_RETURN_CONST INSTRUMENTED_RETURN_CONST #define _INSTRUMENTED_RETURN_VALUE INSTRUMENTED_RETURN_VALUE #define _INSTRUMENTED_YIELD_VALUE INSTRUMENTED_YIELD_VALUE -#define _INTERNAL_INCREMENT_OPT_COUNTER 356 -#define _IS_NONE 357 +#define _INTERNAL_INCREMENT_OPT_COUNTER 367 +#define _IS_NONE 368 #define _IS_OP IS_OP -#define _ITER_CHECK_LIST 358 -#define _ITER_CHECK_RANGE 359 -#define _ITER_CHECK_TUPLE 360 -#define _ITER_JUMP_LIST 361 -#define _ITER_JUMP_RANGE 362 -#define _ITER_JUMP_TUPLE 363 -#define _ITER_NEXT_LIST 364 -#define _ITER_NEXT_RANGE 365 -#define _ITER_NEXT_TUPLE 366 -#define _JUMP_TO_TOP 367 +#define _ITER_CHECK_LIST 369 +#define _ITER_CHECK_RANGE 370 +#define _ITER_CHECK_TUPLE 371 +#define _ITER_JUMP_LIST 372 +#define _ITER_JUMP_RANGE 373 +#define _ITER_JUMP_TUPLE 374 +#define _ITER_NEXT_LIST 375 +#define _ITER_NEXT_RANGE 376 +#define _ITER_NEXT_TUPLE 377 +#define _JUMP_TO_TOP 378 #define _LIST_APPEND LIST_APPEND #define _LIST_EXTEND LIST_EXTEND #define _LOAD_ASSERTION_ERROR LOAD_ASSERTION_ERROR -#define _LOAD_ATTR 368 -#define _LOAD_ATTR_CLASS 369 -#define _LOAD_ATTR_CLASS_0 370 -#define _LOAD_ATTR_CLASS_1 371 +#define _LOAD_ATTR 379 +#define _LOAD_ATTR_CLASS 380 +#define _LOAD_ATTR_CLASS_0 381 +#define _LOAD_ATTR_CLASS_1 382 #define _LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN -#define _LOAD_ATTR_INSTANCE_VALUE 372 -#define _LOAD_ATTR_INSTANCE_VALUE_0 373 -#define _LOAD_ATTR_INSTANCE_VALUE_1 374 -#define _LOAD_ATTR_METHOD_LAZY_DICT 375 -#define _LOAD_ATTR_METHOD_NO_DICT 376 -#define _LOAD_ATTR_METHOD_WITH_VALUES 377 -#define _LOAD_ATTR_MODULE 378 -#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT 379 -#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 380 +#define _LOAD_ATTR_INSTANCE_VALUE 383 +#define _LOAD_ATTR_INSTANCE_VALUE_0 384 +#define _LOAD_ATTR_INSTANCE_VALUE_1 385 +#define _LOAD_ATTR_METHOD_LAZY_DICT 386 +#define _LOAD_ATTR_METHOD_NO_DICT 387 +#define _LOAD_ATTR_METHOD_WITH_VALUES 388 +#define _LOAD_ATTR_MODULE 389 +#define _LOAD_ATTR_NONDESCRIPTOR_NO_DICT 390 +#define _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 391 #define _LOAD_ATTR_PROPERTY LOAD_ATTR_PROPERTY -#define _LOAD_ATTR_SLOT 381 -#define _LOAD_ATTR_SLOT_0 382 -#define _LOAD_ATTR_SLOT_1 383 -#define _LOAD_ATTR_WITH_HINT 384 +#define _LOAD_ATTR_SLOT 392 +#define _LOAD_ATTR_SLOT_0 393 +#define _LOAD_ATTR_SLOT_1 394 +#define _LOAD_ATTR_WITH_HINT 395 #define _LOAD_BUILD_CLASS LOAD_BUILD_CLASS #define _LOAD_CONST LOAD_CONST -#define _LOAD_CONST_INLINE 385 -#define _LOAD_CONST_INLINE_BORROW 386 -#define _LOAD_CONST_INLINE_BORROW_WITH_NULL 387 -#define _LOAD_CONST_INLINE_WITH_NULL 388 +#define _LOAD_CONST_INLINE 396 +#define _LOAD_CONST_INLINE_BORROW 397 +#define _LOAD_CONST_INLINE_BORROW_WITH_NULL 398 +#define _LOAD_CONST_INLINE_WITH_NULL 399 #define _LOAD_DEREF LOAD_DEREF -#define _LOAD_FAST 389 -#define _LOAD_FAST_0 390 -#define _LOAD_FAST_1 391 -#define _LOAD_FAST_2 392 -#define _LOAD_FAST_3 393 -#define _LOAD_FAST_4 394 -#define _LOAD_FAST_5 395 -#define _LOAD_FAST_6 396 -#define _LOAD_FAST_7 397 +#define _LOAD_FAST 400 +#define _LOAD_FAST_0 401 +#define _LOAD_FAST_1 402 +#define _LOAD_FAST_2 403 +#define _LOAD_FAST_3 404 +#define _LOAD_FAST_4 405 +#define _LOAD_FAST_5 406 +#define _LOAD_FAST_6 407 +#define _LOAD_FAST_7 408 #define _LOAD_FAST_AND_CLEAR LOAD_FAST_AND_CLEAR #define _LOAD_FAST_CHECK LOAD_FAST_CHECK #define _LOAD_FAST_LOAD_FAST LOAD_FAST_LOAD_FAST #define _LOAD_FROM_DICT_OR_DEREF LOAD_FROM_DICT_OR_DEREF #define _LOAD_FROM_DICT_OR_GLOBALS LOAD_FROM_DICT_OR_GLOBALS -#define _LOAD_GLOBAL 398 -#define _LOAD_GLOBAL_BUILTINS 399 -#define _LOAD_GLOBAL_MODULE 400 +#define _LOAD_GLOBAL 409 +#define _LOAD_GLOBAL_BUILTINS 410 +#define _LOAD_GLOBAL_MODULE 411 #define _LOAD_LOCALS LOAD_LOCALS #define _LOAD_NAME LOAD_NAME #define _LOAD_SUPER_ATTR_ATTR LOAD_SUPER_ATTR_ATTR @@ -212,47 +214,48 @@ extern "C" { #define _MATCH_SEQUENCE MATCH_SEQUENCE #define _NOP NOP #define _POP_EXCEPT POP_EXCEPT -#define _POP_FRAME 401 -#define _POP_JUMP_IF_FALSE 402 -#define _POP_JUMP_IF_TRUE 403 +#define _POP_FRAME 412 +#define _POP_JUMP_IF_FALSE 413 +#define _POP_JUMP_IF_TRUE 414 #define _POP_TOP POP_TOP +#define _POP_TOP_LOAD_CONST_INLINE_BORROW 415 #define _PUSH_EXC_INFO PUSH_EXC_INFO -#define _PUSH_FRAME 404 +#define _PUSH_FRAME 416 #define _PUSH_NULL PUSH_NULL +#define _REPLACE_WITH_TRUE 417 #define _RESUME_CHECK RESUME_CHECK -#define _SAVE_RETURN_OFFSET 405 -#define _SEND 406 +#define _SAVE_RETURN_OFFSET 418 +#define _SEND 419 #define _SEND_GEN SEND_GEN #define _SETUP_ANNOTATIONS SETUP_ANNOTATIONS #define _SET_ADD SET_ADD #define _SET_FUNCTION_ATTRIBUTE SET_FUNCTION_ATTRIBUTE #define _SET_UPDATE SET_UPDATE -#define _START_EXECUTOR 407 -#define _STORE_ATTR 408 -#define _STORE_ATTR_INSTANCE_VALUE 409 -#define _STORE_ATTR_SLOT 410 +#define _START_EXECUTOR 420 +#define _STORE_ATTR 421 +#define _STORE_ATTR_INSTANCE_VALUE 422 +#define _STORE_ATTR_SLOT 423 #define _STORE_ATTR_WITH_HINT STORE_ATTR_WITH_HINT #define _STORE_DEREF STORE_DEREF -#define _STORE_FAST 411 -#define _STORE_FAST_0 412 -#define _STORE_FAST_1 413 -#define _STORE_FAST_2 414 -#define _STORE_FAST_3 415 -#define _STORE_FAST_4 416 -#define _STORE_FAST_5 417 -#define _STORE_FAST_6 418 -#define _STORE_FAST_7 419 +#define _STORE_FAST 424 +#define _STORE_FAST_0 425 +#define _STORE_FAST_1 426 +#define _STORE_FAST_2 427 +#define _STORE_FAST_3 428 +#define _STORE_FAST_4 429 +#define _STORE_FAST_5 430 +#define _STORE_FAST_6 431 +#define _STORE_FAST_7 432 #define _STORE_FAST_LOAD_FAST STORE_FAST_LOAD_FAST #define _STORE_FAST_STORE_FAST STORE_FAST_STORE_FAST #define _STORE_GLOBAL STORE_GLOBAL #define _STORE_NAME STORE_NAME #define _STORE_SLICE STORE_SLICE -#define _STORE_SUBSCR 420 +#define _STORE_SUBSCR 433 #define _STORE_SUBSCR_DICT STORE_SUBSCR_DICT #define _STORE_SUBSCR_LIST_INT STORE_SUBSCR_LIST_INT #define _SWAP SWAP -#define _TO_BOOL 421 -#define _TO_BOOL_ALWAYS_TRUE TO_BOOL_ALWAYS_TRUE +#define _TO_BOOL 434 #define _TO_BOOL_BOOL TO_BOOL_BOOL #define _TO_BOOL_INT TO_BOOL_INT #define _TO_BOOL_LIST TO_BOOL_LIST @@ -262,12 +265,12 @@ extern "C" { #define _UNARY_NEGATIVE UNARY_NEGATIVE #define _UNARY_NOT UNARY_NOT #define _UNPACK_EX UNPACK_EX -#define _UNPACK_SEQUENCE 422 +#define _UNPACK_SEQUENCE 435 #define _UNPACK_SEQUENCE_LIST UNPACK_SEQUENCE_LIST #define _UNPACK_SEQUENCE_TUPLE UNPACK_SEQUENCE_TUPLE #define _UNPACK_SEQUENCE_TWO_TUPLE UNPACK_SEQUENCE_TWO_TUPLE #define _WITH_EXCEPT_START WITH_EXCEPT_START -#define MAX_UOP_ID 422 +#define MAX_UOP_ID 435 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 35340fe9ee1b634..507bd27c01c553b 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -54,7 +54,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_TO_BOOL_LIST] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, [_TO_BOOL_NONE] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, [_TO_BOOL_STR] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, - [_TO_BOOL_ALWAYS_TRUE] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, + [_REPLACE_WITH_TRUE] = 0, [_UNARY_INVERT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_GUARD_BOTH_INT] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_PASSTHROUGH_FLAG, [_BINARY_OP_MULTIPLY_INT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, @@ -65,7 +65,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_BINARY_OP_ADD_FLOAT] = HAS_PURE_FLAG, [_BINARY_OP_SUBTRACT_FLOAT] = HAS_PURE_FLAG, [_GUARD_BOTH_UNICODE] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_PASSTHROUGH_FLAG, - [_BINARY_OP_ADD_UNICODE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, + [_BINARY_OP_ADD_UNICODE] = HAS_ERROR_FLAG | HAS_PURE_FLAG, [_BINARY_SUBSCR] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_BINARY_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_STORE_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, @@ -114,9 +114,9 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_LOAD_DEREF] = HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_STORE_DEREF] = HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ESCAPES_FLAG, [_COPY_FREE_VARS] = HAS_ARG_FLAG, - [_BUILD_STRING] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_BUILD_TUPLE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_BUILD_LIST] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_BUILD_STRING] = HAS_ARG_FLAG | HAS_ERROR_FLAG, + [_BUILD_TUPLE] = HAS_ARG_FLAG | HAS_ERROR_FLAG, + [_BUILD_LIST] = HAS_ARG_FLAG | HAS_ERROR_FLAG, [_LIST_EXTEND] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_SET_UPDATE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_BUILD_SET] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, @@ -146,14 +146,16 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_LOAD_ATTR_CLASS_1] = 0, [_LOAD_ATTR_CLASS] = HAS_ARG_FLAG | HAS_OPARG_AND_1_FLAG, [_GUARD_DORV_VALUES] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, - [_STORE_ATTR_INSTANCE_VALUE] = HAS_ESCAPES_FLAG, + [_STORE_ATTR_INSTANCE_VALUE] = 0, [_STORE_ATTR_SLOT] = HAS_ESCAPES_FLAG, [_COMPARE_OP] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_COMPARE_OP_FLOAT] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, - [_COMPARE_OP_INT] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, - [_COMPARE_OP_STR] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, + [_COMPARE_OP_INT] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, + [_COMPARE_OP_STR] = HAS_ARG_FLAG, [_IS_OP] = HAS_ARG_FLAG, [_CONTAINS_OP] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CONTAINS_OP_SET] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CONTAINS_OP_DICT] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_CHECK_EG_MATCH] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_CHECK_EXC_MATCH] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_IS_NONE] = 0, @@ -173,19 +175,20 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_ITER_NEXT_TUPLE] = 0, [_ITER_CHECK_RANGE] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, [_GUARD_NOT_EXHAUSTED_RANGE] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, - [_ITER_NEXT_RANGE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_ITER_NEXT_RANGE] = HAS_ERROR_FLAG, [_BEFORE_ASYNC_WITH] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_BEFORE_WITH] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_WITH_EXCEPT_START] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_PUSH_EXC_INFO] = 0, [_GUARD_DORV_VALUES_INST_ATTR_FROM_DICT] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, [_GUARD_KEYS_VERSION] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, - [_LOAD_ATTR_METHOD_WITH_VALUES] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, - [_LOAD_ATTR_METHOD_NO_DICT] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, + [_LOAD_ATTR_METHOD_WITH_VALUES] = HAS_ARG_FLAG, + [_LOAD_ATTR_METHOD_NO_DICT] = HAS_ARG_FLAG, [_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = HAS_ARG_FLAG, [_LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = HAS_ARG_FLAG, [_CHECK_ATTR_METHOD_LAZY_DICT] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, - [_LOAD_ATTR_METHOD_LAZY_DICT] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, + [_LOAD_ATTR_METHOD_LAZY_DICT] = HAS_ARG_FLAG, + [_CHECK_PERIODIC] = HAS_EVAL_BREAK_FLAG, [_CHECK_CALL_BOUND_METHOD_EXACT_ARGS] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, [_INIT_CALL_BOUND_METHOD_EXACT_ARGS] = HAS_ARG_FLAG, [_CHECK_PEP_523] = HAS_DEOPT_FLAG, @@ -197,29 +200,29 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_INIT_CALL_PY_EXACT_ARGS_3] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, [_INIT_CALL_PY_EXACT_ARGS_4] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, [_INIT_CALL_PY_EXACT_ARGS] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, - [_PUSH_FRAME] = HAS_ESCAPES_FLAG, + [_PUSH_FRAME] = 0, [_CALL_TYPE_1] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, - [_CALL_STR_1] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_CALL_TUPLE_1] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_STR_1] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_TUPLE_1] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_EXIT_INIT_CHECK] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_CALL_BUILTIN_CLASS] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG, - [_CALL_BUILTIN_O] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_CALL_BUILTIN_FAST] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_CALL_BUILTIN_FAST_WITH_KEYWORDS] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_BUILTIN_CLASS] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG, + [_CALL_BUILTIN_O] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_BUILTIN_FAST] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_BUILTIN_FAST_WITH_KEYWORDS] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_CALL_LEN] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_CALL_ISINSTANCE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_CALL_METHOD_DESCRIPTOR_O] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_CALL_METHOD_DESCRIPTOR_NOARGS] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_CALL_METHOD_DESCRIPTOR_FAST] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_METHOD_DESCRIPTOR_O] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_METHOD_DESCRIPTOR_NOARGS] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_CALL_METHOD_DESCRIPTOR_FAST] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_MAKE_FUNCTION] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_SET_FUNCTION_ATTRIBUTE] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, - [_BUILD_SLICE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_BUILD_SLICE] = HAS_ARG_FLAG | HAS_ERROR_FLAG, [_CONVERT_VALUE] = HAS_ARG_FLAG | HAS_ERROR_FLAG, [_FORMAT_SIMPLE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_FORMAT_WITH_SPEC] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_COPY] = HAS_ARG_FLAG | HAS_PURE_FLAG, - [_BINARY_OP] = HAS_ARG_FLAG | HAS_ERROR_FLAG, + [_BINARY_OP] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_SWAP] = HAS_ARG_FLAG | HAS_PURE_FLAG, [_GUARD_IS_TRUE_POP] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, [_GUARD_IS_FALSE_POP] = HAS_DEOPT_FLAG | HAS_EXIT_FLAG, @@ -232,10 +235,10 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_CHECK_VALIDITY] = HAS_DEOPT_FLAG, [_LOAD_CONST_INLINE] = HAS_PURE_FLAG, [_LOAD_CONST_INLINE_BORROW] = HAS_PURE_FLAG, + [_POP_TOP_LOAD_CONST_INLINE_BORROW] = HAS_PURE_FLAG, [_LOAD_CONST_INLINE_WITH_NULL] = HAS_PURE_FLAG, [_LOAD_CONST_INLINE_BORROW_WITH_NULL] = HAS_PURE_FLAG, - [_CHECK_GLOBALS] = HAS_DEOPT_FLAG, - [_CHECK_BUILTINS] = HAS_DEOPT_FLAG, + [_CHECK_FUNCTION] = HAS_DEOPT_FLAG, [_INTERNAL_INCREMENT_OPT_COUNTER] = 0, [_COLD_EXIT] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_START_EXECUTOR] = 0, @@ -292,14 +295,14 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_CHECK_ATTR_METHOD_LAZY_DICT] = "_CHECK_ATTR_METHOD_LAZY_DICT", [_CHECK_ATTR_MODULE] = "_CHECK_ATTR_MODULE", [_CHECK_ATTR_WITH_HINT] = "_CHECK_ATTR_WITH_HINT", - [_CHECK_BUILTINS] = "_CHECK_BUILTINS", [_CHECK_CALL_BOUND_METHOD_EXACT_ARGS] = "_CHECK_CALL_BOUND_METHOD_EXACT_ARGS", [_CHECK_EG_MATCH] = "_CHECK_EG_MATCH", [_CHECK_EXC_MATCH] = "_CHECK_EXC_MATCH", + [_CHECK_FUNCTION] = "_CHECK_FUNCTION", [_CHECK_FUNCTION_EXACT_ARGS] = "_CHECK_FUNCTION_EXACT_ARGS", - [_CHECK_GLOBALS] = "_CHECK_GLOBALS", [_CHECK_MANAGED_OBJECT_HAS_VALUES] = "_CHECK_MANAGED_OBJECT_HAS_VALUES", [_CHECK_PEP_523] = "_CHECK_PEP_523", + [_CHECK_PERIODIC] = "_CHECK_PERIODIC", [_CHECK_STACK_SPACE] = "_CHECK_STACK_SPACE", [_CHECK_VALIDITY] = "_CHECK_VALIDITY", [_CHECK_VALIDITY_AND_SET_IP] = "_CHECK_VALIDITY_AND_SET_IP", @@ -309,6 +312,8 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_COMPARE_OP_INT] = "_COMPARE_OP_INT", [_COMPARE_OP_STR] = "_COMPARE_OP_STR", [_CONTAINS_OP] = "_CONTAINS_OP", + [_CONTAINS_OP_DICT] = "_CONTAINS_OP_DICT", + [_CONTAINS_OP_SET] = "_CONTAINS_OP_SET", [_CONVERT_VALUE] = "_CONVERT_VALUE", [_COPY] = "_COPY", [_COPY_FREE_VARS] = "_COPY_FREE_VARS", @@ -425,9 +430,11 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_POP_EXCEPT] = "_POP_EXCEPT", [_POP_FRAME] = "_POP_FRAME", [_POP_TOP] = "_POP_TOP", + [_POP_TOP_LOAD_CONST_INLINE_BORROW] = "_POP_TOP_LOAD_CONST_INLINE_BORROW", [_PUSH_EXC_INFO] = "_PUSH_EXC_INFO", [_PUSH_FRAME] = "_PUSH_FRAME", [_PUSH_NULL] = "_PUSH_NULL", + [_REPLACE_WITH_TRUE] = "_REPLACE_WITH_TRUE", [_RESUME_CHECK] = "_RESUME_CHECK", [_SAVE_RETURN_OFFSET] = "_SAVE_RETURN_OFFSET", [_SETUP_ANNOTATIONS] = "_SETUP_ANNOTATIONS", @@ -459,7 +466,6 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = { [_STORE_SUBSCR_LIST_INT] = "_STORE_SUBSCR_LIST_INT", [_SWAP] = "_SWAP", [_TO_BOOL] = "_TO_BOOL", - [_TO_BOOL_ALWAYS_TRUE] = "_TO_BOOL_ALWAYS_TRUE", [_TO_BOOL_BOOL] = "_TO_BOOL_BOOL", [_TO_BOOL_INT] = "_TO_BOOL_INT", [_TO_BOOL_LIST] = "_TO_BOOL_LIST", diff --git a/Include/object.h b/Include/object.h index 05187fe5dc4f20d..34141af7b7f7ef7 100644 --- a/Include/object.h +++ b/Include/object.h @@ -522,6 +522,10 @@ PyAPI_FUNC(void *) PyType_GetModuleState(PyTypeObject *); PyAPI_FUNC(PyObject *) PyType_GetName(PyTypeObject *); PyAPI_FUNC(PyObject *) PyType_GetQualName(PyTypeObject *); #endif +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030D0000 +PyAPI_FUNC(PyObject *) PyType_GetFullyQualifiedName(PyTypeObject *type); +PyAPI_FUNC(PyObject *) PyType_GetModuleName(PyTypeObject *type); +#endif #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030C0000 PyAPI_FUNC(PyObject *) PyType_FromMetaclass(PyTypeObject*, PyObject*, PyType_Spec*, PyObject*); PyAPI_FUNC(void *) PyObject_GetTypeData(PyObject *obj, PyTypeObject *cls); diff --git a/Include/opcode_ids.h b/Include/opcode_ids.h index fe969342ee79e75..185205c6870edcc 100644 --- a/Include/opcode_ids.h +++ b/Include/opcode_ids.h @@ -163,42 +163,44 @@ extern "C" { #define COMPARE_OP_FLOAT 180 #define COMPARE_OP_INT 181 #define COMPARE_OP_STR 182 -#define FOR_ITER_GEN 183 -#define FOR_ITER_LIST 184 -#define FOR_ITER_RANGE 185 -#define FOR_ITER_TUPLE 186 -#define LOAD_ATTR_CLASS 187 -#define LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN 188 -#define LOAD_ATTR_INSTANCE_VALUE 189 -#define LOAD_ATTR_METHOD_LAZY_DICT 190 -#define LOAD_ATTR_METHOD_NO_DICT 191 -#define LOAD_ATTR_METHOD_WITH_VALUES 192 -#define LOAD_ATTR_MODULE 193 -#define LOAD_ATTR_NONDESCRIPTOR_NO_DICT 194 -#define LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 195 -#define LOAD_ATTR_PROPERTY 196 -#define LOAD_ATTR_SLOT 197 -#define LOAD_ATTR_WITH_HINT 198 -#define LOAD_GLOBAL_BUILTIN 199 -#define LOAD_GLOBAL_MODULE 200 -#define LOAD_SUPER_ATTR_ATTR 201 -#define LOAD_SUPER_ATTR_METHOD 202 -#define RESUME_CHECK 203 -#define SEND_GEN 204 -#define STORE_ATTR_INSTANCE_VALUE 205 -#define STORE_ATTR_SLOT 206 -#define STORE_ATTR_WITH_HINT 207 -#define STORE_SUBSCR_DICT 208 -#define STORE_SUBSCR_LIST_INT 209 -#define TO_BOOL_ALWAYS_TRUE 210 -#define TO_BOOL_BOOL 211 -#define TO_BOOL_INT 212 -#define TO_BOOL_LIST 213 -#define TO_BOOL_NONE 214 -#define TO_BOOL_STR 215 -#define UNPACK_SEQUENCE_LIST 216 -#define UNPACK_SEQUENCE_TUPLE 217 -#define UNPACK_SEQUENCE_TWO_TUPLE 218 +#define CONTAINS_OP_DICT 183 +#define CONTAINS_OP_SET 184 +#define FOR_ITER_GEN 185 +#define FOR_ITER_LIST 186 +#define FOR_ITER_RANGE 187 +#define FOR_ITER_TUPLE 188 +#define LOAD_ATTR_CLASS 189 +#define LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN 190 +#define LOAD_ATTR_INSTANCE_VALUE 191 +#define LOAD_ATTR_METHOD_LAZY_DICT 192 +#define LOAD_ATTR_METHOD_NO_DICT 193 +#define LOAD_ATTR_METHOD_WITH_VALUES 194 +#define LOAD_ATTR_MODULE 195 +#define LOAD_ATTR_NONDESCRIPTOR_NO_DICT 196 +#define LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 197 +#define LOAD_ATTR_PROPERTY 198 +#define LOAD_ATTR_SLOT 199 +#define LOAD_ATTR_WITH_HINT 200 +#define LOAD_GLOBAL_BUILTIN 201 +#define LOAD_GLOBAL_MODULE 202 +#define LOAD_SUPER_ATTR_ATTR 203 +#define LOAD_SUPER_ATTR_METHOD 204 +#define RESUME_CHECK 205 +#define SEND_GEN 206 +#define STORE_ATTR_INSTANCE_VALUE 207 +#define STORE_ATTR_SLOT 208 +#define STORE_ATTR_WITH_HINT 209 +#define STORE_SUBSCR_DICT 210 +#define STORE_SUBSCR_LIST_INT 211 +#define TO_BOOL_ALWAYS_TRUE 212 +#define TO_BOOL_BOOL 213 +#define TO_BOOL_INT 214 +#define TO_BOOL_LIST 215 +#define TO_BOOL_NONE 216 +#define TO_BOOL_STR 217 +#define UNPACK_SEQUENCE_LIST 218 +#define UNPACK_SEQUENCE_TUPLE 219 +#define UNPACK_SEQUENCE_TWO_TUPLE 220 #define INSTRUMENTED_RESUME 236 #define INSTRUMENTED_END_FOR 237 #define INSTRUMENTED_END_SEND 238 diff --git a/Include/patchlevel.h b/Include/patchlevel.h index ae2d22f80817d5d..942922bd0df6987 100644 --- a/Include/patchlevel.h +++ b/Include/patchlevel.h @@ -20,10 +20,10 @@ #define PY_MINOR_VERSION 13 #define PY_MICRO_VERSION 0 #define PY_RELEASE_LEVEL PY_RELEASE_LEVEL_ALPHA -#define PY_RELEASE_SERIAL 4 +#define PY_RELEASE_SERIAL 5 /* Version as a string */ -#define PY_VERSION "3.13.0a4+" +#define PY_VERSION "3.13.0a5+" /*--end constants--*/ /* Version as a single 4-byte hex number, e.g. 0x010502B2 == 1.5.2b2. diff --git a/Include/pyexpat.h b/Include/pyexpat.h index 07020b5dc964cb3..9824d099c3df7d0 100644 --- a/Include/pyexpat.h +++ b/Include/pyexpat.h @@ -48,8 +48,10 @@ struct PyExpat_CAPI enum XML_Status (*SetEncoding)(XML_Parser parser, const XML_Char *encoding); int (*DefaultUnknownEncodingHandler)( void *encodingHandlerData, const XML_Char *name, XML_Encoding *info); - /* might be none for expat < 2.1.0 */ + /* might be NULL for expat < 2.1.0 */ int (*SetHashSalt)(XML_Parser parser, unsigned long hash_salt); + /* might be NULL for expat < 2.6.0 */ + XML_Bool (*SetReparseDeferralEnabled)(XML_Parser parser, XML_Bool enabled); /* always add new stuff to the end! */ }; diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index fdb099bd0c2ecfa..b5bafe6302bc9ea 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -75,6 +75,10 @@ "COMPARE_OP_INT", "COMPARE_OP_STR", ], + "CONTAINS_OP": [ + "CONTAINS_OP_SET", + "CONTAINS_OP_DICT", + ], "FOR_ITER": [ "FOR_ITER_LIST", "FOR_ITER_TUPLE", @@ -138,42 +142,44 @@ 'COMPARE_OP_FLOAT': 180, 'COMPARE_OP_INT': 181, 'COMPARE_OP_STR': 182, - 'FOR_ITER_GEN': 183, - 'FOR_ITER_LIST': 184, - 'FOR_ITER_RANGE': 185, - 'FOR_ITER_TUPLE': 186, - 'LOAD_ATTR_CLASS': 187, - 'LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN': 188, - 'LOAD_ATTR_INSTANCE_VALUE': 189, - 'LOAD_ATTR_METHOD_LAZY_DICT': 190, - 'LOAD_ATTR_METHOD_NO_DICT': 191, - 'LOAD_ATTR_METHOD_WITH_VALUES': 192, - 'LOAD_ATTR_MODULE': 193, - 'LOAD_ATTR_NONDESCRIPTOR_NO_DICT': 194, - 'LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES': 195, - 'LOAD_ATTR_PROPERTY': 196, - 'LOAD_ATTR_SLOT': 197, - 'LOAD_ATTR_WITH_HINT': 198, - 'LOAD_GLOBAL_BUILTIN': 199, - 'LOAD_GLOBAL_MODULE': 200, - 'LOAD_SUPER_ATTR_ATTR': 201, - 'LOAD_SUPER_ATTR_METHOD': 202, - 'RESUME_CHECK': 203, - 'SEND_GEN': 204, - 'STORE_ATTR_INSTANCE_VALUE': 205, - 'STORE_ATTR_SLOT': 206, - 'STORE_ATTR_WITH_HINT': 207, - 'STORE_SUBSCR_DICT': 208, - 'STORE_SUBSCR_LIST_INT': 209, - 'TO_BOOL_ALWAYS_TRUE': 210, - 'TO_BOOL_BOOL': 211, - 'TO_BOOL_INT': 212, - 'TO_BOOL_LIST': 213, - 'TO_BOOL_NONE': 214, - 'TO_BOOL_STR': 215, - 'UNPACK_SEQUENCE_LIST': 216, - 'UNPACK_SEQUENCE_TUPLE': 217, - 'UNPACK_SEQUENCE_TWO_TUPLE': 218, + 'CONTAINS_OP_DICT': 183, + 'CONTAINS_OP_SET': 184, + 'FOR_ITER_GEN': 185, + 'FOR_ITER_LIST': 186, + 'FOR_ITER_RANGE': 187, + 'FOR_ITER_TUPLE': 188, + 'LOAD_ATTR_CLASS': 189, + 'LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN': 190, + 'LOAD_ATTR_INSTANCE_VALUE': 191, + 'LOAD_ATTR_METHOD_LAZY_DICT': 192, + 'LOAD_ATTR_METHOD_NO_DICT': 193, + 'LOAD_ATTR_METHOD_WITH_VALUES': 194, + 'LOAD_ATTR_MODULE': 195, + 'LOAD_ATTR_NONDESCRIPTOR_NO_DICT': 196, + 'LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES': 197, + 'LOAD_ATTR_PROPERTY': 198, + 'LOAD_ATTR_SLOT': 199, + 'LOAD_ATTR_WITH_HINT': 200, + 'LOAD_GLOBAL_BUILTIN': 201, + 'LOAD_GLOBAL_MODULE': 202, + 'LOAD_SUPER_ATTR_ATTR': 203, + 'LOAD_SUPER_ATTR_METHOD': 204, + 'RESUME_CHECK': 205, + 'SEND_GEN': 206, + 'STORE_ATTR_INSTANCE_VALUE': 207, + 'STORE_ATTR_SLOT': 208, + 'STORE_ATTR_WITH_HINT': 209, + 'STORE_SUBSCR_DICT': 210, + 'STORE_SUBSCR_LIST_INT': 211, + 'TO_BOOL_ALWAYS_TRUE': 212, + 'TO_BOOL_BOOL': 213, + 'TO_BOOL_INT': 214, + 'TO_BOOL_LIST': 215, + 'TO_BOOL_NONE': 216, + 'TO_BOOL_STR': 217, + 'UNPACK_SEQUENCE_LIST': 218, + 'UNPACK_SEQUENCE_TUPLE': 219, + 'UNPACK_SEQUENCE_TWO_TUPLE': 220, } opmap = { diff --git a/Lib/argparse.py b/Lib/argparse.py index f86658baf7f2bac..0dbdd67a82f3919 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -223,7 +223,8 @@ def format_help(self): # add the heading if the section was non-empty if self.heading is not SUPPRESS and self.heading is not None: current_indent = self.formatter._current_indent - heading = '%*s%s:\n' % (current_indent, '', self.heading) + heading_text = _('%(heading)s:') % dict(heading=self.heading) + heading = '%*s%s\n' % (current_indent, '', heading_text) else: heading = '' @@ -708,7 +709,7 @@ def _get_help_string(self, action): if action.default is not SUPPRESS: defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] if action.option_strings or action.nargs in defaulting_nargs: - help += ' (default: %(default)s)' + help += _(' (default: %(default)s)') return help @@ -1159,8 +1160,10 @@ def __init__(self, version=None, dest=SUPPRESS, default=SUPPRESS, - help="show program's version number and exit", + help=None, deprecated=False): + if help is None: + help = _("show program's version number and exit") super(_VersionAction, self).__init__( option_strings=option_strings, dest=dest, @@ -2150,10 +2153,11 @@ def consume_positionals(start_index): while start_index <= max_option_string_index: # consume any Positionals preceding the next option - next_option_string_index = min([ - index - for index in option_string_indices - if index >= start_index]) + next_option_string_index = start_index + while next_option_string_index <= max_option_string_index: + if next_option_string_index in option_string_indices: + break + next_option_string_index += 1 if start_index != next_option_string_index: positionals_end_index = consume_positionals(start_index) diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py index 18bb87a5bc4ffd8..cbc1d7c93ef76f5 100644 --- a/Lib/asyncio/__main__.py +++ b/Lib/asyncio/__main__.py @@ -3,6 +3,7 @@ import code import concurrent.futures import inspect +import site import sys import threading import types @@ -109,6 +110,21 @@ def run(self): except ImportError: pass + interactive_hook = getattr(sys, "__interactivehook__", None) + + if interactive_hook is not None: + interactive_hook() + + if interactive_hook is site.register_readline: + # Fix the completer function to use the interactive console locals + try: + import rlcompleter + except: + pass + else: + completer = rlcompleter.Completer(console.locals) + readline.set_completer(completer.complete) + repl_thread = REPLThread() repl_thread.daemon = True repl_thread.start() diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index 072a99fee123c39..680749325025db3 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -54,7 +54,8 @@ def _repr_info(self): info.append('cancelled') if self._callback is not None: info.append(format_helpers._format_callback_source( - self._callback, self._args)) + self._callback, self._args, + debug=self._loop.get_debug())) if self._source_traceback: frame = self._source_traceback[-1] info.append(f'created at {frame[0]}:{frame[1]}') @@ -90,7 +91,8 @@ def _run(self): raise except BaseException as exc: cb = format_helpers._format_callback_source( - self._callback, self._args) + self._callback, self._args, + debug=self._loop.get_debug()) msg = f'Exception in callback {cb}' context = { 'message': msg, diff --git a/Lib/asyncio/format_helpers.py b/Lib/asyncio/format_helpers.py index 27d11fd4fa9553e..93737b7708a67b6 100644 --- a/Lib/asyncio/format_helpers.py +++ b/Lib/asyncio/format_helpers.py @@ -19,19 +19,26 @@ def _get_function_source(func): return None -def _format_callback_source(func, args): - func_repr = _format_callback(func, args, None) +def _format_callback_source(func, args, *, debug=False): + func_repr = _format_callback(func, args, None, debug=debug) source = _get_function_source(func) if source: func_repr += f' at {source[0]}:{source[1]}' return func_repr -def _format_args_and_kwargs(args, kwargs): +def _format_args_and_kwargs(args, kwargs, *, debug=False): """Format function arguments and keyword arguments. Special case for a single parameter: ('hello',) is formatted as ('hello'). + + Note that this function only returns argument details when + debug=True is specified, as arguments may contain sensitive + information. """ + if not debug: + return '()' + # use reprlib to limit the length of the output items = [] if args: @@ -41,10 +48,11 @@ def _format_args_and_kwargs(args, kwargs): return '({})'.format(', '.join(items)) -def _format_callback(func, args, kwargs, suffix=''): +def _format_callback(func, args, kwargs, *, debug=False, suffix=''): if isinstance(func, functools.partial): - suffix = _format_args_and_kwargs(args, kwargs) + suffix - return _format_callback(func.func, func.args, func.keywords, suffix) + suffix = _format_args_and_kwargs(args, kwargs, debug=debug) + suffix + return _format_callback(func.func, func.args, func.keywords, + debug=debug, suffix=suffix) if hasattr(func, '__qualname__') and func.__qualname__: func_repr = func.__qualname__ @@ -53,7 +61,7 @@ def _format_callback(func, args, kwargs, suffix=''): else: func_repr = repr(func) - func_repr += _format_args_and_kwargs(args, kwargs) + func_repr += _format_args_and_kwargs(args, kwargs, debug=debug) if suffix: func_repr += suffix return func_repr diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py index df58b7a799a5add..3fe52dbac25c916 100644 --- a/Lib/asyncio/streams.py +++ b/Lib/asyncio/streams.py @@ -201,7 +201,6 @@ def __init__(self, stream_reader, client_connected_cb=None, loop=None): # is established. self._strong_reader = stream_reader self._reject_connection = False - self._stream_writer = None self._task = None self._transport = None self._client_connected_cb = client_connected_cb @@ -214,10 +213,8 @@ def _stream_reader(self): return None return self._stream_reader_wr() - def _replace_writer(self, writer): + def _replace_transport(self, transport): loop = self._loop - transport = writer.transport - self._stream_writer = writer self._transport = transport self._over_ssl = transport.get_extra_info('sslcontext') is not None @@ -239,11 +236,8 @@ def connection_made(self, transport): reader.set_transport(transport) self._over_ssl = transport.get_extra_info('sslcontext') is not None if self._client_connected_cb is not None: - self._stream_writer = StreamWriter(transport, self, - reader, - self._loop) - res = self._client_connected_cb(reader, - self._stream_writer) + writer = StreamWriter(transport, self, reader, self._loop) + res = self._client_connected_cb(reader, writer) if coroutines.iscoroutine(res): def callback(task): if task.cancelled(): @@ -405,7 +399,7 @@ async def start_tls(self, sslcontext, *, ssl_handshake_timeout=ssl_handshake_timeout, ssl_shutdown_timeout=ssl_shutdown_timeout) self._transport = new_transport - protocol._replace_writer(self) + protocol._replace_transport(new_transport) def __del__(self, warnings=warnings): if not self._transport.is_closing(): diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py index f322b1f6653f6a2..57f01230159319a 100644 --- a/Lib/asyncio/taskgroups.py +++ b/Lib/asyncio/taskgroups.py @@ -154,10 +154,13 @@ def create_task(self, coro, *, name=None, context=None): Similar to `asyncio.create_task`. """ if not self._entered: + coro.close() raise RuntimeError(f"TaskGroup {self!r} has not been entered") if self._exiting and not self._tasks: + coro.close() raise RuntimeError(f"TaskGroup {self!r} is finished") if self._aborting: + coro.close() raise RuntimeError(f"TaskGroup {self!r} is shutting down") if context is None: task = self._loop.create_task(coro, name=name) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index fafee3e738f6aa3..48e31af9a431676 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -464,7 +464,7 @@ async def wait_for(fut, timeout): If the wait is cancelled, the task is also cancelled. - If the task supresses the cancellation and returns a value instead, + If the task suppresses the cancellation and returns a value instead, that value is returned. This function is a coroutine. diff --git a/Lib/base64.py b/Lib/base64.py index e3e983b3064fe7b..25164d1a1df4fc8 100755 --- a/Lib/base64.py +++ b/Lib/base64.py @@ -18,7 +18,7 @@ 'b64encode', 'b64decode', 'b32encode', 'b32decode', 'b32hexencode', 'b32hexdecode', 'b16encode', 'b16decode', # Base85 and Ascii85 encodings - 'b85encode', 'b85decode', 'a85encode', 'a85decode', + 'b85encode', 'b85decode', 'a85encode', 'a85decode', 'z85encode', 'z85decode', # Standard Base64 encoding 'standard_b64encode', 'standard_b64decode', # Some common Base64 alternatives. As referenced by RFC 3458, see thread @@ -497,6 +497,33 @@ def b85decode(b): result = result[:-padding] return result +_z85alphabet = (b'0123456789abcdefghijklmnopqrstuvwxyz' + b'ABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#') +# Translating b85 valid but z85 invalid chars to b'\x00' is required +# to prevent them from being decoded as b85 valid chars. +_z85_b85_decode_diff = b';_`|~' +_z85_decode_translation = bytes.maketrans( + _z85alphabet + _z85_b85_decode_diff, + _b85alphabet + b'\x00' * len(_z85_b85_decode_diff) +) +_z85_encode_translation = bytes.maketrans(_b85alphabet, _z85alphabet) + +def z85encode(s): + """Encode bytes-like object b in z85 format and return a bytes object.""" + return b85encode(s).translate(_z85_encode_translation) + +def z85decode(s): + """Decode the z85-encoded bytes-like object or ASCII string b + + The result is returned as a bytes object. + """ + s = _bytes_from_decode_data(s) + s = s.translate(_z85_decode_translation) + try: + return b85decode(s) + except ValueError as e: + raise ValueError(e.args[0].replace('base85', 'z85')) from None + # Legacy interface. This code could be cleaned up since I don't believe # binascii has any line length limitations. It just doesn't seem worth it # though. The files should be opened in binary mode. diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 2e527dfd810c43b..0aa0c3e15e95198 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -680,7 +680,7 @@ def update(self, iterable=None, /, **kwds): ''' # The regular dict.update() operation makes no sense here because the - # replace behavior results in the some of original untouched counts + # replace behavior results in some of the original untouched counts # being mixed-in with all of the other counts for a mismash that # doesn't have a straight-forward interpretation in most counting # contexts. Instead, we implement straight-addition. Both the inputs diff --git a/Lib/configparser.py b/Lib/configparser.py index 71362d23ec37576..241f10aee93ec49 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -152,6 +152,7 @@ "NoOptionError", "InterpolationError", "InterpolationDepthError", "InterpolationMissingOptionError", "InterpolationSyntaxError", "ParsingError", "MissingSectionHeaderError", + "MultilineContinuationError", "ConfigParser", "RawConfigParser", "Interpolation", "BasicInterpolation", "ExtendedInterpolation", "SectionProxy", "ConverterMapping", @@ -322,6 +323,19 @@ def __init__(self, filename, lineno, line): self.args = (filename, lineno, line) +class MultilineContinuationError(ParsingError): + """Raised when a key without value is followed by continuation line""" + def __init__(self, filename, lineno, line): + Error.__init__( + self, + "Key without value continued with an indented line.\n" + "file: %r, line: %d\n%r" + %(filename, lineno, line)) + self.source = filename + self.lineno = lineno + self.line = line + self.args = (filename, lineno, line) + # Used in parser getters to indicate the default behaviour when a specific # option is not found it to raise an exception. Created to enable `None` as # a valid fallback value. @@ -987,6 +1001,8 @@ def _read(self, fp, fpname): cur_indent_level = first_nonspace.start() if first_nonspace else 0 if (cursect is not None and optname and cur_indent_level > indent_level): + if cursect[optname] is None: + raise MultilineContinuationError(fpname, lineno, line) cursect[optname].append(value) # a section header or option header? else: diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index 141142a57dcb3e2..f63e31a3fb01075 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -468,6 +468,8 @@ def LoadLibrary(self, name): if _os.name == "nt": pythonapi = PyDLL("python dll", None, _sys.dllhandle) +elif _sys.platform == "android": + pythonapi = PyDLL("libpython%d.%d.so" % _sys.version_info[:2]) elif _sys.platform == "cygwin": pythonapi = PyDLL("libpython%d.%d.dll" % _sys.version_info[:2]) else: diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 1d4c10bcc9a86eb..75de04f8d919b17 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -4,10 +4,9 @@ import types import inspect import keyword -import functools import itertools import abc -import _thread +from reprlib import recursive_repr from types import FunctionType, GenericAlias @@ -245,25 +244,6 @@ def __repr__(self): property, }) -# This function's logic is copied from "recursive_repr" function in -# reprlib module to avoid dependency. -def _recursive_repr(user_function): - # Decorator to make a repr function return "..." for a recursive - # call. - repr_running = set() - - @functools.wraps(user_function) - def wrapper(self): - key = id(self), _thread.get_ident() - if key in repr_running: - return '...' - repr_running.add(key) - try: - result = user_function(self) - finally: - repr_running.discard(key) - return result - return wrapper class InitVar: __slots__ = ('type', ) @@ -324,7 +304,7 @@ def __init__(self, default, default_factory, init, repr, hash, compare, self.doc = doc self._field_type = None - @_recursive_repr + @recursive_repr() def __repr__(self): return ('Field(' f'name={self.name!r},' @@ -635,7 +615,7 @@ def _repr_fn(fields, globals): for f in fields]) + ')"'], globals=globals) - return _recursive_repr(fn) + return recursive_repr()(fn) def _frozen_get_del_attr(cls, fields, globals): diff --git a/Lib/email/generator.py b/Lib/email/generator.py index 7ccbe10eb768567..c8056ad47baa0fb 100644 --- a/Lib/email/generator.py +++ b/Lib/email/generator.py @@ -243,7 +243,7 @@ def _handle_text(self, msg): # existing message. msg = deepcopy(msg) del msg['content-transfer-encoding'] - msg.set_payload(payload, charset) + msg.set_payload(msg._payload, charset) payload = msg.get_payload() self._munge_cte = (msg['content-transfer-encoding'], msg['content-type']) diff --git a/Lib/email/message.py b/Lib/email/message.py index fe769580fed5d0a..a14cca56b3745aa 100644 --- a/Lib/email/message.py +++ b/Lib/email/message.py @@ -340,7 +340,7 @@ def set_payload(self, payload, charset=None): return if not isinstance(charset, Charset): charset = Charset(charset) - payload = payload.encode(charset.output_charset) + payload = payload.encode(charset.output_charset, 'surrogateescape') if hasattr(payload, 'decode'): self._payload = payload.decode('ascii', 'surrogateescape') else: diff --git a/Lib/encodings/idna.py b/Lib/encodings/idna.py index 5396047a7fb0b87..60a8d5eb227f822 100644 --- a/Lib/encodings/idna.py +++ b/Lib/encodings/idna.py @@ -11,7 +11,7 @@ sace_prefix = "xn--" # This assumes query strings, so AllowUnassigned is true -def nameprep(label): +def nameprep(label): # type: (str) -> str # Map newlabel = [] for c in label: @@ -25,7 +25,7 @@ def nameprep(label): label = unicodedata.normalize("NFKC", label) # Prohibit - for c in label: + for i, c in enumerate(label): if stringprep.in_table_c12(c) or \ stringprep.in_table_c22(c) or \ stringprep.in_table_c3(c) or \ @@ -35,7 +35,7 @@ def nameprep(label): stringprep.in_table_c7(c) or \ stringprep.in_table_c8(c) or \ stringprep.in_table_c9(c): - raise UnicodeError("Invalid character %r" % c) + raise UnicodeEncodeError("idna", label, i, i+1, f"Invalid character {c!r}") # Check bidi RandAL = [stringprep.in_table_d1(x) for x in label] @@ -46,29 +46,38 @@ def nameprep(label): # This is table C.8, which was already checked # 2) If a string contains any RandALCat character, the string # MUST NOT contain any LCat character. - if any(stringprep.in_table_d2(x) for x in label): - raise UnicodeError("Violation of BIDI requirement 2") + for i, x in enumerate(label): + if stringprep.in_table_d2(x): + raise UnicodeEncodeError("idna", label, i, i+1, + "Violation of BIDI requirement 2") # 3) If a string contains any RandALCat character, a # RandALCat character MUST be the first character of the # string, and a RandALCat character MUST be the last # character of the string. - if not RandAL[0] or not RandAL[-1]: - raise UnicodeError("Violation of BIDI requirement 3") + if not RandAL[0]: + raise UnicodeEncodeError("idna", label, 0, 1, + "Violation of BIDI requirement 3") + if not RandAL[-1]: + raise UnicodeEncodeError("idna", label, len(label)-1, len(label), + "Violation of BIDI requirement 3") return label -def ToASCII(label): +def ToASCII(label): # type: (str) -> bytes try: # Step 1: try ASCII - label = label.encode("ascii") - except UnicodeError: + label_ascii = label.encode("ascii") + except UnicodeEncodeError: pass else: # Skip to step 3: UseSTD3ASCIIRules is false, so # Skip to step 8. - if 0 < len(label) < 64: - return label - raise UnicodeError("label empty or too long") + if 0 < len(label_ascii) < 64: + return label_ascii + if len(label) == 0: + raise UnicodeEncodeError("idna", label, 0, 1, "label empty") + else: + raise UnicodeEncodeError("idna", label, 0, len(label), "label too long") # Step 2: nameprep label = nameprep(label) @@ -76,29 +85,34 @@ def ToASCII(label): # Step 3: UseSTD3ASCIIRules is false # Step 4: try ASCII try: - label = label.encode("ascii") - except UnicodeError: + label_ascii = label.encode("ascii") + except UnicodeEncodeError: pass else: # Skip to step 8. if 0 < len(label) < 64: - return label - raise UnicodeError("label empty or too long") + return label_ascii + if len(label) == 0: + raise UnicodeEncodeError("idna", label, 0, 1, "label empty") + else: + raise UnicodeEncodeError("idna", label, 0, len(label), "label too long") # Step 5: Check ACE prefix - if label.startswith(sace_prefix): - raise UnicodeError("Label starts with ACE prefix") + if label.lower().startswith(sace_prefix): + raise UnicodeEncodeError( + "idna", label, 0, len(sace_prefix), "Label starts with ACE prefix") # Step 6: Encode with PUNYCODE - label = label.encode("punycode") + label_ascii = label.encode("punycode") # Step 7: Prepend ACE prefix - label = ace_prefix + label + label_ascii = ace_prefix + label_ascii # Step 8: Check size - if 0 < len(label) < 64: - return label - raise UnicodeError("label empty or too long") + # do not check for empty as we prepend ace_prefix. + if len(label_ascii) < 64: + return label_ascii + raise UnicodeEncodeError("idna", label, 0, len(label), "label too long") def ToUnicode(label): if len(label) > 1024: @@ -110,7 +124,9 @@ def ToUnicode(label): # per https://www.rfc-editor.org/rfc/rfc3454#section-3.1 while still # preventing us from wasting time decoding a big thing that'll just # hit the actual <= 63 length limit in Step 6. - raise UnicodeError("label way too long") + if isinstance(label, str): + label = label.encode("utf-8", errors="backslashreplace") + raise UnicodeDecodeError("idna", label, 0, len(label), "label way too long") # Step 1: Check for ASCII if isinstance(label, bytes): pure_ascii = True @@ -118,25 +134,32 @@ def ToUnicode(label): try: label = label.encode("ascii") pure_ascii = True - except UnicodeError: + except UnicodeEncodeError: pure_ascii = False if not pure_ascii: + assert isinstance(label, str) # Step 2: Perform nameprep label = nameprep(label) # It doesn't say this, but apparently, it should be ASCII now try: label = label.encode("ascii") - except UnicodeError: - raise UnicodeError("Invalid character in IDN label") + except UnicodeEncodeError as exc: + raise UnicodeEncodeError("idna", label, exc.start, exc.end, + "Invalid character in IDN label") # Step 3: Check for ACE prefix - if not label.startswith(ace_prefix): + assert isinstance(label, bytes) + if not label.lower().startswith(ace_prefix): return str(label, "ascii") # Step 4: Remove ACE prefix label1 = label[len(ace_prefix):] # Step 5: Decode using PUNYCODE - result = label1.decode("punycode") + try: + result = label1.decode("punycode") + except UnicodeDecodeError as exc: + offset = len(ace_prefix) + raise UnicodeDecodeError("idna", label, offset+exc.start, offset+exc.end, exc.reason) # Step 6: Apply ToASCII label2 = ToASCII(result) @@ -144,7 +167,8 @@ def ToUnicode(label): # Step 7: Compare the result of step 6 with the one of step 3 # label2 will already be in lower case. if str(label, "ascii").lower() != str(label2, "ascii"): - raise UnicodeError("IDNA does not round-trip", label, label2) + raise UnicodeDecodeError("idna", label, 0, len(label), + f"IDNA does not round-trip, '{label!r}' != '{label2!r}'") # Step 8: return the result of step 5 return result @@ -156,7 +180,7 @@ def encode(self, input, errors='strict'): if errors != 'strict': # IDNA is quite clear that implementations must be strict - raise UnicodeError("unsupported error handling "+errors) + raise UnicodeError(f"Unsupported error handling: {errors}") if not input: return b'', 0 @@ -168,11 +192,16 @@ def encode(self, input, errors='strict'): else: # ASCII name: fast path labels = result.split(b'.') - for label in labels[:-1]: - if not (0 < len(label) < 64): - raise UnicodeError("label empty or too long") - if len(labels[-1]) >= 64: - raise UnicodeError("label too long") + for i, label in enumerate(labels[:-1]): + if len(label) == 0: + offset = sum(len(l) for l in labels[:i]) + i + raise UnicodeEncodeError("idna", input, offset, offset+1, + "label empty") + for i, label in enumerate(labels): + if len(label) >= 64: + offset = sum(len(l) for l in labels[:i]) + i + raise UnicodeEncodeError("idna", input, offset, offset+len(label), + "label too long") return result, len(input) result = bytearray() @@ -182,17 +211,27 @@ def encode(self, input, errors='strict'): del labels[-1] else: trailing_dot = b'' - for label in labels: + for i, label in enumerate(labels): if result: # Join with U+002E result.extend(b'.') - result.extend(ToASCII(label)) + try: + result.extend(ToASCII(label)) + except (UnicodeEncodeError, UnicodeDecodeError) as exc: + offset = sum(len(l) for l in labels[:i]) + i + raise UnicodeEncodeError( + "idna", + input, + offset + exc.start, + offset + exc.end, + exc.reason, + ) return bytes(result+trailing_dot), len(input) def decode(self, input, errors='strict'): if errors != 'strict': - raise UnicodeError("Unsupported error handling "+errors) + raise UnicodeError(f"Unsupported error handling: {errors}") if not input: return "", 0 @@ -202,7 +241,7 @@ def decode(self, input, errors='strict'): # XXX obviously wrong, see #3232 input = bytes(input) - if ace_prefix not in input: + if ace_prefix not in input.lower(): # Fast path try: return input.decode('ascii'), len(input) @@ -218,8 +257,15 @@ def decode(self, input, errors='strict'): trailing_dot = '' result = [] - for label in labels: - result.append(ToUnicode(label)) + for i, label in enumerate(labels): + try: + u_label = ToUnicode(label) + except (UnicodeEncodeError, UnicodeDecodeError) as exc: + offset = sum(len(x) for x in labels[:i]) + len(labels[:i]) + raise UnicodeDecodeError( + "idna", input, offset+exc.start, offset+exc.end, exc.reason) + else: + result.append(u_label) return ".".join(result)+trailing_dot, len(input) @@ -227,7 +273,7 @@ class IncrementalEncoder(codecs.BufferedIncrementalEncoder): def _buffer_encode(self, input, errors, final): if errors != 'strict': # IDNA is quite clear that implementations must be strict - raise UnicodeError("unsupported error handling "+errors) + raise UnicodeError(f"Unsupported error handling: {errors}") if not input: return (b'', 0) @@ -251,7 +297,16 @@ def _buffer_encode(self, input, errors, final): # Join with U+002E result.extend(b'.') size += 1 - result.extend(ToASCII(label)) + try: + result.extend(ToASCII(label)) + except (UnicodeEncodeError, UnicodeDecodeError) as exc: + raise UnicodeEncodeError( + "idna", + input, + size + exc.start, + size + exc.end, + exc.reason, + ) size += len(label) result += trailing_dot @@ -261,7 +316,7 @@ def _buffer_encode(self, input, errors, final): class IncrementalDecoder(codecs.BufferedIncrementalDecoder): def _buffer_decode(self, input, errors, final): if errors != 'strict': - raise UnicodeError("Unsupported error handling "+errors) + raise UnicodeError("Unsupported error handling: {errors}") if not input: return ("", 0) @@ -271,7 +326,11 @@ def _buffer_decode(self, input, errors, final): labels = dots.split(input) else: # Must be ASCII string - input = str(input, "ascii") + try: + input = str(input, "ascii") + except (UnicodeEncodeError, UnicodeDecodeError) as exc: + raise UnicodeDecodeError("idna", input, + exc.start, exc.end, exc.reason) labels = input.split(".") trailing_dot = '' @@ -288,7 +347,18 @@ def _buffer_decode(self, input, errors, final): result = [] size = 0 for label in labels: - result.append(ToUnicode(label)) + try: + u_label = ToUnicode(label) + except (UnicodeEncodeError, UnicodeDecodeError) as exc: + raise UnicodeDecodeError( + "idna", + input.encode("ascii", errors="backslashreplace"), + size + exc.start, + size + exc.end, + exc.reason, + ) + else: + result.append(u_label) if size: size += 1 size += len(label) diff --git a/Lib/encodings/punycode.py b/Lib/encodings/punycode.py index 1c5726447077b17..4622fc8c9206f37 100644 --- a/Lib/encodings/punycode.py +++ b/Lib/encodings/punycode.py @@ -1,4 +1,4 @@ -""" Codec for the Punicode encoding, as specified in RFC 3492 +""" Codec for the Punycode encoding, as specified in RFC 3492 Written by Martin v. Löwis. """ @@ -131,10 +131,11 @@ def decode_generalized_number(extended, extpos, bias, errors): j = 0 while 1: try: - char = ord(extended[extpos]) + char = extended[extpos] except IndexError: if errors == "strict": - raise UnicodeError("incomplete punicode string") + raise UnicodeDecodeError("punycode", extended, extpos, extpos+1, + "incomplete punycode string") return extpos + 1, None extpos += 1 if 0x41 <= char <= 0x5A: # A-Z @@ -142,8 +143,8 @@ def decode_generalized_number(extended, extpos, bias, errors): elif 0x30 <= char <= 0x39: digit = char - 22 # 0x30-26 elif errors == "strict": - raise UnicodeError("Invalid extended code point '%s'" - % extended[extpos-1]) + raise UnicodeDecodeError("punycode", extended, extpos-1, extpos, + f"Invalid extended code point '{extended[extpos-1]}'") else: return extpos, None t = T(j, bias) @@ -155,11 +156,14 @@ def decode_generalized_number(extended, extpos, bias, errors): def insertion_sort(base, extended, errors): - """3.2 Insertion unsort coding""" + """3.2 Insertion sort coding""" + # This function raises UnicodeDecodeError with position in the extended. + # Caller should add the offset. char = 0x80 pos = -1 bias = 72 extpos = 0 + while extpos < len(extended): newpos, delta = decode_generalized_number(extended, extpos, bias, errors) @@ -171,7 +175,9 @@ def insertion_sort(base, extended, errors): char += pos // (len(base) + 1) if char > 0x10FFFF: if errors == "strict": - raise UnicodeError("Invalid character U+%x" % char) + raise UnicodeDecodeError( + "punycode", extended, pos-1, pos, + f"Invalid character U+{char:x}") char = ord('?') pos = pos % (len(base) + 1) base = base[:pos] + chr(char) + base[pos:] @@ -187,11 +193,21 @@ def punycode_decode(text, errors): pos = text.rfind(b"-") if pos == -1: base = "" - extended = str(text, "ascii").upper() + extended = text.upper() else: - base = str(text[:pos], "ascii", errors) - extended = str(text[pos+1:], "ascii").upper() - return insertion_sort(base, extended, errors) + try: + base = str(text[:pos], "ascii", errors) + except UnicodeDecodeError as exc: + raise UnicodeDecodeError("ascii", text, exc.start, exc.end, + exc.reason) from None + extended = text[pos+1:].upper() + try: + return insertion_sort(base, extended, errors) + except UnicodeDecodeError as exc: + offset = pos + 1 + raise UnicodeDecodeError("punycode", text, + offset+exc.start, offset+exc.end, + exc.reason) from None ### Codec APIs @@ -203,7 +219,7 @@ def encode(self, input, errors='strict'): def decode(self, input, errors='strict'): if errors not in ('strict', 'replace', 'ignore'): - raise UnicodeError("Unsupported error handling "+errors) + raise UnicodeError(f"Unsupported error handling: {errors}") res = punycode_decode(input, errors) return res, len(input) @@ -214,7 +230,7 @@ def encode(self, input, final=False): class IncrementalDecoder(codecs.IncrementalDecoder): def decode(self, input, final=False): if self.errors not in ('strict', 'replace', 'ignore'): - raise UnicodeError("Unsupported error handling "+self.errors) + raise UnicodeError(f"Unsupported error handling: {self.errors}") return punycode_decode(input, self.errors) class StreamWriter(Codec,codecs.StreamWriter): diff --git a/Lib/encodings/undefined.py b/Lib/encodings/undefined.py index 4690288355c710b..082771e1c866773 100644 --- a/Lib/encodings/undefined.py +++ b/Lib/encodings/undefined.py @@ -1,6 +1,6 @@ """ Python 'undefined' Codec - This codec will always raise a ValueError exception when being + This codec will always raise a UnicodeError exception when being used. It is intended for use by the site.py file to switch off automatic string to Unicode coercion. diff --git a/Lib/encodings/utf_16.py b/Lib/encodings/utf_16.py index c61248242be8c7a..d3b9980026666f1 100644 --- a/Lib/encodings/utf_16.py +++ b/Lib/encodings/utf_16.py @@ -64,7 +64,7 @@ def _buffer_decode(self, input, errors, final): elif byteorder == 1: self.decoder = codecs.utf_16_be_decode elif consumed >= 2: - raise UnicodeError("UTF-16 stream does not start with BOM") + raise UnicodeDecodeError("utf-16", input, 0, 2, "Stream does not start with BOM") return (output, consumed) return self.decoder(input, self.errors, final) @@ -138,7 +138,7 @@ def decode(self, input, errors='strict'): elif byteorder == 1: self.decode = codecs.utf_16_be_decode elif consumed>=2: - raise UnicodeError("UTF-16 stream does not start with BOM") + raise UnicodeDecodeError("utf-16", input, 0, 2, "Stream does not start with BOM") return (object, consumed) ### encodings module API diff --git a/Lib/encodings/utf_32.py b/Lib/encodings/utf_32.py index cdf84d14129a623..1924bedbb74c686 100644 --- a/Lib/encodings/utf_32.py +++ b/Lib/encodings/utf_32.py @@ -59,7 +59,7 @@ def _buffer_decode(self, input, errors, final): elif byteorder == 1: self.decoder = codecs.utf_32_be_decode elif consumed >= 4: - raise UnicodeError("UTF-32 stream does not start with BOM") + raise UnicodeDecodeError("utf-32", input, 0, 4, "Stream does not start with BOM") return (output, consumed) return self.decoder(input, self.errors, final) @@ -132,8 +132,8 @@ def decode(self, input, errors='strict'): self.decode = codecs.utf_32_le_decode elif byteorder == 1: self.decode = codecs.utf_32_be_decode - elif consumed>=4: - raise UnicodeError("UTF-32 stream does not start with BOM") + elif consumed >= 4: + raise UnicodeDecodeError("utf-32", input, 0, 4, "Stream does not start with BOM") return (object, consumed) ### encodings module API diff --git a/Lib/enum.py b/Lib/enum.py index d10b99615981ba2..5c5e711f9b078f0 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -162,6 +162,11 @@ def _dedent(text): lines[j] = l[i:] return '\n'.join(lines) +class _not_given: + def __repr__(self): + return('') +_not_given = _not_given() + class _auto_null: def __repr__(self): return '_auto_null' @@ -547,7 +552,10 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k classdict['_inverted_'] = None try: exc = None + classdict['_%s__in_progress' % cls] = True enum_class = super().__new__(metacls, cls, bases, classdict, **kwds) + classdict['_%s__in_progress' % cls] = False + delattr(enum_class, '_%s__in_progress' % cls) except Exception as e: # since 3.12 the line "Error calling __set_name__ on '_proto_member' instance ..." # is tacked on to the error instead of raising a RuntimeError @@ -677,7 +685,7 @@ def __bool__(cls): """ return True - def __call__(cls, value, names=None, *values, module=None, qualname=None, type=None, start=1, boundary=None): + def __call__(cls, value, names=_not_given, *values, module=None, qualname=None, type=None, start=1, boundary=None): """ Either returns an existing member, or creates a new enum class. @@ -706,18 +714,18 @@ def __call__(cls, value, names=None, *values, module=None, qualname=None, type=N """ if cls._member_map_: # simple value lookup if members exist - if names: + if names is not _not_given: value = (value, names) + values return cls.__new__(cls, value) # otherwise, functional API: we're creating a new Enum type - if names is None and type is None: + if names is _not_given and type is None: # no body? no data-type? possibly wrong usage raise TypeError( f"{cls} has no members; specify `names=()` if you meant to create a new, empty, enum" ) return cls._create_( class_name=value, - names=names, + names=None if names is _not_given else names, module=module, qualname=qualname, type=type, @@ -1155,6 +1163,8 @@ def __new__(cls, value): # still not found -- verify that members exist, in-case somebody got here mistakenly # (such as via super when trying to override __new__) if not cls._member_map_: + if getattr(cls, '_%s__in_progress' % cls.__name__, False): + raise TypeError('do not use `super().__new__; call the appropriate __new__ directly') from None raise TypeError("%r has no members defined" % cls) # # still not found -- try _missing_ hook @@ -1670,7 +1680,7 @@ def global_flag_repr(self): cls_name = self.__class__.__name__ if self._name_ is None: return "%s.%s(%r)" % (module, cls_name, self._value_) - if _is_single_bit(self): + if _is_single_bit(self._value_): return '%s.%s' % (module, self._name_) if self._boundary_ is not FlagBoundary.KEEP: return '|'.join(['%s.%s' % (module, name) for name in self.name.split('|')]) diff --git a/Lib/filecmp.py b/Lib/filecmp.py index 30bd900fa805aa9..6ffc71fc059a80b 100644 --- a/Lib/filecmp.py +++ b/Lib/filecmp.py @@ -88,12 +88,15 @@ def _do_cmp(f1, f2): class dircmp: """A class that manages the comparison of 2 directories. - dircmp(a, b, ignore=None, hide=None) + dircmp(a, b, ignore=None, hide=None, shallow=True) A and B are directories. IGNORE is a list of names to ignore, defaults to DEFAULT_IGNORES. HIDE is a list of names to hide, defaults to [os.curdir, os.pardir]. + SHALLOW specifies whether to just check the stat signature (do not read + the files). + defaults to True. High level usage: x = dircmp(dir1, dir2) @@ -121,7 +124,7 @@ class dircmp: in common_dirs. """ - def __init__(self, a, b, ignore=None, hide=None): # Initialize + def __init__(self, a, b, ignore=None, hide=None, shallow=True): # Initialize self.left = a self.right = b if hide is None: @@ -132,6 +135,7 @@ def __init__(self, a, b, ignore=None, hide=None): # Initialize self.ignore = DEFAULT_IGNORES else: self.ignore = ignore + self.shallow = shallow def phase0(self): # Compare everything except common subdirectories self.left_list = _filter(os.listdir(self.left), @@ -184,7 +188,7 @@ def phase2(self): # Distinguish files, directories, funnies self.common_funny.append(x) def phase3(self): # Find out differences between common files - xx = cmpfiles(self.left, self.right, self.common_files) + xx = cmpfiles(self.left, self.right, self.common_files, self.shallow) self.same_files, self.diff_files, self.funny_files = xx def phase4(self): # Find out differences between common subdirectories @@ -196,7 +200,8 @@ def phase4(self): # Find out differences between common subdirectories for x in self.common_dirs: a_x = os.path.join(self.left, x) b_x = os.path.join(self.right, x) - self.subdirs[x] = self.__class__(a_x, b_x, self.ignore, self.hide) + self.subdirs[x] = self.__class__(a_x, b_x, self.ignore, self.hide, + self.shallow) def phase4_closure(self): # Recursively call phase4() on subdirectories self.phase4() diff --git a/Lib/functools.py b/Lib/functools.py index 7045be551c8c491..601cb8e7c0b74b1 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -303,13 +303,13 @@ def __call__(self, /, *args, **keywords): @recursive_repr() def __repr__(self): - qualname = type(self).__qualname__ + cls = type(self) + qualname = cls.__qualname__ + module = cls.__module__ args = [repr(self.func)] args.extend(repr(x) for x in self.args) args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items()) - if type(self).__module__ == "functools": - return f"functools.{qualname}({', '.join(args)})" - return f"{qualname}({', '.join(args)})" + return f"{module}.{qualname}({', '.join(args)})" def __reduce__(self): return type(self), (self.func,), (self.func, self.args, diff --git a/Lib/glob.py b/Lib/glob.py index 343be78a73b20a3..473502c67336f97 100644 --- a/Lib/glob.py +++ b/Lib/glob.py @@ -256,8 +256,7 @@ def translate(pat, *, recursive=False, include_hidden=False, seps=None): """Translate a pathname with shell wildcards to a regular expression. If `recursive` is true, the pattern segment '**' will match any number of - path segments; if '**' appears outside its own segment, ValueError will be - raised. + path segments. If `include_hidden` is true, wildcards can match path segments beginning with a dot ('.'). @@ -291,22 +290,18 @@ def translate(pat, *, recursive=False, include_hidden=False, seps=None): for idx, part in enumerate(parts): if part == '*': results.append(one_segment if idx < last_part_idx else one_last_segment) - continue - if recursive: - if part == '**': - if idx < last_part_idx: - if parts[idx + 1] != '**': - results.append(any_segments) - else: - results.append(any_last_segments) - continue - elif '**' in part: - raise ValueError("Invalid pattern: '**' can only be an entire path component") - if part: - if not include_hidden and part[0] in '*?': - results.append(r'(?!\.)') - results.extend(fnmatch._translate(part, f'{not_sep}*', not_sep)) - if idx < last_part_idx: - results.append(any_sep) + elif recursive and part == '**': + if idx < last_part_idx: + if parts[idx + 1] != '**': + results.append(any_segments) + else: + results.append(any_last_segments) + else: + if part: + if not include_hidden and part[0] in '*?': + results.append(r'(?!\.)') + results.extend(fnmatch._translate(part, f'{not_sep}*', not_sep)) + if idx < last_part_idx: + results.append(any_sep) res = ''.join(results) return fr'(?s:{res})\Z' diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py index 2a9aef03179f6fb..b26be8583d0f81b 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py @@ -465,6 +465,7 @@ def _write_atomic(path, data, mode=0o666): # Python 3.13a1 3566 (Emit JUMP_NO_INTERRUPT instead of JUMP for non-loop no-lineno cases) # Python 3.13a1 3567 (Reimplement line number propagation by the compiler) # Python 3.13a1 3568 (Change semantics of END_FOR) +# Python 3.13a5 3569 (Specialize CONTAINS_OP) # Python 3.14 will start with 3600 @@ -481,7 +482,7 @@ def _write_atomic(path, data, mode=0o666): # Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array # in PC/launcher.c must also be updated. -MAGIC_NUMBER = (3568).to_bytes(2, 'little') + b'\r\n' +MAGIC_NUMBER = (3569).to_bytes(2, 'little') + b'\r\n' _RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c @@ -1469,6 +1470,9 @@ def invalidate_caches(): # https://bugs.python.org/issue45703 _NamespacePath._epoch += 1 + from importlib.metadata import MetadataPathFinder + MetadataPathFinder.invalidate_caches() + @staticmethod def _path_hooks(path): """Search sys.path_hooks for a finder for 'path'.""" diff --git a/Lib/importlib/metadata/__init__.py b/Lib/importlib/metadata/__init__.py index c612fbefee2e802..41c2a4a6088b5d7 100644 --- a/Lib/importlib/metadata/__init__.py +++ b/Lib/importlib/metadata/__init__.py @@ -797,6 +797,7 @@ def _search_paths(cls, name, paths): path.search(prepared) for path in map(FastPath, paths) ) + @classmethod def invalidate_caches(cls) -> None: FastPath.__new__.cache_clear() diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index 3ad71d31c2f4384..da9bd080a8dd5a2 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -13,6 +13,7 @@ import _imp import sys +import threading import types @@ -145,7 +146,7 @@ class _incompatible_extension_module_restrictions: You can get the same effect as this function by implementing the basic interface of multi-phase init (PEP 489) and lying about - support for mulitple interpreters (or per-interpreter GIL). + support for multiple interpreters (or per-interpreter GIL). """ def __init__(self, *, disable_check): @@ -171,36 +172,54 @@ class _LazyModule(types.ModuleType): def __getattribute__(self, attr): """Trigger the load of the module and return the attribute.""" - # All module metadata must be garnered from __spec__ in order to avoid - # using mutated values. - # Stop triggering this method. - self.__class__ = types.ModuleType - # Get the original name to make sure no object substitution occurred - # in sys.modules. - original_name = self.__spec__.name - # Figure out exactly what attributes were mutated between the creation - # of the module and now. - attrs_then = self.__spec__.loader_state['__dict__'] - attrs_now = self.__dict__ - attrs_updated = {} - for key, value in attrs_now.items(): - # Code that set the attribute may have kept a reference to the - # assigned object, making identity more important than equality. - if key not in attrs_then: - attrs_updated[key] = value - elif id(attrs_now[key]) != id(attrs_then[key]): - attrs_updated[key] = value - self.__spec__.loader.exec_module(self) - # If exec_module() was used directly there is no guarantee the module - # object was put into sys.modules. - if original_name in sys.modules: - if id(self) != id(sys.modules[original_name]): - raise ValueError(f"module object for {original_name!r} " - "substituted in sys.modules during a lazy " - "load") - # Update after loading since that's what would happen in an eager - # loading situation. - self.__dict__.update(attrs_updated) + __spec__ = object.__getattribute__(self, '__spec__') + loader_state = __spec__.loader_state + with loader_state['lock']: + # Only the first thread to get the lock should trigger the load + # and reset the module's class. The rest can now getattr(). + if object.__getattribute__(self, '__class__') is _LazyModule: + # The first thread comes here multiple times as it descends the + # call stack. The first time, it sets is_loading and triggers + # exec_module(), which will access module.__dict__, module.__name__, + # and/or module.__spec__, reentering this method. These accesses + # need to be allowed to proceed without triggering the load again. + if loader_state['is_loading'] and attr.startswith('__') and attr.endswith('__'): + return object.__getattribute__(self, attr) + loader_state['is_loading'] = True + + __dict__ = object.__getattribute__(self, '__dict__') + + # All module metadata must be gathered from __spec__ in order to avoid + # using mutated values. + # Get the original name to make sure no object substitution occurred + # in sys.modules. + original_name = __spec__.name + # Figure out exactly what attributes were mutated between the creation + # of the module and now. + attrs_then = loader_state['__dict__'] + attrs_now = __dict__ + attrs_updated = {} + for key, value in attrs_now.items(): + # Code that set an attribute may have kept a reference to the + # assigned object, making identity more important than equality. + if key not in attrs_then: + attrs_updated[key] = value + elif id(attrs_now[key]) != id(attrs_then[key]): + attrs_updated[key] = value + __spec__.loader.exec_module(self) + # If exec_module() was used directly there is no guarantee the module + # object was put into sys.modules. + if original_name in sys.modules: + if id(self) != id(sys.modules[original_name]): + raise ValueError(f"module object for {original_name!r} " + "substituted in sys.modules during a lazy " + "load") + # Update after loading since that's what would happen in an eager + # loading situation. + __dict__.update(attrs_updated) + # Finally, stop triggering this method. + self.__class__ = types.ModuleType + return getattr(self, attr) def __delattr__(self, attr): @@ -244,5 +263,7 @@ def exec_module(self, module): loader_state = {} loader_state['__dict__'] = module.__dict__.copy() loader_state['__class__'] = module.__class__ + loader_state['lock'] = threading.RLock() + loader_state['is_loading'] = False module.__spec__.loader_state = loader_state module.__class__ = _LazyModule diff --git a/Lib/inspect.py b/Lib/inspect.py index da504037ac282c9..8a2b2c96e993b5f 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -762,18 +762,14 @@ def unwrap(func, *, stop=None): :exc:`ValueError` is raised if a cycle is encountered. """ - if stop is None: - def _is_wrapper(f): - return hasattr(f, '__wrapped__') - else: - def _is_wrapper(f): - return hasattr(f, '__wrapped__') and not stop(f) f = func # remember the original func for error reporting # Memoise by id to tolerate non-hashable objects, but store objects to # ensure they aren't destroyed, which would allow their IDs to be reused. memo = {id(f): f} recursion_limit = sys.getrecursionlimit() - while _is_wrapper(func): + while not isinstance(func, type) and hasattr(func, '__wrapped__'): + if stop is not None and stop(func): + break func = func.__wrapped__ id_func = id(func) if (id_func in memo) or (len(memo) >= recursion_limit): @@ -2043,15 +2039,17 @@ def _signature_get_user_defined_method(cls, method_name): named ``method_name`` and returns it only if it is a pure python function. """ - try: - meth = getattr(cls, method_name) - except AttributeError: - return + if method_name == '__new__': + meth = getattr(cls, method_name, None) else: - if not isinstance(meth, _NonUserDefinedCallables): - # Once '__signature__' will be added to 'C'-level - # callables, this check won't be necessary - return meth + meth = getattr_static(cls, method_name, None) + if meth is None or isinstance(meth, _NonUserDefinedCallables): + # Once '__signature__' will be added to 'C'-level + # callables, this check won't be necessary + return None + if method_name != '__new__': + meth = _descriptor_get(meth, cls) + return meth def _signature_get_partial(wrapped_sig, partial, extra_args=()): @@ -2496,6 +2494,15 @@ def _signature_from_function(cls, func, skip_bound_arg=True, __validate_parameters__=is_duck_function) +def _descriptor_get(descriptor, obj): + if isclass(descriptor): + return descriptor + get = getattr(type(descriptor), '__get__', _sentinel) + if get is _sentinel: + return descriptor + return get(descriptor, obj, type(obj)) + + def _signature_from_callable(obj, *, follow_wrapper_chains=True, skip_bound_arg=True, @@ -2604,7 +2611,6 @@ def _signature_from_callable(obj, *, wrapped_sig = _get_signature_of(obj.func) return _signature_get_partial(wrapped_sig, obj) - sig = None if isinstance(obj, type): # obj is a class or a metaclass @@ -2612,88 +2618,65 @@ def _signature_from_callable(obj, *, # in its metaclass call = _signature_get_user_defined_method(type(obj), '__call__') if call is not None: - sig = _get_signature_of(call) - else: - factory_method = None - new = _signature_get_user_defined_method(obj, '__new__') - init = _signature_get_user_defined_method(obj, '__init__') - - # Go through the MRO and see if any class has user-defined - # pure Python __new__ or __init__ method - for base in obj.__mro__: - # Now we check if the 'obj' class has an own '__new__' method - if new is not None and '__new__' in base.__dict__: - factory_method = new - break - # or an own '__init__' method - elif init is not None and '__init__' in base.__dict__: - factory_method = init - break + return _get_signature_of(call) - if factory_method is not None: - sig = _get_signature_of(factory_method) - - if sig is None: - # At this point we know, that `obj` is a class, with no user- - # defined '__init__', '__new__', or class-level '__call__' - - for base in obj.__mro__[:-1]: - # Since '__text_signature__' is implemented as a - # descriptor that extracts text signature from the - # class docstring, if 'obj' is derived from a builtin - # class, its own '__text_signature__' may be 'None'. - # Therefore, we go through the MRO (except the last - # class in there, which is 'object') to find the first - # class with non-empty text signature. - try: - text_sig = base.__text_signature__ - except AttributeError: - pass - else: - if text_sig: - # If 'base' class has a __text_signature__ attribute: - # return a signature based on it - return _signature_fromstr(sigcls, base, text_sig) - - # No '__text_signature__' was found for the 'obj' class. - # Last option is to check if its '__init__' is - # object.__init__ or type.__init__. - if type not in obj.__mro__: - # We have a class (not metaclass), but no user-defined - # __init__ or __new__ for it - if (obj.__init__ is object.__init__ and - obj.__new__ is object.__new__): - # Return a signature of 'object' builtin. - return sigcls.from_callable(object) - else: - raise ValueError( - 'no signature found for builtin type {!r}'.format(obj)) + new = _signature_get_user_defined_method(obj, '__new__') + init = _signature_get_user_defined_method(obj, '__init__') - elif not isinstance(obj, _NonUserDefinedCallables): - # An object with __call__ - # We also check that the 'obj' is not an instance of - # types.WrapperDescriptorType or types.MethodWrapperType to avoid - # infinite recursion (and even potential segfault) - call = _signature_get_user_defined_method(type(obj), '__call__') - if call is not None: + # Go through the MRO and see if any class has user-defined + # pure Python __new__ or __init__ method + for base in obj.__mro__: + # Now we check if the 'obj' class has an own '__new__' method + if new is not None and '__new__' in base.__dict__: + sig = _get_signature_of(new) + if skip_bound_arg: + sig = _signature_bound_method(sig) + return sig + # or an own '__init__' method + elif init is not None and '__init__' in base.__dict__: + return _get_signature_of(init) + + # At this point we know, that `obj` is a class, with no user- + # defined '__init__', '__new__', or class-level '__call__' + + for base in obj.__mro__[:-1]: + # Since '__text_signature__' is implemented as a + # descriptor that extracts text signature from the + # class docstring, if 'obj' is derived from a builtin + # class, its own '__text_signature__' may be 'None'. + # Therefore, we go through the MRO (except the last + # class in there, which is 'object') to find the first + # class with non-empty text signature. try: - sig = _get_signature_of(call) - except ValueError as ex: - msg = 'no signature found for {!r}'.format(obj) - raise ValueError(msg) from ex - - if sig is not None: - # For classes and objects we skip the first parameter of their - # __call__, __new__, or __init__ methods - if skip_bound_arg: - return _signature_bound_method(sig) - else: - return sig + text_sig = base.__text_signature__ + except AttributeError: + pass + else: + if text_sig: + # If 'base' class has a __text_signature__ attribute: + # return a signature based on it + return _signature_fromstr(sigcls, base, text_sig) + + # No '__text_signature__' was found for the 'obj' class. + # Last option is to check if its '__init__' is + # object.__init__ or type.__init__. + if type not in obj.__mro__: + # We have a class (not metaclass), but no user-defined + # __init__ or __new__ for it + if (obj.__init__ is object.__init__ and + obj.__new__ is object.__new__): + # Return a signature of 'object' builtin. + return sigcls.from_callable(object) + else: + raise ValueError( + 'no signature found for builtin type {!r}'.format(obj)) - if isinstance(obj, types.BuiltinFunctionType): - # Raise a nicer error message for builtins - msg = 'no signature found for builtin function {!r}'.format(obj) - raise ValueError(msg) + else: + # An object with __call__ + call = getattr_static(type(obj), '__call__', None) + if call is not None: + call = _descriptor_get(call, obj) + return _get_signature_of(call) raise ValueError('callable {!r} is not supported by signature'.format(obj)) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index e398cc138308d93..7d6edcf2478a82f 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1333,18 +1333,38 @@ def is_reserved(self): @property @functools.lru_cache() def is_private(self): - """Test if this address is allocated for private networks. + """``True`` if the address is defined as not globally reachable by + iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ + (for IPv6) with the following exceptions: - Returns: - A boolean, True if the address is reserved per - iana-ipv4-special-registry. + * ``is_private`` is ``False`` for ``100.64.0.0/10`` + * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the + semantics of the underlying IPv4 addresses and the following condition holds + (see :attr:`IPv6Address.ipv4_mapped`):: + + address.is_private == address.ipv4_mapped.is_private + ``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10`` + IPv4 range where they are both ``False``. """ return any(self in net for net in self._constants._private_networks) @property @functools.lru_cache() def is_global(self): + """``True`` if the address is defined as globally reachable by + iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ + (for IPv6) with the following exception: + + For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the + semantics of the underlying IPv4 addresses and the following condition holds + (see :attr:`IPv6Address.ipv4_mapped`):: + + address.is_global == address.ipv4_mapped.is_global + + ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10`` + IPv4 range where they are both ``False``. + """ return self not in self._constants._public_network and not self.is_private @property @@ -2049,13 +2069,19 @@ def is_site_local(self): @property @functools.lru_cache() def is_private(self): - """Test if this address is allocated for private networks. + """``True`` if the address is defined as not globally reachable by + iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ + (for IPv6) with the following exceptions: - Returns: - A boolean, True if the address is reserved per - iana-ipv6-special-registry, or is ipv4_mapped and is - reserved in the iana-ipv4-special-registry. + * ``is_private`` is ``False`` for ``100.64.0.0/10`` + * For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the + semantics of the underlying IPv4 addresses and the following condition holds + (see :attr:`IPv6Address.ipv4_mapped`):: + + address.is_private == address.ipv4_mapped.is_private + ``is_private`` has value opposite to :attr:`is_global`, except for the ``100.64.0.0/10`` + IPv4 range where they are both ``False``. """ ipv4_mapped = self.ipv4_mapped if ipv4_mapped is not None: @@ -2064,12 +2090,18 @@ def is_private(self): @property def is_global(self): - """Test if this address is allocated for public networks. + """``True`` if the address is defined as globally reachable by + iana-ipv4-special-registry_ (for IPv4) or iana-ipv6-special-registry_ + (for IPv6) with the following exception: - Returns: - A boolean, true if the address is not reserved per - iana-ipv6-special-registry. + For IPv4-mapped IPv6-addresses the ``is_private`` value is determined by the + semantics of the underlying IPv4 addresses and the following condition holds + (see :attr:`IPv6Address.ipv4_mapped`):: + + address.is_global == address.ipv4_mapped.is_global + ``is_global`` has value opposite to :attr:`is_private`, except for the ``100.64.0.0/10`` + IPv4 range where they are both ``False``. """ return not self.is_private diff --git a/Lib/json/encoder.py b/Lib/json/encoder.py index 45f547741885a86..597849eca0524a3 100644 --- a/Lib/json/encoder.py +++ b/Lib/json/encoder.py @@ -174,7 +174,7 @@ def default(self, o): else: return list(iterable) # Let the base class default method raise the TypeError - return JSONEncoder.default(self, o) + return super().default(o) """ raise TypeError(f'Object of type {o.__class__.__name__} ' diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py index 895f11c3392253e..410bd9851f366df 100644 --- a/Lib/logging/handlers.py +++ b/Lib/logging/handlers.py @@ -23,11 +23,17 @@ To use, simply 'import logging.handlers' and log away! """ -import io, logging, socket, os, pickle, struct, time, re -from stat import ST_DEV, ST_INO, ST_MTIME +import copy +import io +import logging +import os +import pickle import queue +import re +import socket +import struct import threading -import copy +import time # # Some constants... @@ -232,19 +238,19 @@ def __init__(self, filename, when='h', interval=1, backupCount=0, if self.when == 'S': self.interval = 1 # one second self.suffix = "%Y-%m-%d_%H-%M-%S" - self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}(\.\w+)?$" + extMatch = r"(? (plen + 1) and not fileName[plen+1].isdigit()): - continue + # Our files could be just about anything after custom naming, + # but they should contain the datetime suffix. + # Try to find the datetime suffix in the file name and verify + # that the file name can be generated by this handler. + m = self.extMatch.search(fileName) + while m: + dfn = self.namer(self.baseFilename + "." + m[0]) + if os.path.basename(dfn) == fileName: + result.append(os.path.join(dirName, fileName)) + break + m = self.extMatch.search(fileName, m.start() + 1) - if fileName[:plen] == prefix: - suffix = fileName[plen:] - # See bpo-45628: The date/time suffix could be anywhere in the - # filename - - parts = suffix.split('.') - for part in parts: - if self.extMatch.match(part): - result.append(os.path.join(dirName, fileName)) - break if len(result) < self.backupCount: result = [] else: @@ -415,17 +422,14 @@ def doRollover(self): then we have to get a list of matching filenames, sort them and remove the one with the oldest suffix. """ - if self.stream: - self.stream.close() - self.stream = None # get the time that this sequence started at and make it a TimeTuple currentTime = int(time.time()) - dstNow = time.localtime(currentTime)[-1] t = self.rolloverAt - self.interval if self.utc: timeTuple = time.gmtime(t) else: timeTuple = time.localtime(t) + dstNow = time.localtime(currentTime)[-1] dstThen = timeTuple[-1] if dstNow != dstThen: if dstNow: @@ -436,26 +440,19 @@ def doRollover(self): dfn = self.rotation_filename(self.baseFilename + "." + time.strftime(self.suffix, timeTuple)) if os.path.exists(dfn): - os.remove(dfn) + # Already rolled over. + return + + if self.stream: + self.stream.close() + self.stream = None self.rotate(self.baseFilename, dfn) if self.backupCount > 0: for s in self.getFilesToDelete(): os.remove(s) if not self.delay: self.stream = self._open() - newRolloverAt = self.computeRollover(currentTime) - while newRolloverAt <= currentTime: - newRolloverAt = newRolloverAt + self.interval - #If DST changes and midnight or weekly rollover, adjust for this. - if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc: - dstAtRollover = time.localtime(newRolloverAt)[-1] - if dstNow != dstAtRollover: - if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour - addend = -3600 - else: # DST bows out before next rollover, so we need to add an hour - addend = 3600 - newRolloverAt += addend - self.rolloverAt = newRolloverAt + self.rolloverAt = self.computeRollover(currentTime) class WatchedFileHandler(logging.FileHandler): """ @@ -471,8 +468,7 @@ class WatchedFileHandler(logging.FileHandler): This handler is not appropriate for use under Windows, because under Windows open files cannot be moved or renamed - logging opens the files with exclusive locks - and so there is no need - for such a handler. Furthermore, ST_INO is not supported under - Windows; stat always returns zero for this value. + for such a handler. This handler is based on a suggestion and patch by Chad J. Schroeder. @@ -488,9 +484,11 @@ def __init__(self, filename, mode='a', encoding=None, delay=False, self._statstream() def _statstream(self): - if self.stream: - sres = os.fstat(self.stream.fileno()) - self.dev, self.ino = sres[ST_DEV], sres[ST_INO] + if self.stream is None: + return + sres = os.fstat(self.stream.fileno()) + self.dev = sres.st_dev + self.ino = sres.st_ino def reopenIfNeeded(self): """ @@ -500,6 +498,9 @@ def reopenIfNeeded(self): has, close the old stream and reopen the file to get the current stream. """ + if self.stream is None: + return + # Reduce the chance of race conditions by stat'ing by path only # once and then fstat'ing our new fd if we opened a new log stream. # See issue #14632: Thanks to John Mulligan for the problem report @@ -507,18 +508,23 @@ def reopenIfNeeded(self): try: # stat the file by path, checking for existence sres = os.stat(self.baseFilename) + + # compare file system stat with that of our stream file handle + reopen = (sres.st_dev != self.dev or sres.st_ino != self.ino) except FileNotFoundError: - sres = None - # compare file system stat with that of our stream file handle - if not sres or sres[ST_DEV] != self.dev or sres[ST_INO] != self.ino: - if self.stream is not None: - # we have an open file handle, clean it up - self.stream.flush() - self.stream.close() - self.stream = None # See Issue #21742: _open () might fail. - # open a new file handle and get new stat info from that fd - self.stream = self._open() - self._statstream() + reopen = True + + if not reopen: + return + + # we have an open file handle, clean it up + self.stream.flush() + self.stream.close() + self.stream = None # See Issue #21742: _open () might fail. + + # open a new file handle and get new stat info from that fd + self.stream = self._open() + self._statstream() def emit(self, record): """ diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py index 58d697fdecacc05..b7e1e132172d020 100644 --- a/Lib/multiprocessing/connection.py +++ b/Lib/multiprocessing/connection.py @@ -476,8 +476,9 @@ def accept(self): ''' if self._listener is None: raise OSError('listener is closed') + c = self._listener.accept() - if self._authkey: + if self._authkey is not None: deliver_challenge(c, self._authkey) answer_challenge(c, self._authkey) return c diff --git a/Lib/multiprocessing/queues.py b/Lib/multiprocessing/queues.py index 852ae87b2768612..925f043900004e7 100644 --- a/Lib/multiprocessing/queues.py +++ b/Lib/multiprocessing/queues.py @@ -20,8 +20,6 @@ from queue import Empty, Full -import _multiprocessing - from . import connection from . import context _ForkingPickler = context.reduction.ForkingPickler diff --git a/Lib/multiprocessing/util.py b/Lib/multiprocessing/util.py index 32871850ddec8b4..75dde02d88c5335 100644 --- a/Lib/multiprocessing/util.py +++ b/Lib/multiprocessing/util.py @@ -102,11 +102,7 @@ def log_to_stderr(level=None): # Abstract socket support def _platform_supports_abstract_sockets(): - if sys.platform == "linux": - return True - if hasattr(sys, 'getandroidapilevel'): - return True - return False + return sys.platform in ("linux", "android") def is_abstract_socket_namespace(address): diff --git a/Lib/opcode.py b/Lib/opcode.py index 88f4df7c0e8c386..5735686fa7fb449 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -56,6 +56,9 @@ "COMPARE_OP": { "counter": 1, }, + "CONTAINS_OP": { + "counter": 1, + }, "BINARY_SUBSCR": { "counter": 1, }, diff --git a/Lib/os.py b/Lib/os.py index 7f38e14e7bdd960..7661ce68ca3be2b 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -475,7 +475,7 @@ def fwalk(top=".", topdown=True, onerror=None, *, follow_symlinks=False, dir_fd= # lstat()/open()/fstat() trick. if not follow_symlinks: orig_st = stat(top, follow_symlinks=False, dir_fd=dir_fd) - topfd = open(top, O_RDONLY, dir_fd=dir_fd) + topfd = open(top, O_RDONLY | O_NONBLOCK, dir_fd=dir_fd) try: if (follow_symlinks or (st.S_ISDIR(orig_st.st_mode) and path.samestat(orig_st, stat(topfd)))): @@ -524,7 +524,7 @@ def _fwalk(topfd, toppath, isbytes, topdown, onerror, follow_symlinks): assert entries is not None name, entry = name orig_st = entry.stat(follow_symlinks=False) - dirfd = open(name, O_RDONLY, dir_fd=topfd) + dirfd = open(name, O_RDONLY | O_NONBLOCK, dir_fd=topfd) except OSError as err: if onerror is not None: onerror(err) diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 27c6b4e367a050f..645d62a0f0699a3 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -313,7 +313,14 @@ def with_name(self, name): def with_stem(self, stem): """Return a new path with the stem changed.""" - return self.with_name(stem + self.suffix) + suffix = self.suffix + if not suffix: + return self.with_name(stem) + elif not stem: + # If the suffix is non-empty, we can't make the stem empty. + raise ValueError(f"{self!r} has a non-empty suffix") + else: + return self.with_name(stem + suffix) def with_suffix(self, suffix): """Return a new path with the file suffix changed. If the path @@ -324,6 +331,7 @@ def with_suffix(self, suffix): if not suffix: return self.with_name(stem) elif not stem: + # If the stem is empty, we can't make the suffix non-empty. raise ValueError(f"{self!r} has an empty name") elif suffix.startswith('.') and len(suffix) > 1: return self.with_name(stem + suffix) @@ -781,7 +789,7 @@ def _make_child_direntry(self, entry): def _make_child_relpath(self, name): return self.joinpath(name) - def glob(self, pattern, *, case_sensitive=None, follow_symlinks=None): + def glob(self, pattern, *, case_sensitive=None, follow_symlinks=True): """Iterate over this subtree and yield all existing files (of any kind, including directories) matching the given relative pattern. """ @@ -838,7 +846,7 @@ def glob(self, pattern, *, case_sensitive=None, follow_symlinks=None): paths = _select_children(paths, bool(stack), follow_symlinks, match) return paths - def rglob(self, pattern, *, case_sensitive=None, follow_symlinks=None): + def rglob(self, pattern, *, case_sensitive=None, follow_symlinks=True): """Recursively yield all existing files (of any kind, including directories) matching the given relative pattern, anywhere in this subtree. diff --git a/Lib/pdb.py b/Lib/pdb.py index 0754e8b628cf570..88ea900e63f42b6 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -188,6 +188,7 @@ def namespace(self): __name__='__main__', __file__=self, __builtins__=__builtins__, + __spec__=None, ) @property @@ -362,26 +363,12 @@ def setup(self, f, tb): self._chained_exceptions[self._chained_exception_index], ) - return self.execRcLines() - - # Can be executed earlier than 'setup' if desired - def execRcLines(self): - if not self.rcLines: - return - # local copy because of recursion - rcLines = self.rcLines - rcLines.reverse() - # execute every line only once - self.rcLines = [] - while rcLines: - line = rcLines.pop().strip() - if line and line[0] != '#': - if self.onecmd(line): - # if onecmd returns True, the command wants to exit - # from the interaction, save leftover rc lines - # to execute before next interaction - self.rcLines += reversed(rcLines) - return True + if self.rcLines: + self.cmdqueue = [ + line for line in self.rcLines + if line.strip() and not line.strip().startswith("#") + ] + self.rcLines = [] # Override Bdb methods @@ -570,12 +557,10 @@ def interaction(self, frame, tb_or_exc): if isinstance(tb_or_exc, BaseException): assert tb is not None, "main exception must have a traceback" with self._hold_exceptions(_chained_exceptions): - if self.setup(frame, tb): - # no interaction desired at this time (happens if .pdbrc contains - # a command like "continue") - self.forget() - return - self.print_stack_entry(self.stack[self.curindex]) + self.setup(frame, tb) + # if we have more commands to process, do not show the stack entry + if not self.cmdqueue: + self.print_stack_entry(self.stack[self.curindex]) self._cmdloop() self.forget() @@ -711,7 +696,7 @@ def precmd(self, line): if marker >= 0: # queue up everything after marker next = line[marker+2:].lstrip() - self.cmdqueue.append(next) + self.cmdqueue.insert(0, next) line = line[:marker].rstrip() # Replace all the convenience variables @@ -736,13 +721,12 @@ def handle_command_def(self, line): """Handles one command line during command list definition.""" cmd, arg, line = self.parseline(line) if not cmd: - return + return False if cmd == 'silent': self.commands_silent[self.commands_bnum] = True - return # continue to handle other cmd def in the cmd list + return False # continue to handle other cmd def in the cmd list elif cmd == 'end': - self.cmdqueue = [] - return 1 # end of cmd list + return True # end of cmd list cmdlist = self.commands[self.commands_bnum] if arg: cmdlist.append(cmd+' '+arg) @@ -756,9 +740,8 @@ def handle_command_def(self, line): # one of the resuming commands if func.__name__ in self.commands_resuming: self.commands_doprompt[self.commands_bnum] = False - self.cmdqueue = [] - return 1 - return + return True + return False # interface abstraction functions diff --git a/Lib/platform.py b/Lib/platform.py index b56472235ee9e42..2756f298f9676ff 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -370,10 +370,7 @@ def win32_is_iot(): def win32_edition(): try: - try: - import winreg - except ImportError: - import _winreg as winreg + import winreg except ImportError: pass else: @@ -432,10 +429,7 @@ def _win32_ver(version, csd, ptype): csd = 'SP' + csd[13:] try: - try: - import winreg - except ImportError: - import _winreg as winreg + import winreg except ImportError: pass else: @@ -503,7 +497,7 @@ def mac_ver(release='', versioninfo=('', '', ''), machine=''): return release, versioninfo, machine def _java_getprop(name, default): - + """This private helper is deprecated in 3.13 and will be removed in 3.15""" from java.lang import System try: value = System.getProperty(name) @@ -525,6 +519,8 @@ def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')): given as parameters (which all default to ''). """ + import warnings + warnings._deprecated('java_ver', remove=(3, 15)) # Import the needed APIs try: import java.lang diff --git a/Lib/pydoc.py b/Lib/pydoc.py index d32fa8d05044177..08fd7aba7c94722 100755 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -855,9 +855,9 @@ def docmodule(self, object, name=None, mod=None, *ignored): cdict[key] = cdict[base] = modname + '.html#' + key funcs, fdict = [], {} for key, value in inspect.getmembers(object, inspect.isroutine): - # if __all__ exists, believe it. Otherwise use old heuristic. - if (all is not None or - inspect.isbuiltin(value) or inspect.getmodule(value) is object): + # if __all__ exists, believe it. Otherwise use a heuristic. + if (all is not None + or (inspect.getmodule(value) or object) is object): if visiblename(key, all, object): funcs.append((key, value)) fdict[key] = '#-' + key @@ -1299,9 +1299,9 @@ def docmodule(self, object, name=None, mod=None, *ignored): classes.append((key, value)) funcs = [] for key, value in inspect.getmembers(object, inspect.isroutine): - # if __all__ exists, believe it. Otherwise use old heuristic. - if (all is not None or - inspect.isbuiltin(value) or inspect.getmodule(value) is object): + # if __all__ exists, believe it. Otherwise use a heuristic. + if (all is not None + or (inspect.getmodule(value) or object) is object): if visiblename(key, all, object): funcs.append((key, value)) data = [] @@ -1685,8 +1685,17 @@ def plain(text): def pipepager(text, cmd): """Page through text by feeding it to another program.""" import subprocess + env = os.environ.copy() + prompt_string = ( + ' ' + '?ltline %lt?L/%L.' + ':byte %bB?s/%s.' + '.' + '?e (END):?pB %pB\\%..' + ' (press h for help or q to quit)') + env['LESS'] = '-RmPm{0}$PM{0}$'.format(prompt_string) proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, - errors='backslashreplace') + errors='backslashreplace', env=env) try: with proc.stdin as pipe: try: @@ -2495,6 +2504,7 @@ def __init__(self, urlhandler, host, port): threading.Thread.__init__(self) self.serving = False self.error = None + self.docserver = None def run(self): """Start the server.""" @@ -2527,9 +2537,9 @@ def stop(self): thread = ServerThread(urlhandler, hostname, port) thread.start() - # Wait until thread.serving is True to make sure we are - # really up before returning. - while not thread.error and not thread.serving: + # Wait until thread.serving is True and thread.docserver is set + # to make sure we are really up before returning. + while not thread.error and not (thread.serving and thread.docserver): time.sleep(.01) return thread diff --git a/Lib/pydoc_data/topics.py b/Lib/pydoc_data/topics.py index a49c38a51d39f20..05045ac8c945c86 100644 --- a/Lib/pydoc_data/topics.py +++ b/Lib/pydoc_data/topics.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Autogenerated by Sphinx on Thu Feb 15 14:30:52 2024 +# Autogenerated by Sphinx on Tue Mar 12 18:35:04 2024 # as part of the release process. topics = {'assert': 'The "assert" statement\n' '**********************\n' @@ -4724,7 +4724,7 @@ 'reflection,\n' ' and "__eq__()" and "__ne__()" are their own reflection. ' 'If the\n' - ' operands are of different types, and right operand’s ' + ' operands are of different types, and the right operand’s ' 'type is a\n' ' direct or indirect subclass of the left operand’s type, ' 'the\n' @@ -4734,6 +4734,11 @@ 'is not\n' ' considered.\n' '\n' + ' When no appropriate method returns any value other than\n' + ' "NotImplemented", the "==" and "!=" operators will fall ' + 'back to\n' + ' "is" and "is not", respectively.\n' + '\n' 'object.__hash__(self)\n' '\n' ' Called by built-in function "hash()" and for operations ' @@ -5223,15 +5228,15 @@ 'overridden by\n' 'the local file.\n' '\n' - 'Changed in version 3.11: ".pdbrc" is now read with "\'utf-8\'" ' - 'encoding.\n' - 'Previously, it was read with the system locale encoding.\n' - '\n' 'Changed in version 3.2: ".pdbrc" can now contain commands that\n' 'continue debugging, such as "continue" or "next". Previously, ' 'these\n' 'commands had no effect.\n' '\n' + 'Changed in version 3.11: ".pdbrc" is now read with "\'utf-8\'" ' + 'encoding.\n' + 'Previously, it was read with the system locale encoding.\n' + '\n' 'h(elp) [command]\n' '\n' ' Without argument, print the list of available commands. With ' @@ -8756,7 +8761,7 @@ '"__rsub__()"\n' ' method, "type(y).__rsub__(y, x)" is called if ' '"type(x).__sub__(x,\n' - ' y)" returns *NotImplemented*.\n' + ' y)" returns "NotImplemented".\n' '\n' ' Note that ternary "pow()" will not try calling ' '"__rpow__()" (the\n' @@ -8799,14 +8804,18 @@ 'the result\n' ' (which could be, but does not have to be, *self*). If a ' 'specific\n' - ' method is not defined, the augmented assignment falls ' - 'back to the\n' - ' normal methods. For instance, if *x* is an instance of ' - 'a class\n' - ' with an "__iadd__()" method, "x += y" is equivalent to ' - '"x =\n' - ' x.__iadd__(y)" . Otherwise, "x.__add__(y)" and ' - '"y.__radd__(x)" are\n' + ' method is not defined, or if that method returns ' + '"NotImplemented",\n' + ' the augmented assignment falls back to the normal ' + 'methods. For\n' + ' instance, if *x* is an instance of a class with an ' + '"__iadd__()"\n' + ' method, "x += y" is equivalent to "x = x.__iadd__(y)" . ' + 'If\n' + ' "__iadd__()" does not exist, or if "x.__iadd__(y)" ' + 'returns\n' + ' "NotImplemented", "x.__add__(y)" and "y.__radd__(x)" ' + 'are\n' ' considered, as with the evaluation of "x + y". In ' 'certain\n' ' situations, augmented assignment can result in ' @@ -8887,7 +8896,7 @@ 'Every object has an identity, a type and a value. An object’s\n' '*identity* never changes once it has been created; you may think ' 'of it\n' - 'as the object’s address in memory. The ‘"is"’ operator compares ' + 'as the object’s address in memory. The "is" operator compares ' 'the\n' 'identity of two objects; the "id()" function returns an integer\n' 'representing its identity.\n' @@ -8952,7 +8961,7 @@ 'Note that the use of the implementation’s tracing or debugging\n' 'facilities may keep objects alive that would normally be ' 'collectable.\n' - 'Also note that catching an exception with a ‘"try"…"except"’ ' + 'Also note that catching an exception with a "try"…"except" ' 'statement\n' 'may keep objects alive.\n' '\n' @@ -8967,8 +8976,9 @@ 'release the external resource, usually a "close()" method. ' 'Programs\n' 'are strongly recommended to explicitly close such objects. The\n' - '‘"try"…"finally"’ statement and the ‘"with"’ statement provide\n' - 'convenient ways to do this.\n' + '"try"…"finally" statement and the "with" statement provide ' + 'convenient\n' + 'ways to do this.\n' '\n' 'Some objects contain references to other objects; these are ' 'called\n' @@ -9345,10 +9355,7 @@ 'The try statement.\n' '\n' 'Changed in version 3.3: "None" is now permitted as "Y" in "raise X\n' - 'from Y".\n' - '\n' - 'New in version 3.3: The "__suppress_context__" attribute to ' - 'suppress\n' + 'from Y".Added the "__suppress_context__" attribute to suppress\n' 'automatic display of the exception context.\n' '\n' 'Changed in version 3.11: If the traceback of the active exception ' @@ -10133,8 +10140,8 @@ 'reflection,\n' ' and "__eq__()" and "__ne__()" are their own reflection. ' 'If the\n' - ' operands are of different types, and right operand’s type ' - 'is a\n' + ' operands are of different types, and the right operand’s ' + 'type is a\n' ' direct or indirect subclass of the left operand’s type, ' 'the\n' ' reflected method of the right operand has priority, ' @@ -10143,6 +10150,11 @@ 'is not\n' ' considered.\n' '\n' + ' When no appropriate method returns any value other than\n' + ' "NotImplemented", the "==" and "!=" operators will fall ' + 'back to\n' + ' "is" and "is not", respectively.\n' + '\n' 'object.__hash__(self)\n' '\n' ' Called by built-in function "hash()" and for operations ' @@ -11682,7 +11694,7 @@ '"__rsub__()"\n' ' method, "type(y).__rsub__(y, x)" is called if ' '"type(x).__sub__(x,\n' - ' y)" returns *NotImplemented*.\n' + ' y)" returns "NotImplemented".\n' '\n' ' Note that ternary "pow()" will not try calling ' '"__rpow__()" (the\n' @@ -11725,14 +11737,17 @@ 'the result\n' ' (which could be, but does not have to be, *self*). If a ' 'specific\n' - ' method is not defined, the augmented assignment falls ' - 'back to the\n' - ' normal methods. For instance, if *x* is an instance of a ' - 'class\n' - ' with an "__iadd__()" method, "x += y" is equivalent to "x ' - '=\n' - ' x.__iadd__(y)" . Otherwise, "x.__add__(y)" and ' - '"y.__radd__(x)" are\n' + ' method is not defined, or if that method returns ' + '"NotImplemented",\n' + ' the augmented assignment falls back to the normal ' + 'methods. For\n' + ' instance, if *x* is an instance of a class with an ' + '"__iadd__()"\n' + ' method, "x += y" is equivalent to "x = x.__iadd__(y)" . ' + 'If\n' + ' "__iadd__()" does not exist, or if "x.__iadd__(y)" ' + 'returns\n' + ' "NotImplemented", "x.__add__(y)" and "y.__radd__(x)" are\n' ' considered, as with the evaluation of "x + y". In ' 'certain\n' ' situations, augmented assignment can result in unexpected ' @@ -13080,9 +13095,8 @@ '\n' 'New in version 3.3: The "\'rb\'" prefix of raw bytes literals has ' 'been\n' - 'added as a synonym of "\'br\'".\n' - '\n' - 'New in version 3.3: Support for the unicode legacy literal\n' + 'added as a synonym of "\'br\'".Support for the unicode legacy ' + 'literal\n' '("u\'value\'") was reintroduced to simplify the maintenance of ' 'dual\n' 'Python 2.x and 3.x codebases. See **PEP 414** for more ' @@ -14765,6 +14779,17 @@ 'tools.\n' ' The PEP that introduced the "co_lines()" method.\n' '\n' + 'codeobject.replace(**kwargs)\n' + '\n' + ' Return a copy of the code object with new values for the ' + 'specified\n' + ' fields.\n' + '\n' + ' Code objects are also supported by the generic function\n' + ' "copy.replace()".\n' + '\n' + ' New in version 3.8.\n' + '\n' '\n' 'Frame objects\n' '-------------\n' @@ -16109,7 +16134,7 @@ '\n' ' For sorting examples and a brief sorting tutorial, see ' 'Sorting\n' - ' HOW TO.\n' + ' Techniques.\n' '\n' ' **CPython implementation detail:** While a list is being ' 'sorted,\n' @@ -16324,9 +16349,8 @@ 'objects\n' 'based on the sequence of values they define (instead of ' 'comparing\n' - 'based on object identity).\n' - '\n' - 'New in version 3.3: The "start", "stop" and "step" attributes.\n' + 'based on object identity).Added the "start", "stop" and "step"\n' + 'attributes.\n' '\n' 'See also:\n' '\n' diff --git a/Lib/queue.py b/Lib/queue.py index 467ff4fcecb1340..387ce5425879a44 100644 --- a/Lib/queue.py +++ b/Lib/queue.py @@ -10,7 +10,15 @@ except ImportError: SimpleQueue = None -__all__ = ['Empty', 'Full', 'Queue', 'PriorityQueue', 'LifoQueue', 'SimpleQueue'] +__all__ = [ + 'Empty', + 'Full', + 'ShutDown', + 'Queue', + 'PriorityQueue', + 'LifoQueue', + 'SimpleQueue', +] try: @@ -72,10 +80,11 @@ def task_done(self): have been processed (meaning that a task_done() call was received for every item that had been put() into the queue). + shutdown(immediate=True) calls task_done() for each remaining item in + the queue. + Raises a ValueError if called more times than there were items placed in the queue. - - Raises ShutDown if the queue has been shut down immediately. ''' with self.all_tasks_done: unfinished = self.unfinished_tasks - 1 @@ -93,8 +102,6 @@ def join(self): to indicate the item was retrieved and all work on it is complete. When the count of unfinished tasks drops to zero, join() unblocks. - - Raises ShutDown if the queue has been shut down immediately. ''' with self.all_tasks_done: while self.unfinished_tasks: @@ -227,18 +234,17 @@ def get_nowait(self): return self.get(block=False) def shutdown(self, immediate=False): - '''Shut-down the queue, making queue gets and puts raise. + '''Shut-down the queue, making queue gets and puts raise ShutDown. By default, gets will only raise once the queue is empty. Set 'immediate' to True to make gets raise immediately instead. All blocked callers of put() will be unblocked, and also get() - and join() if 'immediate'. The ShutDown exception is raised. + and join() if 'immediate'. ''' with self.mutex: self.is_shutdown = True if immediate: - n_items = self._qsize() while self._qsize(): self._get() if self.unfinished_tasks > 0: diff --git a/Lib/shutil.py b/Lib/shutil.py index c19ea0607208afa..94b09509008b0bb 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -47,7 +47,8 @@ COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 64 * 1024 # This should never be removed, see rationale in: # https://bugs.python.org/issue43743#msg393429 -_USE_CP_SENDFILE = hasattr(os, "sendfile") and sys.platform.startswith("linux") +_USE_CP_SENDFILE = (hasattr(os, "sendfile") + and sys.platform.startswith(("linux", "android"))) _HAS_FCOPYFILE = posix and hasattr(posix, "_fcopyfile") # macOS # CMD defaults in Windows 10 @@ -680,7 +681,7 @@ def _rmtree_safe_fd(topfd, path, onexc): continue if is_dir: try: - dirfd = os.open(entry.name, os.O_RDONLY, dir_fd=topfd) + dirfd = os.open(entry.name, os.O_RDONLY | os.O_NONBLOCK, dir_fd=topfd) dirfd_closed = False except FileNotFoundError: continue @@ -785,7 +786,7 @@ def onexc(*args): onexc(os.lstat, path, err) return try: - fd = os.open(path, os.O_RDONLY, dir_fd=dir_fd) + fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK, dir_fd=dir_fd) fd_closed = False except OSError as err: onexc(os.open, path, err) diff --git a/Lib/site.py b/Lib/site.py index 0631f3f6115ec0c..2aee63e24ca52be 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -460,60 +460,64 @@ def gethistoryfile(): def enablerlcompleter(): """Enable default readline configuration on interactive prompts, by registering a sys.__interactivehook__. + """ + sys.__interactivehook__ = register_readline + + +def register_readline(): + """Configure readline completion on interactive prompts. If the readline module can be imported, the hook will set the Tab key as completion key and register ~/.python_history as history file. This can be overridden in the sitecustomize or usercustomize module, or in a PYTHONSTARTUP file. """ - def register_readline(): - import atexit - try: - import readline - import rlcompleter - except ImportError: - return - - # Reading the initialization (config) file may not be enough to set a - # completion key, so we set one first and then read the file. - if readline.backend == 'editline': - readline.parse_and_bind('bind ^I rl_complete') - else: - readline.parse_and_bind('tab: complete') + import atexit + try: + import readline + import rlcompleter + except ImportError: + return + # Reading the initialization (config) file may not be enough to set a + # completion key, so we set one first and then read the file. + if readline.backend == 'editline': + readline.parse_and_bind('bind ^I rl_complete') + else: + readline.parse_and_bind('tab: complete') + + try: + readline.read_init_file() + except OSError: + # An OSError here could have many causes, but the most likely one + # is that there's no .inputrc file (or .editrc file in the case of + # Mac OS X + libedit) in the expected location. In that case, we + # want to ignore the exception. + pass + + if readline.get_current_history_length() == 0: + # If no history was loaded, default to .python_history, + # or PYTHON_HISTORY. + # The guard is necessary to avoid doubling history size at + # each interpreter exit when readline was already configured + # through a PYTHONSTARTUP hook, see: + # http://bugs.python.org/issue5845#msg198636 + history = gethistoryfile() try: - readline.read_init_file() + readline.read_history_file(history) except OSError: - # An OSError here could have many causes, but the most likely one - # is that there's no .inputrc file (or .editrc file in the case of - # Mac OS X + libedit) in the expected location. In that case, we - # want to ignore the exception. pass - if readline.get_current_history_length() == 0: - # If no history was loaded, default to .python_history, - # or PYTHON_HISTORY. - # The guard is necessary to avoid doubling history size at - # each interpreter exit when readline was already configured - # through a PYTHONSTARTUP hook, see: - # http://bugs.python.org/issue5845#msg198636 - history = gethistoryfile() + def write_history(): try: - readline.read_history_file(history) - except OSError: + readline.write_history_file(history) + except (FileNotFoundError, PermissionError): + # home directory does not exist or is not writable + # https://bugs.python.org/issue19891 pass - def write_history(): - try: - readline.write_history_file(history) - except OSError: - # bpo-19891, bpo-41193: Home directory does not exist - # or is not writable, or the filesystem is read-only. - pass + atexit.register(write_history) - atexit.register(write_history) - - sys.__interactivehook__ = register_readline def venv(known_paths): global PREFIXES, ENABLE_USER_SITE diff --git a/Lib/ssl.py b/Lib/ssl.py index 03d0121891ff4cb..cc685c2cc405abb 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -704,6 +704,16 @@ def create_default_context(purpose=Purpose.SERVER_AUTH, *, cafile=None, else: raise ValueError(purpose) + # `VERIFY_X509_PARTIAL_CHAIN` makes OpenSSL's chain building behave more + # like RFC 3280 and 5280, which specify that chain building stops with the + # first trust anchor, even if that anchor is not self-signed. + # + # `VERIFY_X509_STRICT` makes OpenSSL more conservative about the + # certificates it accepts, including "disabling workarounds for + # some broken certificates." + context.verify_flags |= (_ssl.VERIFY_X509_PARTIAL_CHAIN | + _ssl.VERIFY_X509_STRICT) + if cafile or capath or cadata: context.load_verify_locations(cafile, capath, cadata) elif context.verify_mode != CERT_NONE: diff --git a/Lib/statistics.py b/Lib/statistics.py index 83aaedb04515e02..5d636258fd442b2 100644 --- a/Lib/statistics.py +++ b/Lib/statistics.py @@ -112,6 +112,7 @@ 'fmean', 'geometric_mean', 'harmonic_mean', + 'kde', 'linear_regression', 'mean', 'median', @@ -137,7 +138,7 @@ from itertools import count, groupby, repeat from bisect import bisect_left, bisect_right from math import hypot, sqrt, fabs, exp, erf, tau, log, fsum, sumprod -from math import isfinite, isinf +from math import isfinite, isinf, pi, cos, cosh from functools import reduce from operator import itemgetter from collections import Counter, namedtuple, defaultdict @@ -802,6 +803,171 @@ def multimode(data): return [value for value, count in counts.items() if count == maxcount] +def kde(data, h, kernel='normal'): + """Kernel Density Estimation: Create a continuous probability + density function from discrete samples. + + The basic idea is to smooth the data using a kernel function + to help draw inferences about a population from a sample. + + The degree of smoothing is controlled by the scaling parameter h + which is called the bandwidth. Smaller values emphasize local + features while larger values give smoother results. + + The kernel determines the relative weights of the sample data + points. Generally, the choice of kernel shape does not matter + as much as the more influential bandwidth smoothing parameter. + + Kernels that give some weight to every sample point: + + normal or gauss + logistic + sigmoid + + Kernels that only give weight to sample points within + the bandwidth: + + rectangular or uniform + triangular + parabolic or epanechnikov + quartic or biweight + triweight + cosine + + A StatisticsError will be raised if the data sequence is empty. + + Example + ------- + + Given a sample of six data points, construct a continuous + function that estimates the underlying probability density: + + >>> sample = [-2.1, -1.3, -0.4, 1.9, 5.1, 6.2] + >>> f_hat = kde(sample, h=1.5) + + Compute the area under the curve: + + >>> sum(f_hat(x) for x in range(-20, 20)) + 1.0 + + Plot the estimated probability density function at + evenly spaced points from -6 to 10: + + >>> for x in range(-6, 11): + ... density = f_hat(x) + ... plot = ' ' * int(density * 400) + 'x' + ... print(f'{x:2}: {density:.3f} {plot}') + ... + -6: 0.002 x + -5: 0.009 x + -4: 0.031 x + -3: 0.070 x + -2: 0.111 x + -1: 0.125 x + 0: 0.110 x + 1: 0.086 x + 2: 0.068 x + 3: 0.059 x + 4: 0.066 x + 5: 0.082 x + 6: 0.082 x + 7: 0.058 x + 8: 0.028 x + 9: 0.009 x + 10: 0.002 x + + References + ---------- + + Kernel density estimation and its application: + https://www.itm-conferences.org/articles/itmconf/pdf/2018/08/itmconf_sam2018_00037.pdf + + Kernel functions in common use: + https://en.wikipedia.org/wiki/Kernel_(statistics)#kernel_functions_in_common_use + + Interactive graphical demonstration and exploration: + https://demonstrations.wolfram.com/KernelDensityEstimation/ + + """ + + n = len(data) + if not n: + raise StatisticsError('Empty data sequence') + + if not isinstance(data[0], (int, float)): + raise TypeError('Data sequence must contain ints or floats') + + if h <= 0.0: + raise StatisticsError(f'Bandwidth h must be positive, not {h=!r}') + + match kernel: + + case 'normal' | 'gauss': + c = 1 / sqrt(2 * pi) + K = lambda t: c * exp(-1/2 * t * t) + support = None + + case 'logistic': + # 1.0 / (exp(t) + 2.0 + exp(-t)) + K = lambda t: 1/2 / (1.0 + cosh(t)) + support = None + + case 'sigmoid': + # (2/pi) / (exp(t) + exp(-t)) + c = 1 / pi + K = lambda t: c / cosh(t) + support = None + + case 'rectangular' | 'uniform': + K = lambda t: 1/2 + support = 1.0 + + case 'triangular': + K = lambda t: 1.0 - abs(t) + support = 1.0 + + case 'parabolic' | 'epanechnikov': + K = lambda t: 3/4 * (1.0 - t * t) + support = 1.0 + + case 'quartic' | 'biweight': + K = lambda t: 15/16 * (1.0 - t * t) ** 2 + support = 1.0 + + case 'triweight': + K = lambda t: 35/32 * (1.0 - t * t) ** 3 + support = 1.0 + + case 'cosine': + c1 = pi / 4 + c2 = pi / 2 + K = lambda t: c1 * cos(c2 * t) + support = 1.0 + + case _: + raise StatisticsError(f'Unknown kernel name: {kernel!r}') + + if support is None: + + def pdf(x): + return sum(K((x - x_i) / h) for x_i in data) / (n * h) + + else: + + sample = sorted(data) + bandwidth = h * support + + def pdf(x): + i = bisect_left(sample, x - bandwidth) + j = bisect_right(sample, x + bandwidth) + supported = sample[i : j] + return sum(K((x - x_i) / h) for x_i in supported) / (n * h) + + pdf.__doc__ = f'PDF estimate with {h=!r} and {kernel=!r}' + + return pdf + + # Notes on methods for computing quantiles # ---------------------------------------- # diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 20db7747d5db13c..1437bf8148282c0 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -350,7 +350,7 @@ def _args_from_interpreter_flags(): if dev_mode: args.extend(('-X', 'dev')) for opt in ('faulthandler', 'tracemalloc', 'importtime', - 'frozen_modules', 'showrefcount', 'utf8'): + 'frozen_modules', 'showrefcount', 'utf8', 'gil'): if opt in xoptions: value = xoptions[opt] if value is True: diff --git a/Lib/tarfile.py b/Lib/tarfile.py index f4dd0fdab4a3e4d..6f315a6408f185e 100755 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -872,7 +872,7 @@ class TarInfo(object): pax_headers = ('A dictionary containing key-value pairs of an ' 'associated pax extended header.'), sparse = 'Sparse member information.', - tarfile = None, + _tarfile = None, _sparse_structs = None, _link_target = None, ) @@ -901,6 +901,24 @@ def __init__(self, name=""): self.sparse = None # sparse member information self.pax_headers = {} # pax header information + @property + def tarfile(self): + import warnings + warnings.warn( + 'The undocumented "tarfile" attribute of TarInfo objects ' + + 'is deprecated and will be removed in Python 3.16', + DeprecationWarning, stacklevel=2) + return self._tarfile + + @tarfile.setter + def tarfile(self, tarfile): + import warnings + warnings.warn( + 'The undocumented "tarfile" attribute of TarInfo objects ' + + 'is deprecated and will be removed in Python 3.16', + DeprecationWarning, stacklevel=2) + self._tarfile = tarfile + @property def path(self): 'In pax headers, "name" is called "path".' @@ -2030,7 +2048,7 @@ def gettarinfo(self, name=None, arcname=None, fileobj=None): # Now, fill the TarInfo object with # information specific for the file. tarinfo = self.tarinfo() - tarinfo.tarfile = self # Not needed + tarinfo._tarfile = self # To be removed in 3.16. # Use os.stat or os.lstat, depending on if symlinks shall be resolved. if fileobj is None: diff --git a/Lib/test/_test_embed_set_config.py b/Lib/test/_test_embed_set_config.py index a2ddd133cf47c87..5ff521892cb6fe2 100644 --- a/Lib/test/_test_embed_set_config.py +++ b/Lib/test/_test_embed_set_config.py @@ -9,11 +9,49 @@ import os import sys import unittest +from test import support from test.support import MS_WINDOWS MAX_HASH_SEED = 4294967295 + +BOOL_OPTIONS = [ + 'isolated', + 'use_environment', + 'dev_mode', + 'install_signal_handlers', + 'use_hash_seed', + 'faulthandler', + 'import_time', + 'code_debug_ranges', + 'show_ref_count', + 'dump_refs', + 'malloc_stats', + 'parse_argv', + 'site_import', + 'warn_default_encoding', + 'inspect', + 'interactive', + 'parser_debug', + 'write_bytecode', + 'quiet', + 'user_site_directory', + 'configure_c_stdio', + 'buffered_stdio', + 'use_frozen_modules', + 'safe_path', + 'pathconfig_warnings', + 'module_search_paths_set', + 'skip_source_first_line', + '_install_importlib', + '_init_main', + '_is_python_build', +] +if MS_WINDOWS: + BOOL_OPTIONS.append('legacy_windows_stdio') + + class SetConfigTests(unittest.TestCase): def setUp(self): self.old_config = _testinternalcapi.get_config() @@ -52,42 +90,15 @@ def test_set_invalid(self): ] # int (unsigned) - options = [ + int_options = [ '_config_init', - 'isolated', - 'use_environment', - 'dev_mode', - 'install_signal_handlers', - 'use_hash_seed', - 'faulthandler', - 'tracemalloc', - 'import_time', - 'code_debug_ranges', - 'show_ref_count', - 'dump_refs', - 'malloc_stats', - 'parse_argv', - 'site_import', 'bytes_warning', - 'inspect', - 'interactive', 'optimization_level', - 'parser_debug', - 'write_bytecode', + 'tracemalloc', 'verbose', - 'quiet', - 'user_site_directory', - 'configure_c_stdio', - 'buffered_stdio', - 'pathconfig_warnings', - 'module_search_paths_set', - 'skip_source_first_line', - '_install_importlib', - '_init_main', ] - if MS_WINDOWS: - options.append('legacy_windows_stdio') - for key in options: + int_options.extend(BOOL_OPTIONS) + for key in int_options: value_tests.append((key, invalid_uint)) type_tests.append((key, "abc")) type_tests.append((key, 2.0)) @@ -148,6 +159,7 @@ def test_set_invalid(self): _testinternalcapi.set_config(config) def test_flags(self): + bool_options = set(BOOL_OPTIONS) for sys_attr, key, value in ( ("debug", "parser_debug", 1), ("inspect", "inspect", 2), @@ -160,7 +172,10 @@ def test_flags(self): ): with self.subTest(sys=sys_attr, key=key, value=value): self.set_config(**{key: value, 'parse_argv': 0}) - self.assertEqual(getattr(sys.flags, sys_attr), value) + if key in bool_options: + self.assertEqual(getattr(sys.flags, sys_attr), int(bool(value))) + else: + self.assertEqual(getattr(sys.flags, sys_attr), value) self.set_config(write_bytecode=0) self.assertEqual(sys.flags.dont_write_bytecode, True) @@ -197,6 +212,19 @@ def test_flags(self): self.set_config(use_hash_seed=1, hash_seed=123) self.assertEqual(sys.flags.hash_randomization, 1) + if support.Py_GIL_DISABLED: + self.set_config(enable_gil=-1) + self.assertEqual(sys.flags.gil, None) + self.set_config(enable_gil=0) + self.assertEqual(sys.flags.gil, 0) + self.set_config(enable_gil=1) + self.assertEqual(sys.flags.gil, 1) + else: + # Builds without Py_GIL_DISABLED don't have + # PyConfig.enable_gil. sys.flags.gil is always defined to 1, for + # consistency. + self.assertEqual(sys.flags.gil, 1) + def test_options(self): self.check(warnoptions=[]) self.check(warnoptions=["default", "ignore"]) diff --git a/Lib/test/_test_monitoring_shutdown.py b/Lib/test/_test_monitoring_shutdown.py new file mode 100644 index 000000000000000..3d0fbec029dc09c --- /dev/null +++ b/Lib/test/_test_monitoring_shutdown.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +# gh-115832: An object destructor running during the final GC of interpreter +# shutdown triggered an infinite loop in the instrumentation code. + +import sys + +class CallableCycle: + def __init__(self): + self._cycle = self + + def __del__(self): + pass + + def __call__(self, code, instruction_offset): + pass + +def tracefunc(frame, event, arg): + pass + +def main(): + tool_id = sys.monitoring.PROFILER_ID + event_id = sys.monitoring.events.PY_START + + sys.monitoring.use_tool_id(tool_id, "test profiler") + sys.monitoring.set_events(tool_id, event_id) + sys.monitoring.register_callback(tool_id, event_id, CallableCycle()) + +if __name__ == "__main__": + sys.exit(main()) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index e4183eea959dd58..b63b567bbcad08e 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -3504,6 +3504,30 @@ def test_context(self): if self.TYPE == 'processes': self.assertRaises(OSError, l.accept) + def test_empty_authkey(self): + # bpo-43952: allow empty bytes as authkey + def handler(*args): + raise RuntimeError('Connection took too long...') + + def run(addr, authkey): + client = self.connection.Client(addr, authkey=authkey) + client.send(1729) + + key = b'' + + with self.connection.Listener(authkey=key) as listener: + thread = threading.Thread(target=run, args=(listener.address, key)) + thread.start() + try: + with listener.accept() as d: + self.assertEqual(d.recv(), 1729) + finally: + thread.join() + + if self.TYPE == 'processes': + with self.assertRaises(OSError): + listener.accept() + @unittest.skipUnless(util.abstract_sockets_supported, "test needs abstract socket support") def test_abstract_socket(self): @@ -3971,6 +3995,21 @@ def _new_shm_name(self, prefix): # test_multiprocessing_spawn, etc) in parallel. return prefix + str(os.getpid()) + def test_shared_memory_name_with_embedded_null(self): + name_tsmb = self._new_shm_name('test01_null') + sms = shared_memory.SharedMemory(name_tsmb, create=True, size=512) + self.addCleanup(sms.unlink) + with self.assertRaises(ValueError): + shared_memory.SharedMemory(name_tsmb + '\0a', create=False, size=512) + if shared_memory._USE_POSIX: + orig_name = sms._name + try: + sms._name = orig_name + '\0a' + with self.assertRaises(ValueError): + sms.unlink() + finally: + sms._name = orig_name + def test_shared_memory_basics(self): name_tsmb = self._new_shm_name('test01_tsmb') sms = shared_memory.SharedMemory(name_tsmb, create=True, size=512) @@ -4105,7 +4144,7 @@ def test_shared_memory_recreate(self): self.addCleanup(shm2.unlink) self.assertEqual(shm2._name, names[1]) - def test_invalid_shared_memory_cration(self): + def test_invalid_shared_memory_creation(self): # Test creating a shared memory segment with negative size with self.assertRaises(ValueError): sms_invalid = shared_memory.SharedMemory(create=True, size=-1) diff --git a/Lib/test/certdata/allsans.pem b/Lib/test/certdata/allsans.pem index e400e178a1f5994..02f2c2e6346ef88 100644 --- a/Lib/test/certdata/allsans.pem +++ b/Lib/test/certdata/allsans.pem @@ -1,42 +1,42 @@ -----BEGIN PRIVATE KEY----- -MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQDBGvj+Uy/VUyTR -mmIA1UEENThh0+pWODcvvUlkeIo+XTJ3FhF4/RVjImDHjozl28Xf2TzKnvQJa1KC -pqa7fr8cL9QMwk4pH+S4ulxOu02Bl3Yafx2oJVUML37vciJg+zkzPx1k3tXFjXkr -LGjZwOoufBC3AmPuq2xHFBzHrvp5/DIRH2slQFM9fpVZzN77gYyzxba0wCfCPpCf -eJFRyYKW8c7MXrwnM82YtE7Rlnf227EkCdMNaSeZLUIxeVpcnScqZl0SIbR3YEiV -0LPFkx0wJFm8qUEFU/h+0jamgy/ON+11nqmMlp3BjNi/JTVsa7N7A3dvdHC7VVlr -WnUgU6MoSniyL6ijpucyHtZzK2mJy0sHR8PadHKow0O423/5N8GKTSOvaGMXTjAe -OGs+9/P1ZYo3IjjQPz/NV3QlhK8zRqxF3cW0ekHHkT+/jZjCvSKm6mdbMQunKE1W -+dokAc815pb48Mzf1eWKd/7UyUf7CXussyAaJ3clpaK1sbbn9m0CAwEAAQKCAYAe -BaCCgdJk+xk1USg9cuo5ykBqzTSYlQLXdDlN2oO7sGehJhgvVEGX+QdM3ze+oM2B -wNd3tQDB2iKo11oCunDh4/m2xhq6wA+iPK8POoWRSUf+VJb6xlsTmurENV1s8IHz -GrPqM87OePFGqg/fEuQVuAotObzppVMfNdxHm0er4W6zRMw2rWqDnAOCQ5zDQ1/p -ryp5rYpA49M+R9NoAMlByHRbR7s+6Qnk3NuIMDmUcpF2xeQ/KIMUiHnLEU/gKDpi -bsk+VtyjlibR4zhh9/cJrLTApAIA+4eC176EJvKXCh5UIjd92JC7741HTNQXJpvG -9PXbzhyUCmncr04U+46snGHdwD+lG4LS7oBGACTLMtpcMrlgAm6XCg4T8gRVE/9n -FvCkqPHBR+vnhOxm+0x0yUY/DstJby6IPYPsfGK/s2n//j/vJrAZE1Pxlm9EPU13 -MRLcHstwjAc/NXRPnUN1DfcQvPLx6Tt6rqw3Wm1KO75kM+HZ56BX9/Bi1TgkiI0C -gcEA5JTlXssJ3W8Cz6w1ZtGsThHQBDbvHF2D5AdqO7y6/eqzCQgBQl9BTfXOzsvP -I1gf2CLEFBtGK09UjAuJQg90/NlKur7i7xt7HpAzEfGsDAL4P5BW5JnMNrzpJjjL -0uUDsPJlA75Wi29N2SFiaIslY0sZ6nckInat5GRe4O1AMSHoJ5suY9yTZTU3XB4O -A+XyddutI1GsFZgl8/8LyyNMcyNjxG3T5sr7IKf5/nIv6oMDjC2zLVZa8QS/MEnL -Kaa7AoHBANhEsxfcjw2MaPkrsqAsOP0dDf7g2rdz6wKT5BzZu9e+/E76NmvVDpns -e+kCjql9Os3/wonOMINvn1bTCQGTgk8+dw1fMyqg+zQCvH4ImcE6LSqhzblVHsIB -zZ7rW86trri1U9+olNHG4nwkus0i4LV8eeORns+j8DgXr6/eOvjX3ZW5TyU7/Qgm -SiSdBapzJbom3xJrbo9KQsrN5PVCOwuwrgY0o+2BeKyKhnt4uGv0bR+ii06EOJUA -WvjD7gLI9wKBwGVRXk3jH29IOm3EvjLh80bzfEmx89CV3tUfOEZcRGIyOsNhCfXa -dP7SWqWtDxZyhELwPgtPf43I7wfYQTHH2ioNQqN94ubrPmpwrkJg5cq5MkIyf2F6 -jlsg5xMrD6VeH4G6H25GWuQZJN9+fbkrHBpj+ovD3X9tLWzT1H5Miyx8BAQyM6DN -74Nn0C8Dn2C49vyor5i9JdK4ivIY9ahH8CYE5L73k3p0NFXoPtY61ORUyCjFROtu -oIa+fOQxgVzn6wKBwQC3DD7BnY7/Gq7m51ODOqrpoaPs7Qhyagyp298hhDD3hNEt -T56sWmLHaV/fcqipUDNrlGRmGzz4ooutA2YGDYIn7Gj7ym4WULcN6Jr92e25nLIJ -+XWUvjUQZFJThkXogxz1fZSGI7wCamHcTYJGipTDR54rPV+7w7hY4cN0CZbEdIE6 -buRMUZ/zO+VZZAYdpORz0N7SSlgDtAkgenCmHe64EEzbN8bgCcvHzl/RNfZyeSm7 -supSBJuXkfttvvg/JzUCgcEAlx0Pep9qCLvpk0WqzijBVHc3zK4wYxjhN2MBkF42 -SLWfogKpiPfIqxX6YF94roIA0VlW6Pj50v+sbPwq8nwsgFNhml80A4ODKr3O3Y3M -fXDBJW5W5ZRb/vhIKRjXyCSckSRfj7N8HUYjCLkxQansNWimrldmSet0H2mYJN0Y -JpBXdqpa76zoHzWpKFwD0fSVzvnMelPHSDCNOdIEHmR8e1x2F1/ufR/9/dBzPULY -HMj0OhQHoi8kJyMIj3+bQkbC +MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQCczEVv5D2UDtn6 +DMmZ/uCWCLyL+K5xTZp/5j3cyISoaTuU1Ku3kD97eLgpHj4Fgk5ZJi21zsQqepCj +jAhBk6tj6RYUcnMbb8MuxUkQMEDW+5LfSyp+HCaetlHosWdhEDqX4kpJ5ajBwNRt +07mxQExtC4kcno0ut9rG5XzLN29XpCpRHlFFrntOgQAEoiz9/fc8qaTgb37RgGYP +Qsxh7PcRDRe4ZGx1l06Irr8Y+2W50zWCfkwCS3DaLDOKIjSOfPHNqmfcfsTpzrj8 +330cdPklrMIuiBv+iGklCjkPZJiEhxvY2k6ERM4HAxxuPCivrH5MCeMNYvBVUcLr +GROm7JRXRllI/XubwwoAaAb+y+dZtCZ9AnzHIb+nyKiJxWAjzjR+QPL6jHrVWBVA +WTc83YP5FvxUXMfY3sVv9tNSCV3cpYOW5+iXcQzLuczXnOLRYk7p9wkb0/hk9KuK +4BMA90eBhvFMCFgHJ1/xJg2nFmBHPo/xbcwPG/ma5T/McA8mAlECAwEAAQKCAYAB +m29nxPNjod5Wm4xydWQYbZj/J0qkcyru/i1qpqyDbGa1sRNcg5A/A/8BPuPcWxhR +/hvwVeD5XX2/i2cnQuv6D3DQP1cSNCxQPanwzknP2k7IVqUmG0RDErPWuoDIhCnR +ljp0NPQsnj0fLhEkcbgG0xwx7KceUDigGsiTbatIvvBHGhQzrmTpqlVVdtMWvGRt +HQEJYuMuIw6IwALHyy3CITv5wh/Bec5OhNoFF8iUZceR4ZkGWf8bYWIa25xlzH6K +4rhOOh1G2ObHHTjhZq4mGXTHY1MEkAxXKWlR3DJc0Lh5E1UETSI6WBHWRb08iwQ5 +AkLOPyMpt08xHFWbJqywvlxenpri+gjY3xbXqGNhyDYWHZqlQmJVnzxoUOuuHi2R +dO86IckUc4Thjbm7a6AilL9t8juNZvyeQUVgtVi25uPkm/cK6r5vo8y4UOUU41EN +NOathlF69gh93il4t6zJW9jPV2WENv1H/vhKUWKW6cabX3vy4rANwy3q4V//GDEC +gcEAuniGCHaEdSjV2sUHyt/yrCLbU6+eLTfNk6AQyXJk6Wrvj6g3gx90ewEq5i/C +ukmSKDslr9pupq8Z/UNfYHZfJfpwEsYvIZ8DdFSd62/h66DhIoEn1v3Lwt+aexgX +yGJHF0BG9JA2CU5Z5NGjlnQYqQBobO9uZMq62l15Ig1MAMHGL0ZYVvOqGZD7XvtC +4UnclK5kjp51Vd5rydEQxyi5qkyLl9Q6T0FQXOphGIOd8ifYeUGe7YC72cFPevdx +wDXZAoHBANdDVvCMrjmzdrS6td39/2nHTeerFPbsOe2LIQYzqjeEe6GWqd2NL9NZ +bk3/cAuVgbWtdvSQQhhmSqOC7JZic4hbZb3lK6v/sr4F/Zu0CfAu80swWFMeS7vq +eQeYzN4w4dKpJArvU3ll7N9AlZhdlYkbPf0WdeOIjZawdAOxNtNe0O+j+5MsXR59 +qkULatumhcKUnqxFCiVHzy21CVJtRzrtu6oGoSdFbmG82eSJ1rPXiuuDnCyzjyMW +iClYRM4NOQKBwERnO/vUxihYT4LOLlqcpl/A9aYQUT0TMGWMHTxYq2343WJceeiu +3ELXHc6NDKjbnjMF54BH57lbmHQQh+dR5PuAkCZC7z0tIM5G0Bty0nRmcs/+gwfZ +2Cpnbjrjjq3iZ2O/H4hNcpUdWdqXkKP7eKReUvBLMLrmp369NVdpe0z3yGTFMFjN +T8PLLHsePt14A+PCyX6L4E0cp3vEJpx4cwtmwvpyTuWN9xXuoKmmdoVDWqS4jr1f +MQnjYO2h4ed5mQKBwGVttWli4DUP+r7tuwP+ynptDqg6VIaEiEcFZ2okre+63QYm +l6NtAzvyx6a41XKf355bPdG+p2YXzNN+vTue6BE3/5iagxloQjCHYhgbnRMvDDRB +c1y2ybihoqWRufZ30fARAoqkehCZliMbq2E/t1YDIBJAowuzLAP04LVcqxitdIV2 +HvQZ00aqr7AY0SDuNdiZbqp9XWpzi4td4iaUlxuNKP/UX9rBPGGROpoU2LWkujB+ +svfdI3TFCSNyE/mDAQKBwQCP++WZKxExrSFRk3W+TcHKHZb2pusfoPWE7WH6EnDW +dkTZpa3PZaf0xgeglmNBv4Paxw2eMPsIhyNv62XY/6GbY6VJWRyx/s+NsazeP4ji +xUOufnwTePjYw6x0pcl6BknZrHn8LCJU741h0yTum8cDdNfRKdc0AMy0gVXk4ZTG +2cAtbEcWb3J+a5kYf6mp5yx3BNwtewkGZhc2VuQ9mQNbMmOOS/pHQQTRWcxsQwyt +GPAhMKawjrL1KFmu7vIqDSw= -----END PRIVATE KEY----- Certificate: Data: @@ -51,38 +51,38 @@ Certificate: Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=allsans Subject Public Key Info: Public Key Algorithm: rsaEncryption - RSA Public-Key: (3072 bit) + Public-Key: (3072 bit) Modulus: - 00:c1:1a:f8:fe:53:2f:d5:53:24:d1:9a:62:00:d5: - 41:04:35:38:61:d3:ea:56:38:37:2f:bd:49:64:78: - 8a:3e:5d:32:77:16:11:78:fd:15:63:22:60:c7:8e: - 8c:e5:db:c5:df:d9:3c:ca:9e:f4:09:6b:52:82:a6: - a6:bb:7e:bf:1c:2f:d4:0c:c2:4e:29:1f:e4:b8:ba: - 5c:4e:bb:4d:81:97:76:1a:7f:1d:a8:25:55:0c:2f: - 7e:ef:72:22:60:fb:39:33:3f:1d:64:de:d5:c5:8d: - 79:2b:2c:68:d9:c0:ea:2e:7c:10:b7:02:63:ee:ab: - 6c:47:14:1c:c7:ae:fa:79:fc:32:11:1f:6b:25:40: - 53:3d:7e:95:59:cc:de:fb:81:8c:b3:c5:b6:b4:c0: - 27:c2:3e:90:9f:78:91:51:c9:82:96:f1:ce:cc:5e: - bc:27:33:cd:98:b4:4e:d1:96:77:f6:db:b1:24:09: - d3:0d:69:27:99:2d:42:31:79:5a:5c:9d:27:2a:66: - 5d:12:21:b4:77:60:48:95:d0:b3:c5:93:1d:30:24: - 59:bc:a9:41:05:53:f8:7e:d2:36:a6:83:2f:ce:37: - ed:75:9e:a9:8c:96:9d:c1:8c:d8:bf:25:35:6c:6b: - b3:7b:03:77:6f:74:70:bb:55:59:6b:5a:75:20:53: - a3:28:4a:78:b2:2f:a8:a3:a6:e7:32:1e:d6:73:2b: - 69:89:cb:4b:07:47:c3:da:74:72:a8:c3:43:b8:db: - 7f:f9:37:c1:8a:4d:23:af:68:63:17:4e:30:1e:38: - 6b:3e:f7:f3:f5:65:8a:37:22:38:d0:3f:3f:cd:57: - 74:25:84:af:33:46:ac:45:dd:c5:b4:7a:41:c7:91: - 3f:bf:8d:98:c2:bd:22:a6:ea:67:5b:31:0b:a7:28: - 4d:56:f9:da:24:01:cf:35:e6:96:f8:f0:cc:df:d5: - e5:8a:77:fe:d4:c9:47:fb:09:7b:ac:b3:20:1a:27: - 77:25:a5:a2:b5:b1:b6:e7:f6:6d + 00:9c:cc:45:6f:e4:3d:94:0e:d9:fa:0c:c9:99:fe: + e0:96:08:bc:8b:f8:ae:71:4d:9a:7f:e6:3d:dc:c8: + 84:a8:69:3b:94:d4:ab:b7:90:3f:7b:78:b8:29:1e: + 3e:05:82:4e:59:26:2d:b5:ce:c4:2a:7a:90:a3:8c: + 08:41:93:ab:63:e9:16:14:72:73:1b:6f:c3:2e:c5: + 49:10:30:40:d6:fb:92:df:4b:2a:7e:1c:26:9e:b6: + 51:e8:b1:67:61:10:3a:97:e2:4a:49:e5:a8:c1:c0: + d4:6d:d3:b9:b1:40:4c:6d:0b:89:1c:9e:8d:2e:b7: + da:c6:e5:7c:cb:37:6f:57:a4:2a:51:1e:51:45:ae: + 7b:4e:81:00:04:a2:2c:fd:fd:f7:3c:a9:a4:e0:6f: + 7e:d1:80:66:0f:42:cc:61:ec:f7:11:0d:17:b8:64: + 6c:75:97:4e:88:ae:bf:18:fb:65:b9:d3:35:82:7e: + 4c:02:4b:70:da:2c:33:8a:22:34:8e:7c:f1:cd:aa: + 67:dc:7e:c4:e9:ce:b8:fc:df:7d:1c:74:f9:25:ac: + c2:2e:88:1b:fe:88:69:25:0a:39:0f:64:98:84:87: + 1b:d8:da:4e:84:44:ce:07:03:1c:6e:3c:28:af:ac: + 7e:4c:09:e3:0d:62:f0:55:51:c2:eb:19:13:a6:ec: + 94:57:46:59:48:fd:7b:9b:c3:0a:00:68:06:fe:cb: + e7:59:b4:26:7d:02:7c:c7:21:bf:a7:c8:a8:89:c5: + 60:23:ce:34:7e:40:f2:fa:8c:7a:d5:58:15:40:59: + 37:3c:dd:83:f9:16:fc:54:5c:c7:d8:de:c5:6f:f6: + d3:52:09:5d:dc:a5:83:96:e7:e8:97:71:0c:cb:b9: + cc:d7:9c:e2:d1:62:4e:e9:f7:09:1b:d3:f8:64:f4: + ab:8a:e0:13:00:f7:47:81:86:f1:4c:08:58:07:27: + 5f:f1:26:0d:a7:16:60:47:3e:8f:f1:6d:cc:0f:1b: + f9:9a:e5:3f:cc:70:0f:26:02:51 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Alternative Name: - DNS:allsans, othername:, othername:, email:user@example.org, DNS:www.example.org, DirName:/C=XY/L=Castle Anthrax/O=Python Software Foundation/CN=dirname example, URI:https://www.python.org/, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1, Registered ID:1.2.3.4.5 + DNS:allsans, othername: 1.2.3.4::some other identifier, othername: 1.3.6.1.5.2.2::, email:user@example.org, DNS:www.example.org, DirName:/C=XY/L=Castle Anthrax/O=Python Software Foundation/CN=dirname example, URI:https://www.python.org/, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1, Registered ID:1.2.3.4.5 X509v3 Key Usage: critical Digital Signature, Key Encipherment X509v3 Extended Key Usage: @@ -90,59 +90,56 @@ Certificate: X509v3 Basic Constraints: critical CA:FALSE X509v3 Subject Key Identifier: - D4:F1:D8:23:E0:A7:E9:CA:12:45:A0:0D:03:C2:25:A6:E8:65:BC:EE + 31:5E:C0:5E:2F:47:FF:8B:92:F9:EE:3D:B1:87:D0:53:75:3B:B1:48 X509v3 Authority Key Identifier: - keyid:B3:8A:A0:A2:BA:71:F1:A8:24:79:D4:A4:5B:25:36:15:1E:49:C8:CD + keyid:F3:EC:94:8E:F2:8E:30:C4:8E:68:C2:BF:8E:6A:19:C0:C1:9F:76:65 DirName:/C=XY/O=Python Software Foundation CA/CN=our-ca-server serial:CB:2D:80:99:5A:69:52:5B - Authority Information Access: CA Issuers - URI:http://testca.pythontest.net/testca/pycacert.cer OCSP - URI:http://testca.pythontest.net/testca/ocsp/ - X509v3 CRL Distribution Points: - Full Name: URI:http://testca.pythontest.net/testca/revocation.crl - Signature Algorithm: sha256WithRSAEncryption - 70:77:d8:82:b0:f4:ab:de:84:ce:88:32:63:5e:23:0f:b6:58: - a2:b1:65:ff:12:22:0b:88:a6:fa:06:40:9a:e7:63:a7:5d:ae: - 94:c5:68:3c:4b:e9:95:34:01:75:24:df:9d:6e:9b:e4:ff:3f: - 61:97:29:7b:ab:34:2c:14:d3:01:d2:eb:fb:84:40:db:12:54: - 7e:7a:44:bc:08:eb:9f:e2:15:0b:11:4f:25:d2:56:51:95:ad: - 6d:ad:07:aa:6a:61:f9:39:d5:82:8c:45:31:9f:2a:ff:18:98: - 49:0c:bb:17:ad:d5:24:d3:d1:c7:c4:10:3e:c4:79:26:58:f4: - c5:de:82:16:c4:c3:c4:a7:a3:62:22:41:90:36:0f:bc:4c:fd: - 6a:18:22:f2:87:e9:07:db:b4:3d:65:00:e4:70:f9:d6:e5:a8: - a1:b9:c9:9d:e7:5d:78:aa:98:d5:f8:f4:fd:5c:d9:4c:d0:6d: - bf:87:71:d3:5b:ec:f4:bf:46:f9:c8:f8:10:c5:72:af:c3:15: - b9:c4:06:67:0b:3f:f6:f4:64:c5:27:74:c1:6b:00:37:da:ea: - 18:36:77:36:a7:3e:80:2e:5d:54:0f:01:df:ce:9e:97:dd:c9: - f2:8b:59:82:c5:65:31:c8:73:20:fd:24:23:25:d8:00:df:90: - 93:26:76:08:0a:06:a9:0e:d3:d3:4c:6f:ef:a7:fb:de:eb:2a: - 40:b9:e4:b1:44:0c:37:ca:c6:9e:44:4a:b4:7c:2c:40:52:35: - bb:b3:71:28:3d:35:fd:be:c9:4f:54:b3:99:c5:5f:84:38:fb: - 2b:fb:ea:dd:88:e8:9d:c1:9b:67:87:3d:79:7b:3d:7e:61:1f: - 70:3c:b7:c8:4c:17:a5:0c:a3:28:c7:ab:48:11:14:f7:98:7a: - da:4e:fb:91:76:89:0a:a6:c6:72:e0:96:d9:f1:80:ea:68:90: - 37:5c:c6:69:c7:d7:bc:c7:d1:ae:5b:a9:12:59:c6:e4:6c:61: - a9:8b:ba:51:b3:13 + Signature Value: + 72:42:a6:fc:ee:3c:21:47:05:33:e8:8c:6b:27:07:4a:ed:e2: + 81:47:96:79:43:ff:0f:ef:5a:06:aa:4c:01:70:5b:21:c4:b7: + 5d:17:29:c8:10:02:c3:08:7b:8c:86:56:9e:e9:7c:6e:a8:b6: + 26:13:9e:1e:1f:93:66:85:67:63:9e:08:fb:55:39:56:82:f5: + be:0c:38:1e:eb:c4:54:b2:a7:7b:18:55:bb:00:87:43:50:50: + bb:e1:29:10:cf:3d:c9:07:c7:d2:5d:b6:45:68:1f:d6:de:00: + 96:3e:29:73:f6:22:70:21:a2:ba:68:28:94:ec:37:bc:a7:00: + 70:58:4e:d1:48:ae:ef:8d:11:a4:6e:10:2f:92:83:07:e2:76: + ac:bf:4f:bb:d6:9f:47:9e:a4:02:03:16:f8:a8:0a:3d:67:17: + 31:44:0e:68:d0:d3:24:d5:e7:bf:67:30:8f:88:97:92:0a:1e: + d7:74:df:7e:7b:4c:c6:d9:c3:84:92:2b:a0:89:11:08:4c:dd: + 32:49:df:36:23:d4:63:56:e4:f1:68:5a:6f:a0:c3:3c:e2:36: + ee:f3:46:60:78:4d:76:a5:5a:4a:61:c6:f8:ae:18:68:c2:8d: + 0e:2f:76:50:bb:be:b9:56:f1:04:5c:ac:ad:d7:d6:a4:1e:45: + 45:52:f4:10:a2:0f:9b:e3:d9:73:17:b6:52:42:a6:5b:c9:e9: + 8d:60:74:68:d0:1f:7a:ce:01:8e:9e:55:cb:cf:64:c1:cc:9a: + 72:aa:b4:5f:b5:55:13:41:10:51:a0:2c:a5:5b:43:12:ca:cc: + b7:c4:ac:f2:6f:72:fd:0d:50:6a:d6:81:c1:91:93:21:fe:de: + 9a:be:e5:3c:2a:98:95:a1:42:f8:f2:5c:75:c6:f1:fd:11:b1: + 22:26:33:5b:43:63:21:06:61:d2:cd:04:f3:30:c6:a8:3f:17: + d3:05:a3:87:45:2e:52:1e:51:88:e3:59:4c:78:51:b0:7b:b4: + 58:d9:27:22:6e:8c -----BEGIN CERTIFICATE----- MIIHDTCCBXWgAwIBAgIJAMstgJlaaVJfMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx NDIzMTZaMF0xCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xEDAOBgNVBAMMB2Fs -bHNhbnMwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDBGvj+Uy/VUyTR -mmIA1UEENThh0+pWODcvvUlkeIo+XTJ3FhF4/RVjImDHjozl28Xf2TzKnvQJa1KC -pqa7fr8cL9QMwk4pH+S4ulxOu02Bl3Yafx2oJVUML37vciJg+zkzPx1k3tXFjXkr -LGjZwOoufBC3AmPuq2xHFBzHrvp5/DIRH2slQFM9fpVZzN77gYyzxba0wCfCPpCf -eJFRyYKW8c7MXrwnM82YtE7Rlnf227EkCdMNaSeZLUIxeVpcnScqZl0SIbR3YEiV -0LPFkx0wJFm8qUEFU/h+0jamgy/ON+11nqmMlp3BjNi/JTVsa7N7A3dvdHC7VVlr -WnUgU6MoSniyL6ijpucyHtZzK2mJy0sHR8PadHKow0O423/5N8GKTSOvaGMXTjAe -OGs+9/P1ZYo3IjjQPz/NV3QlhK8zRqxF3cW0ekHHkT+/jZjCvSKm6mdbMQunKE1W -+dokAc815pb48Mzf1eWKd/7UyUf7CXussyAaJ3clpaK1sbbn9m0CAwEAAaOCAt4w +bHNhbnMwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCczEVv5D2UDtn6 +DMmZ/uCWCLyL+K5xTZp/5j3cyISoaTuU1Ku3kD97eLgpHj4Fgk5ZJi21zsQqepCj +jAhBk6tj6RYUcnMbb8MuxUkQMEDW+5LfSyp+HCaetlHosWdhEDqX4kpJ5ajBwNRt +07mxQExtC4kcno0ut9rG5XzLN29XpCpRHlFFrntOgQAEoiz9/fc8qaTgb37RgGYP +Qsxh7PcRDRe4ZGx1l06Irr8Y+2W50zWCfkwCS3DaLDOKIjSOfPHNqmfcfsTpzrj8 +330cdPklrMIuiBv+iGklCjkPZJiEhxvY2k6ERM4HAxxuPCivrH5MCeMNYvBVUcLr +GROm7JRXRllI/XubwwoAaAb+y+dZtCZ9AnzHIb+nyKiJxWAjzjR+QPL6jHrVWBVA +WTc83YP5FvxUXMfY3sVv9tNSCV3cpYOW5+iXcQzLuczXnOLRYk7p9wkb0/hk9KuK +4BMA90eBhvFMCFgHJ1/xJg2nFmBHPo/xbcwPG/ma5T/McA8mAlECAwEAAaOCAt4w ggLaMIIBMAYDVR0RBIIBJzCCASOCB2FsbHNhbnOgHgYDKgMEoBcMFXNvbWUgb3Ro ZXIgaWRlbnRpZmllcqA1BgYrBgEFAgKgKzApoBAbDktFUkJFUk9TLlJFQUxNoRUw E6ADAgEBoQwwChsIdXNlcm5hbWWBEHVzZXJAZXhhbXBsZS5vcmeCD3d3dy5leGFt @@ -150,21 +147,21 @@ cGxlLm9yZ6RnMGUxCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJh eDEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xGDAWBgNVBAMM D2Rpcm5hbWUgZXhhbXBsZYYXaHR0cHM6Ly93d3cucHl0aG9uLm9yZy+HBH8AAAGH EAAAAAAAAAAAAAAAAAAAAAGIBCoDBAUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQW -MBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTU -8dgj4KfpyhJFoA0DwiWm6GW87jB9BgNVHSMEdjB0gBSziqCiunHxqCR51KRbJTYV -HknIzaFRpE8wTTELMAkGA1UEBhMCWFkxJjAkBgNVBAoMHVB5dGhvbiBTb2Z0d2Fy +MBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQx +XsBeL0f/i5L57j2xh9BTdTuxSDB9BgNVHSMEdjB0gBTz7JSO8o4wxI5owr+OahnA +wZ92ZaFRpE8wTTELMAkGA1UEBhMCWFkxJjAkBgNVBAoMHVB5dGhvbiBTb2Z0d2Fy ZSBGb3VuZGF0aW9uIENBMRYwFAYDVQQDDA1vdXItY2Etc2VydmVyggkAyy2AmVpp UlswgYMGCCsGAQUFBwEBBHcwdTA8BggrBgEFBQcwAoYwaHR0cDovL3Rlc3RjYS5w eXRob250ZXN0Lm5ldC90ZXN0Y2EvcHljYWNlcnQuY2VyMDUGCCsGAQUFBzABhilo dHRwOi8vdGVzdGNhLnB5dGhvbnRlc3QubmV0L3Rlc3RjYS9vY3NwLzBDBgNVHR8E PDA6MDigNqA0hjJodHRwOi8vdGVzdGNhLnB5dGhvbnRlc3QubmV0L3Rlc3RjYS9y -ZXZvY2F0aW9uLmNybDANBgkqhkiG9w0BAQsFAAOCAYEAcHfYgrD0q96EzogyY14j -D7ZYorFl/xIiC4im+gZAmudjp12ulMVoPEvplTQBdSTfnW6b5P8/YZcpe6s0LBTT -AdLr+4RA2xJUfnpEvAjrn+IVCxFPJdJWUZWtba0Hqmph+TnVgoxFMZ8q/xiYSQy7 -F63VJNPRx8QQPsR5Jlj0xd6CFsTDxKejYiJBkDYPvEz9ahgi8ofpB9u0PWUA5HD5 -1uWoobnJneddeKqY1fj0/VzZTNBtv4dx01vs9L9G+cj4EMVyr8MVucQGZws/9vRk -xSd0wWsAN9rqGDZ3Nqc+gC5dVA8B386el93J8otZgsVlMchzIP0kIyXYAN+QkyZ2 -CAoGqQ7T00xv76f73usqQLnksUQMN8rGnkRKtHwsQFI1u7NxKD01/b7JT1SzmcVf -hDj7K/vq3YjoncGbZ4c9eXs9fmEfcDy3yEwXpQyjKMerSBEU95h62k77kXaJCqbG -cuCW2fGA6miQN1zGacfXvMfRrlupElnG5GxhqYu6UbMT +ZXZvY2F0aW9uLmNybDANBgkqhkiG9w0BAQsFAAOCAYEAckKm/O48IUcFM+iMaycH +Su3igUeWeUP/D+9aBqpMAXBbIcS3XRcpyBACwwh7jIZWnul8bqi2JhOeHh+TZoVn +Y54I+1U5VoL1vgw4HuvEVLKnexhVuwCHQ1BQu+EpEM89yQfH0l22RWgf1t4Alj4p +c/YicCGiumgolOw3vKcAcFhO0Uiu740RpG4QL5KDB+J2rL9Pu9afR56kAgMW+KgK +PWcXMUQOaNDTJNXnv2cwj4iXkgoe13TffntMxtnDhJIroIkRCEzdMknfNiPUY1bk +8Whab6DDPOI27vNGYHhNdqVaSmHG+K4YaMKNDi92ULu+uVbxBFysrdfWpB5FRVL0 +EKIPm+PZcxe2UkKmW8npjWB0aNAfes4Bjp5Vy89kwcyacqq0X7VVE0EQUaAspVtD +EsrMt8Ss8m9y/Q1QataBwZGTIf7emr7lPCqYlaFC+PJcdcbx/RGxIiYzW0NjIQZh +0s0E8zDGqD8X0wWjh0UuUh5RiONZTHhRsHu0WNknIm6M -----END CERTIFICATE----- diff --git a/Lib/test/certdata/capath/b1930218.0 b/Lib/test/certdata/capath/b1930218.0 index 941d7919f8033e1..aa9dbbe294f829d 100644 --- a/Lib/test/certdata/capath/b1930218.0 +++ b/Lib/test/certdata/capath/b1930218.0 @@ -1,26 +1,27 @@ -----BEGIN CERTIFICATE----- -MIIEbTCCAtWgAwIBAgIJAMstgJlaaVJbMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV +MIIEgDCCAuigAwIBAgIJAMstgJlaaVJbMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx NDIzMTZaME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUg Rm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZlcjCCAaIwDQYJKoZI -hvcNAQEBBQADggGPADCCAYoCggGBALGE009cBICRT4JJujAL9+jL+RTvPZ8LPwpi -/BsgpSDRYF+HWh8W0e2XcKbaGwMsfqBbPE4vFn4OiSmJ4RANONpqd183E7Moj3tc -dq2e6NP1nvWDqhAHjeZRmPB8DVLyDCEe2LmZJqklAye7XKsuMyei1iOog4dEKZ+X -tSRv17kK/Sjuu/tBWOodmd1EhquYvhzcy6mJHTZcqehHtfRSSKq1pGfvPtfi0zPe -mCnYerBZXOexDsz9n+v21ToOC8/+Cz2iv0UYzpTnqVVgiNTYhFB5BS5BA3SuZyb2 -WxIImM4Kl+0BD4lPF1z6Ph01JEeSMr/3pBgrPNBImeGizaPMUFMgtcbjZoV7VxDs -M0/Bd+cbfoHGxPNFIMCR3RN2ewOv9naOooNjV91jvLtaHBdSitYGSMwPx9NP6Noi -bIb5TlymKQc72FZMWbMgSQd7lITPK8McGk6HZJK6QuHmrX0d9lSQbyvps8xLKzMm -I/1lwDzwea3JwYHvNwTgJz6w7hW+UQIDAQABo1AwTjAdBgNVHQ4EFgQUs4qgorpx -8agkedSkWyU2FR5JyM0wHwYDVR0jBBgwFoAUs4qgorpx8agkedSkWyU2FR5JyM0w -DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAYEAazIv5wUY6lzJlfTgwgxB -XxoKlcnHfQXuilYpNVBAt/6fe1scw2kvoMvSuJEvUBli9ycYbZV7UxYVolrcFOP7 -sTKpadumM0c8ux/S3HD5ai4M2Ixt5V0dQzxOkd6gyNqgSw6dXrYPSknwe7ZTnv01 -FFvjTbQYpjZh6I8zm9QF+VRm3+DLGKNO3BeooLPBqPTWncp/aFMa15Xa6NOeSABx -lZkRB8+WwH3OfTDoT+GDFjOh/1mbPkznOjgBnw9nTP0ti0rUAUY3M+gTaxWpHWh2 -RaKCM2kmMGAFyI+9tHWrvnqLSGhwQLQbUcXmeq1rT9sXwGBnLmNhmyxImbh2RaCe -zO8zHlBOq3LDZciyebM1gyF404tsOhjoZTI5uMCdcS81NorAF2LYiz7hIhgrTGOm -Dp0K+qtbNfuIkXdMjYydqc/8q8LmWgV7fgRuOc+Tzmc7esuvtjbh+3FkRdSm8M7v -dQSZaZrliAoQAnSJ7HWERIBI38H36TfOzpKSXIkiCHMf +hvcNAQEBBQADggGPADCCAYoCggGBANCgm7G5O3nuMS+4URwBde0JWUysyL9qCvh6 +CPAl4yV7avjE2KqgYAclsM9zcQVSaL8Gk64QYZa8s2mBGn0Z/CCGj5poG+3N4mxh +Z8dOVepDBiEb6bm+hF/C2uuJiOBCpkVJKtC5a4yTyUQ7yvw8lH/dcMWt2Es73B74 +VUu1J4b437CDz/cWN78TFzTUyVXtaxbJf60gTvAe2Ru/jbrNypbvHmnLUWZhSA3o +eaNZYdQQjeANOwuFttWFEt2lB8VL+iP6VDn3lwvJREceVnc8PBMBC2131hS6RPRT +NVbZPbk+NV/bM5pPWrk4RMkySf5m9h8al6rKTEr2uF5Af/sLHfhbodz4wC7QbUn1 +0kbUkFf+koE0ri04u6gXDOHlP+L3JgVUUPVksxxuRP9vqbQDlukOwojYclKQmcZB +D0aQWbg+b9Linh02gpXTWIoS8+LYDSBRI/CQLZo+fSaGsqfX+ShgA+N3x4gEyf6J +d3AQT8Ogijv0q0J74xSS2K4W1qHefQIDAQABo2MwYTAdBgNVHQ4EFgQU8+yUjvKO +MMSOaMK/jmoZwMGfdmUwHwYDVR0jBBgwFoAU8+yUjvKOMMSOaMK/jmoZwMGfdmUw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggGBAIsAVHKzjevzrzSf1mDq3oQ/jASPGaa+AmfEY8V040c3WYOUBvFFGegHL9ZO +S0+oPccHByeS9H5zT4syGZRGeiXE2cQnsBFjOmCLheFzTzQ7a6Q0jEmOzc9PsmUn +QRmw/IAxePJzapt9cTRQ/Hio2gW0nFs6mXprXe870+k7MwESZc9eB9gZr9VT6vAQ +rMS2Jjw0LnTuZN0dNnWJRACwDf0vswHMGosCzWzogILKv4LXAJ3YNhXSBzf8bHMd +2qgc6CCOMnr+bScW5Fhs6z7w/iRSKXG4lntTS0UgVUBehhvsyUaRku6sk2WRLpS2 +tqzoozSJpBoSDU1EpVLti5HuL6avpJUl+c7HW6cA05PKtDxdTfexPMxttEW+gu0Y +kMiG0XVRUARM6E/S1lCqdede/6F7Jxkca0ksbE1rY8w7cwDzmSbQgofTqTactD25 +SGiokvAnjgzNFXZChIDJP6N+tN3X+Kx2umCXPFofTt5x7gk5EN0x1WhXXRrlQroO +aOZF0w== -----END CERTIFICATE----- diff --git a/Lib/test/certdata/capath/ceff1710.0 b/Lib/test/certdata/capath/ceff1710.0 index 941d7919f8033e1..aa9dbbe294f829d 100644 --- a/Lib/test/certdata/capath/ceff1710.0 +++ b/Lib/test/certdata/capath/ceff1710.0 @@ -1,26 +1,27 @@ -----BEGIN CERTIFICATE----- -MIIEbTCCAtWgAwIBAgIJAMstgJlaaVJbMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV +MIIEgDCCAuigAwIBAgIJAMstgJlaaVJbMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx NDIzMTZaME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUg Rm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZlcjCCAaIwDQYJKoZI -hvcNAQEBBQADggGPADCCAYoCggGBALGE009cBICRT4JJujAL9+jL+RTvPZ8LPwpi -/BsgpSDRYF+HWh8W0e2XcKbaGwMsfqBbPE4vFn4OiSmJ4RANONpqd183E7Moj3tc -dq2e6NP1nvWDqhAHjeZRmPB8DVLyDCEe2LmZJqklAye7XKsuMyei1iOog4dEKZ+X -tSRv17kK/Sjuu/tBWOodmd1EhquYvhzcy6mJHTZcqehHtfRSSKq1pGfvPtfi0zPe -mCnYerBZXOexDsz9n+v21ToOC8/+Cz2iv0UYzpTnqVVgiNTYhFB5BS5BA3SuZyb2 -WxIImM4Kl+0BD4lPF1z6Ph01JEeSMr/3pBgrPNBImeGizaPMUFMgtcbjZoV7VxDs -M0/Bd+cbfoHGxPNFIMCR3RN2ewOv9naOooNjV91jvLtaHBdSitYGSMwPx9NP6Noi -bIb5TlymKQc72FZMWbMgSQd7lITPK8McGk6HZJK6QuHmrX0d9lSQbyvps8xLKzMm -I/1lwDzwea3JwYHvNwTgJz6w7hW+UQIDAQABo1AwTjAdBgNVHQ4EFgQUs4qgorpx -8agkedSkWyU2FR5JyM0wHwYDVR0jBBgwFoAUs4qgorpx8agkedSkWyU2FR5JyM0w -DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAYEAazIv5wUY6lzJlfTgwgxB -XxoKlcnHfQXuilYpNVBAt/6fe1scw2kvoMvSuJEvUBli9ycYbZV7UxYVolrcFOP7 -sTKpadumM0c8ux/S3HD5ai4M2Ixt5V0dQzxOkd6gyNqgSw6dXrYPSknwe7ZTnv01 -FFvjTbQYpjZh6I8zm9QF+VRm3+DLGKNO3BeooLPBqPTWncp/aFMa15Xa6NOeSABx -lZkRB8+WwH3OfTDoT+GDFjOh/1mbPkznOjgBnw9nTP0ti0rUAUY3M+gTaxWpHWh2 -RaKCM2kmMGAFyI+9tHWrvnqLSGhwQLQbUcXmeq1rT9sXwGBnLmNhmyxImbh2RaCe -zO8zHlBOq3LDZciyebM1gyF404tsOhjoZTI5uMCdcS81NorAF2LYiz7hIhgrTGOm -Dp0K+qtbNfuIkXdMjYydqc/8q8LmWgV7fgRuOc+Tzmc7esuvtjbh+3FkRdSm8M7v -dQSZaZrliAoQAnSJ7HWERIBI38H36TfOzpKSXIkiCHMf +hvcNAQEBBQADggGPADCCAYoCggGBANCgm7G5O3nuMS+4URwBde0JWUysyL9qCvh6 +CPAl4yV7avjE2KqgYAclsM9zcQVSaL8Gk64QYZa8s2mBGn0Z/CCGj5poG+3N4mxh +Z8dOVepDBiEb6bm+hF/C2uuJiOBCpkVJKtC5a4yTyUQ7yvw8lH/dcMWt2Es73B74 +VUu1J4b437CDz/cWN78TFzTUyVXtaxbJf60gTvAe2Ru/jbrNypbvHmnLUWZhSA3o +eaNZYdQQjeANOwuFttWFEt2lB8VL+iP6VDn3lwvJREceVnc8PBMBC2131hS6RPRT +NVbZPbk+NV/bM5pPWrk4RMkySf5m9h8al6rKTEr2uF5Af/sLHfhbodz4wC7QbUn1 +0kbUkFf+koE0ri04u6gXDOHlP+L3JgVUUPVksxxuRP9vqbQDlukOwojYclKQmcZB +D0aQWbg+b9Linh02gpXTWIoS8+LYDSBRI/CQLZo+fSaGsqfX+ShgA+N3x4gEyf6J +d3AQT8Ogijv0q0J74xSS2K4W1qHefQIDAQABo2MwYTAdBgNVHQ4EFgQU8+yUjvKO +MMSOaMK/jmoZwMGfdmUwHwYDVR0jBBgwFoAU8+yUjvKOMMSOaMK/jmoZwMGfdmUw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggGBAIsAVHKzjevzrzSf1mDq3oQ/jASPGaa+AmfEY8V040c3WYOUBvFFGegHL9ZO +S0+oPccHByeS9H5zT4syGZRGeiXE2cQnsBFjOmCLheFzTzQ7a6Q0jEmOzc9PsmUn +QRmw/IAxePJzapt9cTRQ/Hio2gW0nFs6mXprXe870+k7MwESZc9eB9gZr9VT6vAQ +rMS2Jjw0LnTuZN0dNnWJRACwDf0vswHMGosCzWzogILKv4LXAJ3YNhXSBzf8bHMd +2qgc6CCOMnr+bScW5Fhs6z7w/iRSKXG4lntTS0UgVUBehhvsyUaRku6sk2WRLpS2 +tqzoozSJpBoSDU1EpVLti5HuL6avpJUl+c7HW6cA05PKtDxdTfexPMxttEW+gu0Y +kMiG0XVRUARM6E/S1lCqdede/6F7Jxkca0ksbE1rY8w7cwDzmSbQgofTqTactD25 +SGiokvAnjgzNFXZChIDJP6N+tN3X+Kx2umCXPFofTt5x7gk5EN0x1WhXXRrlQroO +aOZF0w== -----END CERTIFICATE----- diff --git a/Lib/test/certdata/idnsans.pem b/Lib/test/certdata/idnsans.pem index cbcac7818ddc67a..07a42422af1fd34 100644 --- a/Lib/test/certdata/idnsans.pem +++ b/Lib/test/certdata/idnsans.pem @@ -1,42 +1,42 @@ -----BEGIN PRIVATE KEY----- -MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQC8sqplTuHuLjbW -TL5SL2D1fw9U6WQzLVAF5gsyhd5lr2FpfYwjrob5Mav91aOLbJRTvoNyXsJ26FPS -0RycRGXbomcIEJxXGy9aI+0MLYBt1G5mgqCH+HcVCwPzCNlhVnTwvpgA7y8zs3+6 -ezZAPWkF0yWOMYLtTcq9A5GWeavt5VMgm1KZF3gO4k58oPyk3Ae9D0LAaYsX6DFi -BYx41eUR5UbSb5IYXaDd8d6jqW/jnYhgc6Cxkv1gTJFn87V5lrG0vYMSRUtWDQ9Y -Jh/EKAxjGw7AeY429p6TE4UoJhDmoFYR2NLvawhNIplxol/v0fs0veFQjI/UsTD8 -2tRfnYL4IX8szhLsE5/5Iq8aiLHjVbIMwmDYAa0P63Ap2kf1biSn9mpDL8lQazSo -yr8xzIq2QS5HMvGbeMAmS0ih10Zx84uVmkWlavgvtSflw8K/ZXT9c70rZp/TdBGY -95cOFsbg5U/20M/Llpis9tcBCaoVaYSFupatrP+p8y19qP2nebsCAwEAAQKCAYEA -uaYWWwHW6pzxOrnabcVLYX0WunW9LVShbIw97AElI2n/LuhkXh6xkK48BsqP0vaK -oDHJ5VYxgQdmoP03Zs8sX4BSWe7twg1u8wJxkA+cUXI1BAn0opHjpwJlalEEfe2v -s8PwjMrF59nsCq56W42PrDlms5UmuQ5WLsw6Co++hZmfxW7LPu+GIS6qBZfluNT5 -kBpZlDDCtkyteUD4SVI3wvmOSi+Wzv4e7P2wC9kByjENIcfhC5QQURRD4sA1hWCp -2SThYWqJOCEc2SvGgoqgTRaJuQ2aVG9qrntXt0N4V+WdJWXBK0jedkB2flLve1fR -KmDYuc9k/c1svmS3Y+iZohBha9H8jpuJmXYBxxg1iNg9m7qkfg8F8wxCYLQKB+U6 -tjRS7by+jSE08On7mpDDhJORnlh+rfEuWPPwAKQpLpdp76KDTvR++GvfOMUiOrFM -e9s5aXp+vcgkSSqYvigE+sFpCjQWwkGBkMdT16Pf9CzhQaM08YuLnzfLEYgLFw6R -AoHBAN5NQINBmlq/cptGSru66kfecqHfI7xHnnGWKAkto/B1x7Crrgs4Tk5b4vaA -JmAqatt5P1e7zco7uAXXebY5VURuH/30TlkuaB+oGFp0OMw6165n8RVPT2ZaDViK -ssJ9LT8fJ+23TWCCT2Z1zUlM/NnHAMjKOVsJK3/KEkVvlc7ROC7uVooc78AsQehg -zpL3GBYEeBukT8aNUMqUlesCsIs/dQHW7DzQL2xGkQagm5/PDsxaCsT7ynA8eL3X -TW+IXwKBwQDZTV3TaG6wqtL8y2DR0lN5jY/eYayX4e18iZ+XEZVTntPdVVyJIE4d -0A5ZfcILb9WE8R21iptROYSjcH/05j+3fQMJ1WAK0sNfGTUNNT3jYU8YzLvos+wW -G8E+mNMpFPWNvLV5Qrl4VvoifGh8AMvplUEz8uAzGJbXbRxUPcmjth2ph8zULEDn -/+o4OcT3gh1bp+HCqch0OuiJRn9qNUpsJG5GMm5FtjBjZM97ucZ1/0DaWl3JUxUN -/pueo3J9vCUCgcBg2Fjdlcvv8u2z1aijJmgATVm1SWfhE3ZkV50zem2sSTNotTJK -cwoyOveimeueA3ywBp9g0lFx5Bhkex3sFAggmrVXRoKHeZ8lA28woOdJmezybxfp -R7b4iQy9YRdFgZEfqawUdMHB5KNAqNt5LpANNBQUZX0dOt53eooBM/6Yri8CyxRq -cPbFysIfwWTdQ8Z7eRD2Qdv7TP9AcgDp9C8DSu7nkUEzsSKn0gpGT9vcgDEbN7Lv -ZB4qTT3wvoZeq5MCgcBIG18eDtJkN1sp3Yb0OTnP5QSvg3PVNngq0jQt2fzWMacW -FARP0HN7exW35n4kc2jD44q7OhJOAqsb3PHo3xqXlZkTg0WKceO4w9GR32/46spn -bVCRaFrX/z/BuM6hHD5bWRpS8aw/3YTFOsklFNKVYRyw01BIREmRlLhIz/QAKidv -oQt8AG9NTON44tqUUw3Q40WL5fEJeJ6/JrCTGrnmZrRdANEMuucVpFchNEVB1IC9 -tCzY6IPdD/atzojoZi0CgcB2x9oWLjJ0XJIp2pMAb8nCMVjkKrznKFjZbDm8EQBs -ou7pM2zkO3VRcWT1BXQocinJsjQqjQiTawP6IN2FQgT0d89V+pwd+jdvpdildQhP -1/6SErVRZV//oopKTsC6TIBL/EmW1TkP3ulQIZs8YklFgybeHdDyNFi+VgPXkVGe -IHp0nEzrui9q0YJsjHfFHBeGyzDSfbiBYiF7Auk66gYZbXufebP/LZNG/FIamPP3 -rwYIeeV1IVwk9tPBw6fGwrs= +MIIG/AIBADANBgkqhkiG9w0BAQEFAASCBuYwggbiAgEAAoIBgQCp6zt40WB3K7yj +BGugnRuqI3ApftThZWDIpvW0cVmN0nqQxsO6CCnS4dS7SYhGFiIqWjNVc2WG0gv7 +nC5DFguqbndNZk9/SjX8EOxKz4ANjd61WnTkDO5Tbiiyd+TuEBxhmbEF69bF9dtd +1Sgo8jmM7j+aa6ClYh/49bx+blJDF76EGSrmB1q+obMeZURhPXNBeoiqKR83x5Hc +LTJYMocvb6m8uABwuSka13Gb3QGu06p5ldK6TDK38HsoOy6MFO5F1PrkakG/eBHO +jcBOGPfNmTwWOqvwlcQWykr4QspWS+yTzdkgZ+mxar/yQuq7wuYSNaEfGH5yoYtV +WIgKwwZRDPqpSQuVe+J+MWLPQ6RTM+rXIHVzHtPk1f8DrgN+hSepJy/sVBBEQCzj +nyB+scn76ETWch3iyVoMj3oVOGs0b4XTDMmUw/DmEt5TDah7TqE3G+fpBIbgMSjx +MzUQZl27izmM9nQCJRAosNoNwXqlM754K9WcY6gT8kkcj1CfTmMCAwEAAQKCAYAz +9ZdHkDsf5fN2pAznXfOOOOz8+2hMjmwkn42GAp1gdWr+Z5GFiyaC8oTTSp6N1AnZ +iqCk8jcrHYMFi1JIOG8TzFjWBcGsinxsmp4vGDmvq2Ddcw5IiD2+rHJsdKZAOBP9 +snpD9cTE3zQYAu0XbE617krrxRqoSBO/1SExRjoIgzPCgFGyarBQl/DGjC/3Tku2 +y6oL4qxFqdTMD9QTzUuycUJlz5xu2+gaaaQ3hcMUe2xnZq28Qz3FKpf2ivZmZqWf +4+AIe0lRosmFoLAFjIyyuGCkWZ2t9KDIZV0OOS4+DvVOC/Um9r4VojeikripCGKY +2FzkkuQP3jz6pJ1UxCDg7YXZdR2IbcS18F1OYmLViU8oLDR6T01s0Npmp39dDazf +A4U+WyV3o1ydiSpwAiN8pJadmr5BHrCSmawV8ztW5yholufrO+FR5ze2+QG99txm +6l7lUI8Nz01lYG6D10MjaQ9INk2WSjBPVNbfsTl73/hR76/319ctfOINRMHilJ0C +gcEAvFgTdc5Qf9E7xEZE5UtpSTPvZ24DxQ7hmGW3pTs6+Nw4ccknnW0lLkWvY4Sf +gXl4TyTTUhe6PAN3UHzbjN2vdsTmAVUlnkNH40ymF8FgRGFNDuvuCZvf5ZWNddSl +6Vu/e5TFPDAqS8rGnl23DgXhlT+3Y0/mrftWoigdOxgximLAsmmqp/tANGi9jqE1 +3L0BN+xKqMMKSXkMtuzJHuini8MaWQgQcW/4czh4ArdesMzuVrstOD8947XHreY9 +pesVAoHBAOb0y/AFEdR+mhk/cfqjTzsNU2sS9yHlzMVgecF8BA26xcfAwg4d47VS ++LK8fV6KC4hHk4uQWjQzCG2PYXBbFT52xeJ3EC8DwWxJP09b4HV/1mWxXl5htjnr +dfyTmXKvEe5ZBpKGWc8i7s7jBi7R5EpgIfc586iNRyjYAk60dyG0iP13SurRvXBg +ID25VR4wABl3HQ3Hhv61dqC9FPrdHZQJdysfUqNrAFniWsSR2eyG5i4S1uHa3G+i +MzBTOuBRlwKBwBNXUBhG6YlWqTaMqMKLLfKwfKM4bvargost1uAG5xVrN/inWYQX +EzxfN5WWpvKa0Ln/5BuICD3ldTk0uS8MDNq7eYslfUl1S0qSMnQ6DXK4MzuXCsi9 +0w42f2JcRfVi0JUWP/LgV1eVKTRWF1g/Tl0PP/vY1q2DI/BfAjFxWJUHcxZfN4Es +kflP0Dd3YpqaZieiAkC2VrYY0i9uvXCJH7uAe5Is+9NKVk8uu1Q8FGM/iDIr4obm +J6rcnfbDsAz7yQKBwGtIbW9qO3UU9ioiQaTmtYg90XEclzXk1HEfNo+9NvjVuMfo +b3w1QDBbgXEtg6MlxuOgNBaRkIVM625ROzcA6GZir9tZ6Wede/z8LW+Ew0hxgLsu +YCLBiu9uxBj2y0HttwubySTJSfChToNGC/o1v7EY5M492kSCk/qSFMhQpkI+5Z+w +CVn44eHQlUl2zOY/79vka9eZxsiMrLVP/+3kRrgciYG7hByrOLeIIRfMlIl9xHDE +iZLSorEsjFC3aNMIswKBwFELC2fvlziW9rECQcGXnvc1DPmZcxm1ATFZ93FpKleF +TjLIWSdst0PmO8CSIuEZ2ZXPoK9CMJyQG+kt3k7IgZ1xKXg9y6ThwbznurXp1jaW +NjEnYtFMBK9Ur3oaAsrG2XwZ2PMvnI/Yp8tciGvjJlzSM8gHJ9BL8Yf+3gIJi/0D +KtaF9ha9J/SDDZdEiLIQ4LvSqYmlUgsCgiUvY3SVwCh8xDfBWD1hKw9vUiZu5cnJ +81hAHFgeD4f+C8fLols/sA== -----END PRIVATE KEY----- Certificate: Data: @@ -51,34 +51,34 @@ Certificate: Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=idnsans Subject Public Key Info: Public Key Algorithm: rsaEncryption - RSA Public-Key: (3072 bit) + Public-Key: (3072 bit) Modulus: - 00:bc:b2:aa:65:4e:e1:ee:2e:36:d6:4c:be:52:2f: - 60:f5:7f:0f:54:e9:64:33:2d:50:05:e6:0b:32:85: - de:65:af:61:69:7d:8c:23:ae:86:f9:31:ab:fd:d5: - a3:8b:6c:94:53:be:83:72:5e:c2:76:e8:53:d2:d1: - 1c:9c:44:65:db:a2:67:08:10:9c:57:1b:2f:5a:23: - ed:0c:2d:80:6d:d4:6e:66:82:a0:87:f8:77:15:0b: - 03:f3:08:d9:61:56:74:f0:be:98:00:ef:2f:33:b3: - 7f:ba:7b:36:40:3d:69:05:d3:25:8e:31:82:ed:4d: - ca:bd:03:91:96:79:ab:ed:e5:53:20:9b:52:99:17: - 78:0e:e2:4e:7c:a0:fc:a4:dc:07:bd:0f:42:c0:69: - 8b:17:e8:31:62:05:8c:78:d5:e5:11:e5:46:d2:6f: - 92:18:5d:a0:dd:f1:de:a3:a9:6f:e3:9d:88:60:73: - a0:b1:92:fd:60:4c:91:67:f3:b5:79:96:b1:b4:bd: - 83:12:45:4b:56:0d:0f:58:26:1f:c4:28:0c:63:1b: - 0e:c0:79:8e:36:f6:9e:93:13:85:28:26:10:e6:a0: - 56:11:d8:d2:ef:6b:08:4d:22:99:71:a2:5f:ef:d1: - fb:34:bd:e1:50:8c:8f:d4:b1:30:fc:da:d4:5f:9d: - 82:f8:21:7f:2c:ce:12:ec:13:9f:f9:22:af:1a:88: - b1:e3:55:b2:0c:c2:60:d8:01:ad:0f:eb:70:29:da: - 47:f5:6e:24:a7:f6:6a:43:2f:c9:50:6b:34:a8:ca: - bf:31:cc:8a:b6:41:2e:47:32:f1:9b:78:c0:26:4b: - 48:a1:d7:46:71:f3:8b:95:9a:45:a5:6a:f8:2f:b5: - 27:e5:c3:c2:bf:65:74:fd:73:bd:2b:66:9f:d3:74: - 11:98:f7:97:0e:16:c6:e0:e5:4f:f6:d0:cf:cb:96: - 98:ac:f6:d7:01:09:aa:15:69:84:85:ba:96:ad:ac: - ff:a9:f3:2d:7d:a8:fd:a7:79:bb + 00:a9:eb:3b:78:d1:60:77:2b:bc:a3:04:6b:a0:9d: + 1b:aa:23:70:29:7e:d4:e1:65:60:c8:a6:f5:b4:71: + 59:8d:d2:7a:90:c6:c3:ba:08:29:d2:e1:d4:bb:49: + 88:46:16:22:2a:5a:33:55:73:65:86:d2:0b:fb:9c: + 2e:43:16:0b:aa:6e:77:4d:66:4f:7f:4a:35:fc:10: + ec:4a:cf:80:0d:8d:de:b5:5a:74:e4:0c:ee:53:6e: + 28:b2:77:e4:ee:10:1c:61:99:b1:05:eb:d6:c5:f5: + db:5d:d5:28:28:f2:39:8c:ee:3f:9a:6b:a0:a5:62: + 1f:f8:f5:bc:7e:6e:52:43:17:be:84:19:2a:e6:07: + 5a:be:a1:b3:1e:65:44:61:3d:73:41:7a:88:aa:29: + 1f:37:c7:91:dc:2d:32:58:32:87:2f:6f:a9:bc:b8: + 00:70:b9:29:1a:d7:71:9b:dd:01:ae:d3:aa:79:95: + d2:ba:4c:32:b7:f0:7b:28:3b:2e:8c:14:ee:45:d4: + fa:e4:6a:41:bf:78:11:ce:8d:c0:4e:18:f7:cd:99: + 3c:16:3a:ab:f0:95:c4:16:ca:4a:f8:42:ca:56:4b: + ec:93:cd:d9:20:67:e9:b1:6a:bf:f2:42:ea:bb:c2: + e6:12:35:a1:1f:18:7e:72:a1:8b:55:58:88:0a:c3: + 06:51:0c:fa:a9:49:0b:95:7b:e2:7e:31:62:cf:43: + a4:53:33:ea:d7:20:75:73:1e:d3:e4:d5:ff:03:ae: + 03:7e:85:27:a9:27:2f:ec:54:10:44:40:2c:e3:9f: + 20:7e:b1:c9:fb:e8:44:d6:72:1d:e2:c9:5a:0c:8f: + 7a:15:38:6b:34:6f:85:d3:0c:c9:94:c3:f0:e6:12: + de:53:0d:a8:7b:4e:a1:37:1b:e7:e9:04:86:e0:31: + 28:f1:33:35:10:66:5d:bb:8b:39:8c:f6:74:02:25: + 10:28:b0:da:0d:c1:7a:a5:33:be:78:2b:d5:9c:63: + a8:13:f2:49:1c:8f:50:9f:4e:63 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Alternative Name: @@ -90,80 +90,77 @@ Certificate: X509v3 Basic Constraints: critical CA:FALSE X509v3 Subject Key Identifier: - 5C:BE:18:7F:7B:3F:CE:99:66:80:79:53:4B:DD:33:1B:42:A5:7E:00 + 5B:93:42:58:B0:B4:18:CC:41:4C:15:EB:42:33:66:77:4C:71:2F:42 X509v3 Authority Key Identifier: - keyid:B3:8A:A0:A2:BA:71:F1:A8:24:79:D4:A4:5B:25:36:15:1E:49:C8:CD + keyid:F3:EC:94:8E:F2:8E:30:C4:8E:68:C2:BF:8E:6A:19:C0:C1:9F:76:65 DirName:/C=XY/O=Python Software Foundation CA/CN=our-ca-server serial:CB:2D:80:99:5A:69:52:5B - Authority Information Access: CA Issuers - URI:http://testca.pythontest.net/testca/pycacert.cer OCSP - URI:http://testca.pythontest.net/testca/ocsp/ - X509v3 CRL Distribution Points: - Full Name: URI:http://testca.pythontest.net/testca/revocation.crl - Signature Algorithm: sha256WithRSAEncryption - 5d:7a:f8:81:e0:a7:c1:3f:39:eb:d3:52:2c:e1:cb:4d:29:b3: - 77:18:17:18:9e:12:fc:11:cc:3c:49:cb:6b:f4:4d:6c:b8:d2: - f4:e9:37:f8:6b:ed:f5:d7:f1:eb:5a:41:04:c7:f3:8c:da:e1: - 05:8e:ae:58:71:d9:01:8a:32:46:b2:dd:95:46:e1:ce:82:04: - fa:0b:1c:29:75:07:85:ce:cd:59:d4:cc:f3:56:b3:72:4d:cb: - 90:0f:ce:02:21:ce:5d:17:84:96:7f:6a:00:57:42:b7:24:5b: - 07:25:1e:77:a8:9d:da:41:09:8e:29:79:b4:b0:a1:45:c8:70: - ae:2c:86:24:ae:3d:9a:74:a7:04:78:d6:1f:1b:17:c5:c1:6d: - b1:1a:fd:f4:50:2e:61:16:84:89:d0:42:3f:b6:bf:bd:52:bd: - c8:3e:8e:87:b4:f0:bd:ad:c7:51:65:2f:77:e8:69:79:0e:03: - 63:89:e7:70:ad:c8:d1:2f:1a:a5:06:d2:90:db:7c:07:35:9a: - 0b:0e:85:87:d1:70:17:a7:88:0f:c6:b5:9c:88:00:fa:f9:b2: - 0a:19:5a:4b:8d:91:12:51:5e:0e:c1:d8:9e:02:78:d0:2d:24: - 09:fe:d4:97:3c:cb:a0:1f:9a:ab:f7:0f:e2:fa:64:23:4e:53: - 0a:15:3e:f5:04:01:86:29:8b:8e:24:40:2f:b1:90:87:5c:3b: - 7b:a7:4c:06:af:c3:90:7f:e9:c6:56:42:61:15:2c:83:f1:7c: - 4f:89:17:f3:a0:11:34:3f:8d:af:75:34:60:1e:e0:f2:f3:02: - e7:aa:b3:f7:9f:1c:f8:69:f4:fe:da:57:6e:1b:95:53:70:cd: - ed:b6:bb:2a:84:eb:ab:c3:a9:b4:d5:15:a0:b2:cc:81:2d:f1: - 56:c1:54:9b:5f:14:4c:5f:ad:5f:f5:06:ee:22:60:45:e4:50: - 35:64:ac:ac:ca:4a:bf:86:78:f8:53:2d:17:d8:e8:84:c8:07: - a4:c2:29:76:c7:1f + Signature Value: + 5f:d8:9b:dc:22:55:80:47:e1:9b:04:3e:46:53:9b:e5:a7:4a: + 8f:eb:53:01:39:d5:04:f6:cf:dc:48:84:8a:a9:c3:a5:35:22: + 2f:ab:74:77:ec:a6:fd:b1:e6:e6:74:82:38:54:0b:27:36:e6: + ec:3d:fe:92:1a:b2:7a:35:0d:a3:e5:7c:ff:e5:5b:1a:28:4b: + 29:1f:99:1b:3e:11:e9:e2:e0:d7:da:06:4f:e3:7b:8c:ad:30: + f4:39:24:e8:ad:2a:0e:71:74:ab:ed:62:e9:9f:85:7e:6a:b0: + bb:53:b4:d7:6b:b8:da:54:15:5c:9a:41:cf:61:f1:ab:67:d6: + 27:5c:0c:a3:d7:41:e7:27:3e:58:89:d6:1f:3f:2a:52:cc:13: + 0b:4b:e6:d6:ba:a0:c7:fd:e3:17:a4:b8:da:cc:cb:88:70:21: + 3b:70:df:09:40:6c:e7:02:81:08:80:b0:36:77:fb:44:c5:cf: + bf:19:54:7c:d1:4e:1f:a2:44:9e:d8:56:0e:bf:4b:0b:e0:84: + 6f:bc:f6:c6:7f:35:7a:17:ca:83:b3:82:c6:4e:d3:f3:d8:30: + 05:fd:6d:3c:8a:ab:63:55:6f:c5:18:ba:66:fe:e2:35:04:2b: + ae:76:34:f0:56:18:e8:54:db:83:b2:1b:93:0a:25:81:81:f0: + 25:ca:0a:95:be:8e:2f:05:3f:6c:e7:de:d1:7c:b8:a3:71:7c: + 6f:8a:05:c3:69:eb:6f:e6:76:8c:11:e1:59:0b:12:53:07:42: + 84:e8:89:ee:ab:7d:28:81:48:e8:79:d5:cf:a2:05:a4:fd:72: + 2c:7d:b4:1c:08:90:4e:0d:10:05:d1:9a:c0:69:4c:0a:14:39: + 17:fb:4d:5b:f6:42:bb:46:27:23:0f:5e:57:5b:b8:ae:9b:a3: + 0e:23:59:41:63:41:a4:f1:69:df:b3:a3:5c:10:d5:63:30:74: + a8:3c:0c:8e:1c:6b:10:e1:13:27:02:26:9b:fd:88:93:7e:91: + 9c:f9:c2:07:27:a4 -----BEGIN CERTIFICATE----- MIIGvTCCBSWgAwIBAgIJAMstgJlaaVJgMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx NDIzMTZaMF0xCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xEDAOBgNVBAMMB2lk -bnNhbnMwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQC8sqplTuHuLjbW -TL5SL2D1fw9U6WQzLVAF5gsyhd5lr2FpfYwjrob5Mav91aOLbJRTvoNyXsJ26FPS -0RycRGXbomcIEJxXGy9aI+0MLYBt1G5mgqCH+HcVCwPzCNlhVnTwvpgA7y8zs3+6 -ezZAPWkF0yWOMYLtTcq9A5GWeavt5VMgm1KZF3gO4k58oPyk3Ae9D0LAaYsX6DFi -BYx41eUR5UbSb5IYXaDd8d6jqW/jnYhgc6Cxkv1gTJFn87V5lrG0vYMSRUtWDQ9Y -Jh/EKAxjGw7AeY429p6TE4UoJhDmoFYR2NLvawhNIplxol/v0fs0veFQjI/UsTD8 -2tRfnYL4IX8szhLsE5/5Iq8aiLHjVbIMwmDYAa0P63Ap2kf1biSn9mpDL8lQazSo -yr8xzIq2QS5HMvGbeMAmS0ih10Zx84uVmkWlavgvtSflw8K/ZXT9c70rZp/TdBGY -95cOFsbg5U/20M/Llpis9tcBCaoVaYSFupatrP+p8y19qP2nebsCAwEAAaOCAo4w +bnNhbnMwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCp6zt40WB3K7yj +BGugnRuqI3ApftThZWDIpvW0cVmN0nqQxsO6CCnS4dS7SYhGFiIqWjNVc2WG0gv7 +nC5DFguqbndNZk9/SjX8EOxKz4ANjd61WnTkDO5Tbiiyd+TuEBxhmbEF69bF9dtd +1Sgo8jmM7j+aa6ClYh/49bx+blJDF76EGSrmB1q+obMeZURhPXNBeoiqKR83x5Hc +LTJYMocvb6m8uABwuSka13Gb3QGu06p5ldK6TDK38HsoOy6MFO5F1PrkakG/eBHO +jcBOGPfNmTwWOqvwlcQWykr4QspWS+yTzdkgZ+mxar/yQuq7wuYSNaEfGH5yoYtV +WIgKwwZRDPqpSQuVe+J+MWLPQ6RTM+rXIHVzHtPk1f8DrgN+hSepJy/sVBBEQCzj +nyB+scn76ETWch3iyVoMj3oVOGs0b4XTDMmUw/DmEt5TDah7TqE3G+fpBIbgMSjx +MzUQZl27izmM9nQCJRAosNoNwXqlM754K9WcY6gT8kkcj1CfTmMCAwEAAaOCAo4w ggKKMIHhBgNVHREEgdkwgdaCB2lkbnNhbnOCH3huLS1rbmlnLTVxYS5pZG4ucHl0 aG9udGVzdC5uZXSCLnhuLS1rbmlnc2dzc2NoZW4tbGNiMHcuaWRuYTIwMDMucHl0 aG9udGVzdC5uZXSCLnhuLS1rbmlnc2djaGVuLWI0YTNkdW4uaWRuYTIwMDgucHl0 aG9udGVzdC5uZXSCJHhuLS1ueGFzbXE2Yi5pZG5hMjAwMy5weXRob250ZXN0Lm5l dIIkeG4tLW54YXNtbTFjLmlkbmEyMDA4LnB5dGhvbnRlc3QubmV0MA4GA1UdDwEB /wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/ -BAIwADAdBgNVHQ4EFgQUXL4Yf3s/zplmgHlTS90zG0KlfgAwfQYDVR0jBHYwdIAU -s4qgorpx8agkedSkWyU2FR5JyM2hUaRPME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQK +BAIwADAdBgNVHQ4EFgQUW5NCWLC0GMxBTBXrQjNmd0xxL0IwfQYDVR0jBHYwdIAU +8+yUjvKOMMSOaMK/jmoZwMGfdmWhUaRPME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQK DB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNh LXNlcnZlcoIJAMstgJlaaVJbMIGDBggrBgEFBQcBAQR3MHUwPAYIKwYBBQUHMAKG MGh0dHA6Ly90ZXN0Y2EucHl0aG9udGVzdC5uZXQvdGVzdGNhL3B5Y2FjZXJ0LmNl cjA1BggrBgEFBQcwAYYpaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0 Y2Evb2NzcC8wQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL3Rlc3RjYS5weXRob250 ZXN0Lm5ldC90ZXN0Y2EvcmV2b2NhdGlvbi5jcmwwDQYJKoZIhvcNAQELBQADggGB -AF16+IHgp8E/OevTUizhy00ps3cYFxieEvwRzDxJy2v0TWy40vTpN/hr7fXX8eta -QQTH84za4QWOrlhx2QGKMkay3ZVG4c6CBPoLHCl1B4XOzVnUzPNWs3JNy5APzgIh -zl0XhJZ/agBXQrckWwclHneondpBCY4pebSwoUXIcK4shiSuPZp0pwR41h8bF8XB -bbEa/fRQLmEWhInQQj+2v71Svcg+joe08L2tx1FlL3foaXkOA2OJ53CtyNEvGqUG -0pDbfAc1mgsOhYfRcBeniA/GtZyIAPr5sgoZWkuNkRJRXg7B2J4CeNAtJAn+1Jc8 -y6Afmqv3D+L6ZCNOUwoVPvUEAYYpi44kQC+xkIdcO3unTAavw5B/6cZWQmEVLIPx -fE+JF/OgETQ/ja91NGAe4PLzAueqs/efHPhp9P7aV24blVNwze22uyqE66vDqbTV -FaCyzIEt8VbBVJtfFExfrV/1Bu4iYEXkUDVkrKzKSr+GePhTLRfY6ITIB6TCKXbH -Hw== +AF/Ym9wiVYBH4ZsEPkZTm+WnSo/rUwE51QT2z9xIhIqpw6U1Ii+rdHfspv2x5uZ0 +gjhUCyc25uw9/pIasno1DaPlfP/lWxooSykfmRs+Eeni4NfaBk/je4ytMPQ5JOit +Kg5xdKvtYumfhX5qsLtTtNdruNpUFVyaQc9h8atn1idcDKPXQecnPliJ1h8/KlLM +EwtL5ta6oMf94xekuNrMy4hwITtw3wlAbOcCgQiAsDZ3+0TFz78ZVHzRTh+iRJ7Y +Vg6/SwvghG+89sZ/NXoXyoOzgsZO0/PYMAX9bTyKq2NVb8UYumb+4jUEK652NPBW +GOhU24OyG5MKJYGB8CXKCpW+ji8FP2zn3tF8uKNxfG+KBcNp62/mdowR4VkLElMH +QoToie6rfSiBSOh51c+iBaT9cix9tBwIkE4NEAXRmsBpTAoUORf7TVv2QrtGJyMP +XldbuK6bow4jWUFjQaTxad+zo1wQ1WMwdKg8DI4caxDhEycCJpv9iJN+kZz5wgcn +pA== -----END CERTIFICATE----- diff --git a/Lib/test/certdata/keycert.passwd.pem b/Lib/test/certdata/keycert.passwd.pem index c330c36d8f9fde0..187021b8eeb9fa4 100644 --- a/Lib/test/certdata/keycert.passwd.pem +++ b/Lib/test/certdata/keycert.passwd.pem @@ -1,69 +1,69 @@ -----BEGIN ENCRYPTED PRIVATE KEY----- -MIIHbTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIhD+rJdxqb6ECAggA -MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBDTdyjCP3riOSUfxix4aXEvBIIH -ECGkbsFabrcFMZcplw5jHMaOlG7rYjUzwDJ80JM8uzbv2Jb8SvNlns2+xmnEvH/M -mNvRmnXmplbVjH3XBMK8o2Psnr2V/a0j7/pgqpRxHykG+koOY4gzdt3MAg8JPbS2 -hymSl+Y5EpciO3xLfz4aFL1ZNqspQbO/TD13Ij7DUIy7xIRBMp4taoZCrP0cEBAZ -+wgu9m23I4dh3E8RUBzWyFFNic2MVVHrui6JbHc4dIHfyKLtXJDhUcS0vIC9PvcV -jhorh3UZC4lM+/jjXV5AhzQ0VrJ2tXAUX2dA144XHzkSH2QmwfnajPsci7BL2CGC -rjyTy4NfB/lDwU+55dqJZQSKXMxAapJMrtgw7LD5CKQcN6zmfhXGssJ7HQUXKkaX -I1YOFzuUD7oo56BVCnVswv0jX9RxrE5QYNreMlOP9cS+kIYH65N+PAhlURuQC14K -PgDkHn5knSa2UQA5tc5f7zdHOZhGRUfcjLP+KAWA3nh+/2OKw/X3zuPx75YT/FKe -tACPw5hjEpl62m9Xa0eWepZXwqkIOkzHMmCyNCsbC0mmRoEjmvfnslfsmnh4Dg/c -4YsTYMOLLIeCa+WIc38aA5W2lNO9lW0LwLhX1rP+GRVPv+TVHXlfoyaI+jp0iXrJ -t3xxT0gaiIR/VznyS7Py68QV/zB7VdqbsNzS7LdquHK1k8+7OYiWjY3gqyU40Iu2 -d1eSnIoDvQJwyYp7XYXbOlXNLY+s1Qb7yxcW3vXm0Bg3gKT8r1XHWJ9rj+CxAn5r -ysfkPs1JsesxzzQjwTiDNvHnBnZnwxuxfBr26ektEHmuAXSl8V6dzLN/aaPjpTj4 -CkE7KyqX3U9bLkp+ztl4xWKEmW44nskzm0+iqrtrxMyTfvvID4QrABjZL4zmWIqc -e3ZfA3AYk9VDIegk/YKGC5VZ8YS7ZXQ0ASK652XqJ7QlMKTxxV7zda6Fp4uW6/qN -ezt5wgbGGhZQXj2wDQmWNQYyG/juIgYTpCUA54U5XBIjuR6pg+Ytm0UrvNjsUoAC -wGelyqaLDq8U8jdIFYVTJy9aJjQOYXjsUJ0dZN2aGHSlju0ZGIZc49cTIVQ9BTC5 -Yc0Vlwzpl+LuA25DzKZNSb/ci0lO/cQGJ2uXQQgaNgdsHlu8nukENGJhnIzx4fzK -wEh3yHxhTRCzPPwDfXmx0IHXrPqJhSpAgaXBVIm8OjvmMxO+W75W4uLfNY/B7e2H -3cjklGuvkofOf7sEOrGUYf4cb6Obg8FpvHgpKo5Twwmoh/qvEKckBFqNhZXDDl88 -GbGlSEgyaAV1Ig8s1NJKBolWFa0juyPAwJ8vT1T4iwW7kQ7KXKt2UNn96K/HxkLu -pikvukz8oRHMlfVHa0R48UB1fFHwZLzPmwkpu6ancIxk3uO3yfhf6iDk3bmnyMlz -g3k/b6MrLYaOVByRxay85jH3Vvgqfgn6wa6BJ7xQ81eZ8B45gFuTH0J5JtLL7SH8 -darRPLCYfA+Ums9/H6pU5EXfd3yfjMIbvhCXHkJrrljkZ+th3p8dyto6wmYqIY6I -qR9sU+o6DhRaiP8tCICuhHxQpXylUM6WeJkJwduTJ8KWIvzsj4mReIKOl/oC2jSd -gIdKhb9Q3zj9ce4N5m6v66tyvjxGZ+xf3BvUPDD+LwZeXgf7OBsNVbXzQbzto594 -nbCzPocFi3gERE50ru4K70eQCy08TPG5NpOz+DDdO5vpAuMLYEuI7O3L+3GjW40Q -G5bu7H5/i7o/RWR67qhG/7p9kPw3nkUtYgnvnWaPMIuTfb4c2d069kjlfgWjIbbI -tpSKmm5DHlqTE4/ECAbIEDtSaw9dXHCdL3nh5+n428xDdGbjN4lT86tfu17EYKzl -ydH1RJ1LX3o3TEj9UkmDPt7LnftvwybMFEcP7hM2xD4lC++wKQs7Alg6dTkBnJV4 -5xU78WRntJkJTU7kFkpPKA0QfyCuSF1fAMoukDBkqUdOj6jE0BlJQlHk5iwgnJlt -uEdkTjHZEjIUxWC6llPcAzaPNlmnD45AgfEW+Jn21IvutmJiQAz5lm9Z9PXaR0C8 -hXB6owRY67C0YKQwXhoNf6xQun2xGBGYy5rPEEezX1S1tUH5GR/KW1Lh+FzFqHXI -ZEb5avfDqHKehGAjPON+Br7akuQ125M9LLjKuSyPaQzeeCAy356Xd7XzVwbPddbm -9S9WSPqzaPgh10chIHoNoC8HMd33dB5j9/Q6jrbU/oPlptu/GlorWblvJdcTuBGI -IVn45RFnkG8hCz0GJSNzW7+70YdESQbfJW79vssWMaiSjFE0pMyFXrFR5lBywBTx -PiGEUWtvrKG94X1TMlGUzDzDJOQNZ9dT94bonNe9pVmP5BP4/DzwwiWh6qrzWk6p -j8OE4cfCSh2WvHnhJbH7/N0v+JKjtxeIeJ16jx/K2oK5 +MIIHbTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIc17oH9riZswCAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBDwi0Mkj59S0hplpnDSNHwPBIIH +EFGdZuO4Cwzg0bspLhE1UpBN5cBq1rKbf4PyVtCczIqJt3KjO3H5I4KdQd9zihkN +A1qzMiqVZOnQZw1eWFXMdyWuCgvNe1S/PRLWY3iZfnuZ9gZXQvyMEHy4JU7pe2Ib +GNm9mzadzJtGv0YZ05Kkza20zRlOxC/cgaNUV6TPeTSwW9CR2bylxw0lTFKBph+o +uFGcAzhqQuw9vsURYJf1f1iE7bQsnWU2kKmb9cx6kaUXiGJpkUMUraBL/rShoHa0 +eet6saiFnK3XGMCIK0mhS9s92CIQV5H9oQQPo/7s6MOoUHjC/gFoWBXoIDOcN9aR +ngybosCLtofY2m14WcHXvu4NJnfnKStx73K3dy3ZLr2iyjnsqGD1OhqGEWOVG/ho +QiZEhZ+9sOnqWI2OuMhMoQJNvrLj7AY4QbdkahdjNvLjDAQSuMI2uSUDFDNfkQdy +hqF/iiEM28PmSHCapgCpzR4+VfEfXBoyBCqs973asa9qhrorfnBVxXnvsqmKNLGH +dymtEPei9scpoftE5T9TPqQj46446bXk23Xpg8QIFa8InQC2Y+yZqqlqvzCAbN6S +Qcq1DcTSAMnbmBXVu9hPmJYIYOlBMHL8JGbsGrkVOhLiiIou4w3G+DyAvIwPj6j9 +BHLqa7HgUnUEC+zL4azVHOSMqmDsOiF3w9fkBWNSkOyNoZpe+gBjbxq7sp+GjAJv +1CemRC3LSoNzLcjRG2IEGs1jlEHSSfijvwlE4lEy3JVc+QK8BOkKXXDVhY1SQHcS +pniEnj95RFVmAujdFDBoUgySyxK/y6Ju/tHPpSTG9VMNbJTKTdBWAVWWHVJzBFhR +0Ug62VrBK7fmfUdH1b37aIxqsPND2De6WLm0RX+7r3XPDJ7hm+baKCchI5CvnG19 +ky8InhMbU4qV+9LceMETmNKKDkhKl4Zx/Y3nab7DG9s/RZfrTdCHojc9Va/t0Ykp +qlVrvdj/893CdI78SW3VjWBJGWfKMyT16hBMY3TPz6ulbFXk6Pul/KcLLWslghS+ +GKZjyBe96UwfH4C7WjuIB+zo+De3Wr8xOCdJR5zwEutBMM+L/Wul8B6wIEGS71kB +TN/CAoeIgHLQFbcw4YE80dllTnSEsqF+ahVTTcCt3iLUaOgeTUxteMbXY9+nekSX +x8aUcvkMhbU9omdEowFr5/HIMKXo4UXat4fIGgh2pG8v8fA46hZXkhWUh/PhbnQw +StXzn4fA13erqVI679kHMmOIQebv4oqdcwkImrH5fEsACNjQbkYZF5fD4z+1GHkA +e2eGqejVT+OV14I8qfx9oqs2f8aqijH8fYLU0TymE7p53DYZy4WvDwk22I4rMzoQ +sGkOZwfKUYpdBI2t6tEf1ROBjoNG0E2Onq+5iooibN08rKXKAQMWsK+2vNHNHwBW +49vRheQNnRqSuLY+b7QAjA0KuRWo9YptCbnXyF/Aw64jMfAGjggDLoaZfALGZk3n +P+ZoL9xc7rYRpIca44BeYI6AhHFcWWIOX7Sm69FvmyHlfsgTAXVgY1lQPuGy68Au +PHSkgUyydDtkrfb2W2gJuqD/+h+9X2z+o/+nETYPCZm3sH5xvTY/DTcTx9kTpXxx +YQBaFTt12eVX7wZVr5K3u9M371rg+SeXC2SzL4T6APHD52cxbA1jgM0JFh3KJTuk +fADxIzM1NdzYQ45J6i2w+/Fh4VPnXZ0oiUSwE094XTBlvhI6zHgar2Q0Qx1P51vB +odd9XzyDLULuIzei0DYjTIg0KhE+wAGq1I5qtiMhmy5TdCKKNA9WGb1Pq38zpyjU +wGmztzSzCEjfLyhChaUObVRRxEfD5ioxKer/fczOhKQe8FXmGy5u/04tVmmEyNOO +JkkDtZy+UbKuJ257QnY72wPjgtHNy+S4Iv7zHUbNJNhxk+xBlRcmRNWCEM20LBSO +Tj4S9gyan+gH2+WFxy8FaENUhM+vHFEeJcjQIBFBeWICmSmdkh/r0YK1UVJ9NLfR +l0HiKm3lKg+kNCexTAPLMt2rGZ4PAKVnhVaxtuHMYYDpl2GYmyH73B9BfcPdA/Zx +GUBmd9hwcLz9SuUg+fjHcogZRRRlcZlKhw3zUCsqHSCQXZCQm7mBlG/5C/7cM7wQ +IRtsNospLStOg51gv21ClQ+uWx30XEcwmnIfVoLl1vMaguuf1u5u3dWBD/UgmqiP +1Ym8jv0BF/AS+u/CtUpwe7ZWxFT0vbyi10xxIF7O07fwFa+5dME3ycZwcyiE95K1 +ftcHlGOIhuVBMSNZXC4I9LM+7IWy+hanUcK+v5RvwBDSJV3fnAOdfrka1L/HyEEb +x/FYKEiU/TAjXDw2NtZ2itpADTSG5KbdJSwPr01Ak7aE+QYe7TIKJhBDZXGQlqq8 +1wv77zyv7V5Xq2cxSEKgSqzB9fhYZCASe8+HWlV2T+Sd -----END ENCRYPTED PRIVATE KEY----- -----BEGIN CERTIFICATE----- -MIIEWTCCAsGgAwIBAgIJAJinz4jHSjLtMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV -BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u -IFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xODA4 -MjkxNDIzMTVaFw0yODA4MjYxNDIzMTVaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQH -DA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5k -YXRpb24xEjAQBgNVBAMMCWxvY2FsaG9zdDCCAaIwDQYJKoZIhvcNAQEBBQADggGP -ADCCAYoCggGBALKUqUtopT6E68kN+uJNEt34i2EbmG/bwjcD8IaMsgJPSsMO2Bpd -3S6qWgkCeOyCfmAwBxK2kNbxGb63ouysEv7l8GCTJTWv3hG/HQcejJpnAEGi6K1U -fDbyE/db6yZ12SoHVTGkadN4vYGCPd1Wj9ZO1F877SHQ8rDWX3xgTWkxN2ojBw44 -T8RHSDiG8D/CvG4uEy+VUszL+Uvny5y2poNSqvI3J56sptWSrh8nIIbkPZPBdUne -LYMOHTFK3ZjXSmhlXgziTxK71nnzM3Y9K9gxPnRqoXbvu/wFo55hQCkETiRkYgmm -jXcBMZ0TClQVnQWuLjMthRnWFZs4Lfmwqjs7FZD/61581R2BYehvpWbLvvuOJhwv -DFzexL2sXcAl7SsxbzeQKRHqGbIDfbnQTXfs3/VC6Ye5P82P2ucj+XC32N9piRmO -gCBP8L3ub+YzzdxikZN2gZXXE2jsb3QyE/R2LkWdWyshpKe+RsZP1SBRbHShUyOh -yJ90baoiEwj2mwIDAQABoxgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZI -hvcNAQELBQADggGBAHRUO/UIHl3jXQENewYayHxkIx8t7nu40iO2DXbicSijz5bo -5//xAB6RxhBAlsDBehgQP1uoZg+WJW+nHu3CIVOU3qZNZRaozxiCl2UFKcNqLOmx -R3NKpo1jYf4REQIeG8Yw9+hSWLRbshNteP6bKUUf+vanhg9+axyOEOH/iOQvgk/m -b8wA8wNa4ujWljPbTQnj7ry8RqhTM0GcAN5LSdSvcKcpzLcs3aYwh+Z8e30sQWna -F40sa5u7izgBTOrwpcDm/w5kC46vpRQ5fnbshVw6pne2by0mdMECASid/p25N103 -jMqTFlmO7kpf/jpCSmamp3/JSEE1BJKHwQ6Ql4nzRA2N1mnvWH7Zxcv043gkHeAu -0x8evpvwuhdIyproejNFlBpKmW8OX7yKTCPPMC/VkX8Q1rVkxU0DQ6hmvwZlhoKa -9Wc2uXpw9xF8itV4Uvcdr3dwqByvIqn7iI/gB+4l41e0u8OmH2MKOx4Nxlly5TNW -HcVKQHyOeyvnINuBAQ== +MIIEgzCCAuugAwIBAgIUU+FIM/dUbCklbdDwNPd2xemDAEwwDQYJKoZIhvcNAQEL +BQAwXzELMAkGA1UEBhMCWFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYD +VQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjESMBAGA1UEAwwJbG9jYWxo +b3N0MB4XDTIzMTEyNTA0MjEzNloXDTQzMDEyNDA0MjEzNlowXzELMAkGA1UEBhMC +WFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYDVQQKDBpQeXRob24gU29m +dHdhcmUgRm91bmRhdGlvbjESMBAGA1UEAwwJbG9jYWxob3N0MIIBojANBgkqhkiG +9w0BAQEFAAOCAY8AMIIBigKCAYEAzXTIl1su11AGu6sDPsoxqcRGyAX0yjxIcswF +vj+eW/fBs2GcBby95VEOKpJPKRYYB7fAEAjAKK59zFdsDX/ynxPZLqyLQocBkFVq +tclhCRZu//KZND+uQuHSx3PjGkSvK/nrGjg5T0bkM4SFeb0YdLb+0aDTKGozUC82 +oBAilNcrFz1VXpEF0qUe9QeKQhyd0MaW5T1oSn+U3RAj2MXm3TGExyZeaicpIM5O +HFlnwUxsYSDZo0jUj342MbPOZh8szZDWi042jdtSA3i8uMSplEf4O8ZPmX0JCtrz +fVjRVdaKXIjrhMNWB8K44q6AeyhqJcVHtOmPYoHDm0qIjcrurt0LZaGhmCuKimNd +njcPxW0VQmDIS/mO5+s24SK+Mpznm5q/clXEwyD8FbrtrzV5cHCE8eNkxjuQjkmi +wW9uadK1s54tDwRWMl6DRWRyxoF0an885UQWmbsgEB5aRmEx2L0JeD0/q6Iw1Nta +As8DG4AaWuYMrgZXz7XvyiMq3IxVAgMBAAGjNzA1MBQGA1UdEQQNMAuCCWxvY2Fs +aG9zdDAdBgNVHQ4EFgQUl2wd7iWE1JTZUVq2yFBKGm9N36owDQYJKoZIhvcNAQEL +BQADggGBAF0f5x6QXFbgdyLOyeAPD/1DDxNjM68fJSmNM/6vxHJeDFzK0Pja+iJo +xv54YiS9F2tiKPpejk4ujvLQgvrYrTQvliIE+7fUT0dV74wZKPdLphftT9uEo1dH +TeIld+549fqcfZCJfVPE2Ka4vfyMGij9hVfY5FoZL1Xpnq/ZGYyWZNAPbkG292p8 +KrfLZm/0fFYAhq8tG/6DX7+2btxeX4MP/49tzskcYWgOjlkknyhJ76aMG9BJ1D7F +/TIEh5ihNwRTmyt023RBz/xWiN4xBLyIlpQ6d5ECKmFNFr0qnEui6UovfCHUF6lZ +qcAQ5VFQQ2CayNlVmQ+UGmWIqANlacYWBt7Q6VqpGg24zTMec1/Pqd6X07ScSfrm +MAtywrWrU7p1aEkN5lBa4n/XKZHGYMjor/YcMdF5yjdSrZr274YYO1pafmTFwRwH +5o16c8WPc0aPvTFbkGIFT5ddxYstw+QwsBtLKE2lJ4Qfmxt0Ew/0L7xkbK1BaCOo +EGD2IF7VDQ== -----END CERTIFICATE----- - diff --git a/Lib/test/certdata/keycert.pem b/Lib/test/certdata/keycert.pem index 0d398633739a51e..a30d15ca4d61a62 100644 --- a/Lib/test/certdata/keycert.pem +++ b/Lib/test/certdata/keycert.pem @@ -1,66 +1,67 @@ -----BEGIN PRIVATE KEY----- -MIIG/wIBADANBgkqhkiG9w0BAQEFAASCBukwggblAgEAAoIBgQCylKlLaKU+hOvJ -DfriTRLd+IthG5hv28I3A/CGjLICT0rDDtgaXd0uqloJAnjsgn5gMAcStpDW8Rm+ -t6LsrBL+5fBgkyU1r94Rvx0HHoyaZwBBouitVHw28hP3W+smddkqB1UxpGnTeL2B -gj3dVo/WTtRfO+0h0PKw1l98YE1pMTdqIwcOOE/ER0g4hvA/wrxuLhMvlVLMy/lL -58uctqaDUqryNyeerKbVkq4fJyCG5D2TwXVJ3i2DDh0xSt2Y10poZV4M4k8Su9Z5 -8zN2PSvYMT50aqF277v8BaOeYUApBE4kZGIJpo13ATGdEwpUFZ0Fri4zLYUZ1hWb -OC35sKo7OxWQ/+tefNUdgWHob6Vmy777jiYcLwxc3sS9rF3AJe0rMW83kCkR6hmy -A3250E137N/1QumHuT/Nj9rnI/lwt9jfaYkZjoAgT/C97m/mM83cYpGTdoGV1xNo -7G90MhP0di5FnVsrIaSnvkbGT9UgUWx0oVMjocifdG2qIhMI9psCAwEAAQKCAYBT -sHmaPmNaZj59jZCqp0YVQlpHWwBYQ5vD3pPE6oCttm0p9nXt/VkfenQRTthOtmT1 -POzDp00/feP7zeGLmqSYUjgRekPw4gdnN7Ip2PY5kdW77NWwDSzdLxuOS8Rq1MW9 -/Yu+ZPe3RBlDbT8C0IM+Atlh/BqIQ3zIxN4g0pzUlF0M33d6AYfYSzOcUhibOO7H -j84r+YXBNkIRgYKZYbutRXuZYaGuqejRpBj3voVu0d3Ntdb6lCWuClpB9HzfGN0c -RTv8g6UYO4sK3qyFn90ibIR/1GB9watvtoWVZqggiWeBzSWVWRsGEf9O+Cx4oJw1 -IphglhmhbgNksbj7bD24on/icldSOiVkoUemUOFmHWhCm4PnB1GmbD8YMfEdSbks -qDr1Ps1zg4mGOinVD/4cY7vuPFO/HCH07wfeaUGzRt4g0/yLr+XjVofOA3oowyxv -JAzr+niHA3lg5ecj4r7M68efwzN1OCyjMrVJw2RAzwvGxE+rm5NiT08SWlKQZnkC -gcEA4wvyLpIur/UB84nV3XVJ89UMNBLm++aTFzld047BLJtMaOhvNqx6Cl5c8VuW -l261KHjiVzpfNM3/A2LBQJcYkhX7avkqEXlj57cl+dCWAVwUzKmLJTPjfaTTZnYJ -xeN3dMYjJz2z2WtgvfvDoJLukVwIMmhTY8wtqqYyQBJ/l06pBsfw5TNvmVIOQHds -8ASOiFt+WRLk2bl9xrGGayqt3VV93KVRzF27cpjOgEcG74F3c0ZW9snERN7vIYwB -JfrlAoHBAMlahPwMP2TYylG8OzHe7EiehTekSO26LGh0Cq3wTGXYsK/q8hQCzL14 -kWW638vpwXL6L9ntvrd7hjzWRO3vX/VxnYEA6f0bpqHq1tZi6lzix5CTUN5McpDg -QnjenSJNrNjS1zEF8WeY9iLEuDI/M/iUW4y9R6s3WpgQhPDXpSvd2g3gMGRUYhxQ -Xna8auiJeYFq0oNaOxvJj+VeOfJ3ZMJttd+Y7gTOYZcbg3SdRb/kdxYki0RMD2hF -4ZvjJ6CTfwKBwQDiMqiZFTJGQwYqp4vWEmAW+I4r4xkUpWatoI2Fk5eI5T9+1PLX -uYXsho56NxEU1UrOg4Cb/p+TcBc8PErkGqR0BkpxDMOInTOXSrQe6lxIBoECVXc3 -HTbrmiay0a5y5GfCgxPKqIJhfcToAceoVjovv0y7S4yoxGZKuUEe7E8JY2iqRNAO -yOvKCCICv/hcN235E44RF+2/rDlOltagNej5tY6rIFkaDdgOF4bD7f9O5eEni1Bg -litfoesDtQP/3rECgcEAkQfvQ7D6tIPmbqsbJBfCr6fmoqZllT4FIJN84b50+OL0 -mTGsfjdqC4tdhx3sdu7/VPbaIqm5NmX10bowWgWSY7MbVME4yQPyqSwC5NbIonEC -d6N0mzoLR0kQ+Ai4u+2g82gicgAq2oj1uSNi3WZi48jQjHYFulCbo246o1NgeFFK -77WshYe2R1ioQfQDOU1URKCR0uTaMHClgfu112yiGd12JAD+aF3TM0kxDXz+sXI5 -SKy311DFxECZeXRLpcC3AoHBAJkNMJWTyPYbeVu+CTQkec8Uun233EkXa2kUNZc/ -5DuXDaK+A3DMgYRufTKSPpDHGaCZ1SYPInX1Uoe2dgVjWssRL2uitR4ENabDoAOA -ICVYXYYNagqQu5wwirF0QeaMXo1fjhuuHQh8GsMdXZvYEaAITZ9/NG5x/oY08+8H -kr78SMBOPy3XQn964uKG+e3JwpOG14GKABdAlrHKFXNWchu/6dgcYXB87mrC/GhO -zNwzC+QhFTZoOomFoqMgFWujng== +MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQDNdMiXWy7XUAa7 +qwM+yjGpxEbIBfTKPEhyzAW+P55b98GzYZwFvL3lUQ4qkk8pFhgHt8AQCMAorn3M +V2wNf/KfE9kurItChwGQVWq1yWEJFm7/8pk0P65C4dLHc+MaRK8r+esaODlPRuQz +hIV5vRh0tv7RoNMoajNQLzagECKU1ysXPVVekQXSpR71B4pCHJ3QxpblPWhKf5Td +ECPYxebdMYTHJl5qJykgzk4cWWfBTGxhINmjSNSPfjYxs85mHyzNkNaLTjaN21ID +eLy4xKmUR/g7xk+ZfQkK2vN9WNFV1opciOuEw1YHwrjiroB7KGolxUe06Y9igcOb +SoiNyu6u3QtloaGYK4qKY12eNw/FbRVCYMhL+Y7n6zbhIr4ynOebmr9yVcTDIPwV +uu2vNXlwcITx42TGO5COSaLBb25p0rWzni0PBFYyXoNFZHLGgXRqfzzlRBaZuyAQ +HlpGYTHYvQl4PT+rojDU21oCzwMbgBpa5gyuBlfPte/KIyrcjFUCAwEAAQKCAYAO +M1r0+TCy4Z1hhceu5JdLql0RELZTbxi71IW2GVwW87gv75hy3hGLAs/1mdC+YIBP +MkBka1JqzWq0/7rgcP5CSAMsInFqqv2s7fZ286ERGXuZFbnInnkrNsQUlJo3E9W+ +tqKtGIM/i0EVHX0DRdJlqMtSjmjh43tB+M1wAUV+n6OjEtJue5wZK+AIpBmGicdP +qZY+6IBnm8tcfzPXFRCoq7ZHdIu0jxnc4l2MQJK3DdL04KoiStOkSl8xDsI+lTtq +D3qa41LE0TY8X2jJ/w6KK3cUeK7F4DQYs+kfCKWMVPpn0/5u6TbC1F7gLvkrseph +7cIgrruNNs9iKacnR1w3U72R+hNxHsNfo4RGHFa192p/Mfc+kiBd5RNR/M9oHdeq +U6T/+KM+QyF5dDOyonY0QjwfAcEx+ZsV72nj8AerjM907I6dgHo/9YZ2S1Dt/xuG +ntD+76GDzmrOvXmmpF0DsTn+Wql7AC4uzaOjv6PVziqz03pR61RpjPDemyJEWMkC +gcEA7BkGGX3enBENs3X6BYFoeXfGO/hV7/aNpA6ykLzw657dqwy2b6bWLiIaqZdZ +u0oiY6+SpOtavkZBFTq4bTVD58FHL0n73Yvvaft507kijpYBrxyDOfTJOETv+dVG +XiY8AUSAE6GjPi0ebuYIVUxoDnMeWDuRJNvTck4byn1hJ1aVlEhwXNxt/nAjq48s +5QDuR6Z9F8lqEACRYCHSMQYFm35c7c1pPsHJnElX8a7eZ9lT7HGPXHaf/ypMkOzo +dvJNAoHBAN7GhDomff/kSgQLyzmqKqQowTZlyihnReapygwr8YpNcqKDqq6VlnfH +Jl1+qtSMSVI0csmccwJWkz1WtSjDsvY+oMdv4gUK3028vQAMQZo+Sh7OElFPFET3 +UmL+Nh73ACPgpiommsdLZQPcIqpWNT5NzO+Jm5xa+U9ToVZgQ7xjrqee5NUiMutr +r7UWAz7vDWu3x7bzYRRdUJxU18NogGbFGWJ1KM0c67GUXu2E7wBQdjVdS78UWs+4 +XBxKQkG2KQKBwQCtO+M82x122BB8iGkulvhogBjlMd8klnzxTpN5HhmMWWH+uvI1 +1G29Jer4WwRNJyU6jb4E4mgPyw7AG/jssLOlniy0Jw32TlIaKpoGXwZbJvgPW9Vx +tgnbDsIiR3o9ZMKMj42GWgike4ikCIc+xzRmvdMbHIHwUJfCfEtp9TtPGPnh9pDz +og3XLsMNg52GXnt3+VI6HOCE41XH+qj2rZt5r2tSVXEOyjQ7R5mOzSeFfXJVwDFX +v/a/zHKnuB0OAdUCgcBLrxPTEaqy2eMPdtZHM/mipbnmejRw/4zu7XYYJoG7483z +SlodT/K7pKvzDYqKBVMPm4P33K/x9mm1aBTJ0ZqmL+a9etRFtEjjByEKuB89gLX7 +uzTb7MrNF10lBopqgK3KgpLRNSZWWNXrtskMJ5eVICdkpdJ5Dyst+RKR3siEYzU9 ++yxxAFpeQsqB8gWORva/RsOR8yNjIMS3J9fZqlIdGA8ktPr0nEOyo96QQR5VdACE +5rpKI2cqtM6OSegynOkCgcAnr2Xzjef6tdcrxrQrq0DjEFTMoCAxQRa6tuF/NYHV +AK70Y4hBNX84Bvym4hmfbMUEuOCJU+QHQf/iDQrHXPhtX3X2/t8M+AlIzmwLKf2o +VwCYnZ8SqiwSaWVg+GANWLh0JuKn/ZYyR8urR79dAXFfp0UK+N39vIxNoBisBf+F +G8mca7zx3UtK2eOW8WgGHz+Y20VZy0m/nkNekd1ZTXoSGhL+iN4XsTRn1YQIn69R +kNdcwhtZZ3dpChUdf+w/LIc= -----END PRIVATE KEY----- -----BEGIN CERTIFICATE----- -MIIEWTCCAsGgAwIBAgIJAJinz4jHSjLtMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV -BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u -IFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xODA4 -MjkxNDIzMTVaFw0yODA4MjYxNDIzMTVaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQH -DA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5k -YXRpb24xEjAQBgNVBAMMCWxvY2FsaG9zdDCCAaIwDQYJKoZIhvcNAQEBBQADggGP -ADCCAYoCggGBALKUqUtopT6E68kN+uJNEt34i2EbmG/bwjcD8IaMsgJPSsMO2Bpd -3S6qWgkCeOyCfmAwBxK2kNbxGb63ouysEv7l8GCTJTWv3hG/HQcejJpnAEGi6K1U -fDbyE/db6yZ12SoHVTGkadN4vYGCPd1Wj9ZO1F877SHQ8rDWX3xgTWkxN2ojBw44 -T8RHSDiG8D/CvG4uEy+VUszL+Uvny5y2poNSqvI3J56sptWSrh8nIIbkPZPBdUne -LYMOHTFK3ZjXSmhlXgziTxK71nnzM3Y9K9gxPnRqoXbvu/wFo55hQCkETiRkYgmm -jXcBMZ0TClQVnQWuLjMthRnWFZs4Lfmwqjs7FZD/61581R2BYehvpWbLvvuOJhwv -DFzexL2sXcAl7SsxbzeQKRHqGbIDfbnQTXfs3/VC6Ye5P82P2ucj+XC32N9piRmO -gCBP8L3ub+YzzdxikZN2gZXXE2jsb3QyE/R2LkWdWyshpKe+RsZP1SBRbHShUyOh -yJ90baoiEwj2mwIDAQABoxgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZI -hvcNAQELBQADggGBAHRUO/UIHl3jXQENewYayHxkIx8t7nu40iO2DXbicSijz5bo -5//xAB6RxhBAlsDBehgQP1uoZg+WJW+nHu3CIVOU3qZNZRaozxiCl2UFKcNqLOmx -R3NKpo1jYf4REQIeG8Yw9+hSWLRbshNteP6bKUUf+vanhg9+axyOEOH/iOQvgk/m -b8wA8wNa4ujWljPbTQnj7ry8RqhTM0GcAN5LSdSvcKcpzLcs3aYwh+Z8e30sQWna -F40sa5u7izgBTOrwpcDm/w5kC46vpRQ5fnbshVw6pne2by0mdMECASid/p25N103 -jMqTFlmO7kpf/jpCSmamp3/JSEE1BJKHwQ6Ql4nzRA2N1mnvWH7Zxcv043gkHeAu -0x8evpvwuhdIyproejNFlBpKmW8OX7yKTCPPMC/VkX8Q1rVkxU0DQ6hmvwZlhoKa -9Wc2uXpw9xF8itV4Uvcdr3dwqByvIqn7iI/gB+4l41e0u8OmH2MKOx4Nxlly5TNW -HcVKQHyOeyvnINuBAQ== +MIIEgzCCAuugAwIBAgIUU+FIM/dUbCklbdDwNPd2xemDAEwwDQYJKoZIhvcNAQEL +BQAwXzELMAkGA1UEBhMCWFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYD +VQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjESMBAGA1UEAwwJbG9jYWxo +b3N0MB4XDTIzMTEyNTA0MjEzNloXDTQzMDEyNDA0MjEzNlowXzELMAkGA1UEBhMC +WFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYDVQQKDBpQeXRob24gU29m +dHdhcmUgRm91bmRhdGlvbjESMBAGA1UEAwwJbG9jYWxob3N0MIIBojANBgkqhkiG +9w0BAQEFAAOCAY8AMIIBigKCAYEAzXTIl1su11AGu6sDPsoxqcRGyAX0yjxIcswF +vj+eW/fBs2GcBby95VEOKpJPKRYYB7fAEAjAKK59zFdsDX/ynxPZLqyLQocBkFVq +tclhCRZu//KZND+uQuHSx3PjGkSvK/nrGjg5T0bkM4SFeb0YdLb+0aDTKGozUC82 +oBAilNcrFz1VXpEF0qUe9QeKQhyd0MaW5T1oSn+U3RAj2MXm3TGExyZeaicpIM5O +HFlnwUxsYSDZo0jUj342MbPOZh8szZDWi042jdtSA3i8uMSplEf4O8ZPmX0JCtrz +fVjRVdaKXIjrhMNWB8K44q6AeyhqJcVHtOmPYoHDm0qIjcrurt0LZaGhmCuKimNd +njcPxW0VQmDIS/mO5+s24SK+Mpznm5q/clXEwyD8FbrtrzV5cHCE8eNkxjuQjkmi +wW9uadK1s54tDwRWMl6DRWRyxoF0an885UQWmbsgEB5aRmEx2L0JeD0/q6Iw1Nta +As8DG4AaWuYMrgZXz7XvyiMq3IxVAgMBAAGjNzA1MBQGA1UdEQQNMAuCCWxvY2Fs +aG9zdDAdBgNVHQ4EFgQUl2wd7iWE1JTZUVq2yFBKGm9N36owDQYJKoZIhvcNAQEL +BQADggGBAF0f5x6QXFbgdyLOyeAPD/1DDxNjM68fJSmNM/6vxHJeDFzK0Pja+iJo +xv54YiS9F2tiKPpejk4ujvLQgvrYrTQvliIE+7fUT0dV74wZKPdLphftT9uEo1dH +TeIld+549fqcfZCJfVPE2Ka4vfyMGij9hVfY5FoZL1Xpnq/ZGYyWZNAPbkG292p8 +KrfLZm/0fFYAhq8tG/6DX7+2btxeX4MP/49tzskcYWgOjlkknyhJ76aMG9BJ1D7F +/TIEh5ihNwRTmyt023RBz/xWiN4xBLyIlpQ6d5ECKmFNFr0qnEui6UovfCHUF6lZ +qcAQ5VFQQ2CayNlVmQ+UGmWIqANlacYWBt7Q6VqpGg24zTMec1/Pqd6X07ScSfrm +MAtywrWrU7p1aEkN5lBa4n/XKZHGYMjor/YcMdF5yjdSrZr274YYO1pafmTFwRwH +5o16c8WPc0aPvTFbkGIFT5ddxYstw+QwsBtLKE2lJ4Qfmxt0Ew/0L7xkbK1BaCOo +EGD2IF7VDQ== -----END CERTIFICATE----- diff --git a/Lib/test/certdata/keycert2.pem b/Lib/test/certdata/keycert2.pem index e59d45439d4b6df..c7c4aa74583c8ab 100644 --- a/Lib/test/certdata/keycert2.pem +++ b/Lib/test/certdata/keycert2.pem @@ -1,66 +1,67 @@ -----BEGIN PRIVATE KEY----- -MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQCf8FWxi4oVlDVx -e8NDFgb+IYAGr/hZWuY1Zq7d7g57yPoxJrgt+bN89+U7qTduqyB2Hy8G0TqeACOr -IdpPZ8P7V5E5YiASwfJ72nbVo7qR9DAKA5FE8PU0bJFmFLjDDihc970zc4ilRDfR -WylUpj68nefOY4CzFzeiqVOLX2wezs7Z0hflkSXGBmC0j1FbQU2I3YJg3CKCabhT -tU6OyKItzjJ2vVaOoQ+B0Kv8leaRQ6ANZBAFQF2LepSy5F2+oSD+QHjPr+012V5D -mrsdIc9We8YyonS1u/3HI7lLohf3W+qFroQWjn0DJI56ScV1uEr/B0+hn2jBRTM5 -d1F9BeVWm1u8BOJu50CvOeuxiVLsxJpa4T41DJznJk5V+hE4hKvDKmlrwulsRp8o -jUEyUi8dzWOBRfAijIWv3qAPjGA/J33n6+PllCczC2BsVZhVmLqSMCwp1g2JTCM/ -KC7T4vOl/EGkm76fcmLeA1Ef8oUdRg+3T77VP+HqZ2JP06J8O8MCAwEAAQKCAYAw -YvJZ82BEJQGCIrIxMpHNAm+MFmKpDdIFp9oRdDrXgjcG9bLU3e1KSmkEgq4tggIh -GlAM3PHB6ULhPC2ixj7JZHWgCaqwYhKtG6vF+HGyRFDgRrIFTGyyfoICgxReloLp -lV2dGj/l19yXLuAzJtRmFdOSYhIGnGiNgnKvAKBiNajoxyHJpv7piPZqyc0QMZJ2 -bKVMDm02TSuhz4FDuzktaGtl9uQf5GQfnvTZRrRpkC70vigGnrFuSBiCgopF6NLq -6AXl8YS3Jcu2oGWrZDfS/GlG1QmvGGsmr9wndJSGG43jcpcRZt0g1nJNu4Fioq3e -7y6Gap9TEsciuQOv/6RD457XkNARmTQxFpEwmSgOPQn2pFcDspo71Ej7azzL/Z+3 -jvnVo3wxgxBcrpyh+vhBtJARp4pT4anW4PcD6IcPSOWbnI8Ldoj1XN5QkJcBcykK -6LmsAUqsmEQDNsmnGZWyYSCns4P2vUJi0hwQz8UiQwgAta3xnq4v5On7l3cq35kC -gcEA0+joOFbZBeGlCb27tDW4VCW0cQuczzuNEoBUKnsNSqy0nx1O7hgHm/f/NQDD -cpxiD15bRQ0KM9QbQC4dGaVoLsM07hUGk97dCxQPs2zot4CodCKGohs7E154tEDP -zVg3YS5mubUmqdqtn8ZCKeeZye/Tv2ageyF300sEgj2Cd7EZ8S4sB0PxZ2tqT3jy -cBL5cDruLEWuHIQjN7WwSjxnXocpb1OU7dJ+v4zFPCkSCOoa0DTTw4jFhPEOBdqV -T619AoHBAME3QyW4QVtU2Ct9u0B1XThhqSEyOpUrcH9nOoefggwP4WF3phVx16BG -aDKUIGQ62klRa5fi2eooxcjQRLv1sWO0UzssnO6ABMnGkUiRdrowo6xukNak0RTp -0gvNoJ0SZxGF0yWSCw1Rq3qP2Koj7XDumFChAzLMyUsnoOl29SA7GfXcZp1pZTiq -kOfFMWt0CIHu/EK03YWcd4vfQEq6lus39RCSXuL++Jva3yiEl5s069RFZvP1bNrD -emkfetDSPwKBwQClk+8fVnzs44sZOW9ZOEB3P57mVbSJGHb6Zdtd9hhEqP3Y9gWe -dJg9fmGjAJ23CAp3B7s5ER9PsAQ6+c0zJNNq9ox9G2CwWgtNhLdf81FDUPxPAktA -jxZx4/dcoOe+A5gCD0elA67aOUxA86DvLVA1QXeqrn3muBfwuUUknvs6mt8yXGl6 -o9QUgxHmVxLYD3tn/iPr4+ZP0c/Sz9yXpOsAKYxuuFg+G6N9+HiEsXKuFH4vAZgV -yODNJ61VVZ4lS+ECgcAqFqOl39E81+qO7sCPdgFsermg5ZQlUmUbG52AVZq6jesG -lE21disGWs/v1JyJuNg8CGRrnZriiycqa1PNreOKWImY5kr5GSHx4jNbn3RBcr70 -nNEoMJbq+1QqBgzqqkuRYZlxIbMOn6++7v6/cTwT0aWUSr6rnjhrCqLeuG8FKlqp -V+1ydLb79QvDsQzm30vLIggJb+ShakgQS/1xSdv+OR5FEd1hjTESokbiSJ/Ny2Vj -xAp9MgUYUmSj6ZuTSXkCgcAggshdRQLom/EK2pYwffIpKfBiyLbi+KIjKxkiPEsb -jrrQbvh9ZN6iAG3StVAYB5c6vewfeIlcDT0YJDyy1hGRLRG7vf9ubPf+n7Xp1y0W -oo9L9qfCHu0jmWwtinkFYjpTDkXlxXCG2v3TllNsNX/5afYo8sb9oxXHLTpBlwZB -fw6IgNZblWQevdgmUMTP9W2W7AZUxEz4gOM6lQkOwC3U59Dx2yO6rD3An6G1tlZF -2MClyf8o5d5ePObH8rkxrpY= +MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQCyAUXjczgUEn7m +mOwDMi/++wDRxqJAJ2f7F9ADxTuOm+EtdpfYr4mBn8Uz9e3I+ZheG5y3QZ1ddBYA +9YTfcUL0on8UXLOOBVZCetxsQXoSAuDMPV0IXeEgtZZDXe7STqKSQeYk7Cz+VtHe +lZ8j7oOOcx5sJgpbaD+OGJnPoAdB8l8nQfxqAG45sW4P6gfLKoJLviKctDe5pvgi +JC8tvytg/IhESKeefLZ4ix2dNjj2GNUaL+khU6UEuM1kJHcPVjPoYc+y8fop/qhQ +0ithBhO2OvJ+YmOFdCE67SyCwU3p8zJpN+XkwbHttgmNg4OSs7H6V7E52/CsTNTA +SthBHXtxqaM+vjbGARrz2Fpc/n+LwRt7MGIR0gVtntTgnP0HoeHskhAIeDtaPrZ6 +zHdl3aDwgAecVebTEBT5YPboz+X1lWdOrRD2JW3bqXSRIN3E4qz5IMuNx3VvhpSR +eFZzR6QIbxQqzO/Vp93Ivy8hPZ6WMgfSYWs7CGtu4NP79PJfdMsCAwEAAQKCAYAc +e3yp2NlbyNvaXRTCrCim5ZXrexuiJUwLjvNfbxNJDeM5iZThfLEFd0GwP0U1l86M +HGH2pr6d4gHVVHPW5wIeL9Qit3SZoHv9djhH8DAuqpw6wgTdXlw0BipNjD23FBMK +URYYyVuntM+vDITi1Hrjc8Ml7e5RUvx8aa5O3R3cLQKRvwq7EWeRvrTMQhfOJ/ai +VQGnzmRuRevFVsHf0YuI4M+TEYcUooL2BdiOu8rggfezUYA9r2sjtshSok0UvKeb +79pNzWmg9EWVeFk+A0HQpyLq+3EVyB5UZ3CZRkT0XhEm1B7mpKrtcGMjaumNAam7 +jkhidGdhT/PV9BB1TttcqwTf+JH9P9sSpY9ZTA1LkkeWe9Rwqpxjjssqxxl6Xnds ++wUfuovVvHuBinsO+5PLE5UQByn21WcIBNnPCAMvALy/68T7z8+ATJ+I2CqBXuM2 +z5663hNrvdCu93PpK4j/k/1l3NTrthaorbnPhkmNYHQkBicpAfFQywrv6enD+30C +gcEA7Vlw76og4oxI7SSD6gTfo85OqTLp2CUZhNNxzYiUOOssRnGHBKsGZ8p0OhLN +vk9/SgbeHL5jsmnyd8ZuYWmsOQHRWgg1zO3S25iuq+VAo4cL/7IynoJ0RP5xP0Pw +QD+xJLZQp0XuLUtXnlc6dM5Hg7tOTthOP9UxA1i57lzpYfkRnKmSeWi+4IDJymOt +WoWnEK7Yr7qSg6aScLWRyIvAPVmKF9LToSFaTq0eOD0GIwAQxqNbIwN3U0UJ5Ruc +KRBVAoHBAL/+DNGqnEzhhWS6zqZp2eH90YR+b3R4yOK4PROm2AVA3h1GhIAiWX68 +PvKYZK9dZ9EdAswlFf9PVQYIXUraR3az0UiIywnNMri+kO1ZxwofGvljrOfRRLg0 +B46wuHi6dVgTWzjTl503G9+FpAYNHv184xsr1tne0pf2TKEnN7oyQciCV8qtr8vV +HrL46uaD0w1fcXIXbO3F/7ErLsvsgLzKfxR5BeQo6Fq0GmzD+lCmzVNirtfl2CZj +2ukROXUQnwKBwQDR1CqFlm/wGHk4PPnp31ke5XqhFoOpNFM1HAEV5VK0ZyQDOsZU +mCXXiCHsXUdKodk0RpIB80cMKaHTxbc7o0JAO50q7OszOmUZAggZq1jTuMYgzRb3 +DvlfLVpMxfEVu7kNbagr2STRIjRZpV/md569lM+L4Kp8wCrOfJgTZExm8txhFYCK +mNF2hCThKfHNfy7NDuY9pMF2ZcI8pig1lWbkVc5BdX7miifeOinnKfvM4XfzQ+OE +NsI8+WHgC+KoYukCgcBwrOpdCmHchOZCbZfl9m1Wwh16QrGqi1BqLnI53EsfGijA +yaftgzs+s7/FpEZC3PCWuw3vPTyhr69YcQQ/b8dNFM8YYJ+4SuMfpUds5Kl5eTPd +dO/+xMQtzus4hOJeiB9h50o8GYH7VGJZVhcjLgQoBGlMgvf+uVSitnvWgCumbORK +hqR7YF+xoov3wToquubcDE2KBdF54h/jnFJEf7I2GilmnHgmpRNoWBbCCmoXdy09 +aMbwEgY+0Y+iBOfRmkUCgcEAoHJLw7VnZQGQQL4l3lnoGU9o06RPkNbpda9G/Ptz +v+K7DXmHiLFVDszvZaPVreohuc0tKdrT0cZpZ21h0GQD4B6JX66R/y6CCAr0QpdA +pFZO9sc5ky6RJ4xZCoCsNJzORNUb36cagEzBWExb7Jz2v6gNa044K5bs3CVv5h15 +rJtTxZNn/gcnIk+gt//67WUnKLS4PR5PVCCqYhSbhFwx/OvVTJmflIBUinAclf2Q +M4mhHOfwBicqYzzEYbOE9Vk9 -----END PRIVATE KEY----- -----BEGIN CERTIFICATE----- -MIIEbTCCAtWgAwIBAgIUF15VKdwjiTzzKgs6PnNpEekV9QQwDQYJKoZIhvcNAQEL +MIIEjDCCAvSgAwIBAgIUQ2S3jJ5nve5k5956sgsrWY3vw9MwDQYJKoZIhvcNAQEL BQAwYjELMAkGA1UEBhMCWFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYD VQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEVMBMGA1UEAwwMZmFrZWhv -c3RuYW1lMB4XDTIxMDMxNzA4NDgyMFoXDTQwMDUxNjA4NDgyMFowYjELMAkGA1UE +c3RuYW1lMB4XDTIzMTEyNTA0MjEzN1oXDTQzMDEyNDA0MjEzN1owYjELMAkGA1UE BhMCWFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYDVQQKDBpQeXRob24g U29mdHdhcmUgRm91bmRhdGlvbjEVMBMGA1UEAwwMZmFrZWhvc3RuYW1lMIIBojAN -BgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAn/BVsYuKFZQ1cXvDQxYG/iGABq/4 -WVrmNWau3e4Oe8j6MSa4LfmzfPflO6k3bqsgdh8vBtE6ngAjqyHaT2fD+1eROWIg -EsHye9p21aO6kfQwCgORRPD1NGyRZhS4ww4oXPe9M3OIpUQ30VspVKY+vJ3nzmOA -sxc3oqlTi19sHs7O2dIX5ZElxgZgtI9RW0FNiN2CYNwigmm4U7VOjsiiLc4ydr1W -jqEPgdCr/JXmkUOgDWQQBUBdi3qUsuRdvqEg/kB4z6/tNdleQ5q7HSHPVnvGMqJ0 -tbv9xyO5S6IX91vqha6EFo59AySOeknFdbhK/wdPoZ9owUUzOXdRfQXlVptbvATi -budArznrsYlS7MSaWuE+NQyc5yZOVfoROISrwyppa8LpbEafKI1BMlIvHc1jgUXw -IoyFr96gD4xgPyd95+vj5ZQnMwtgbFWYVZi6kjAsKdYNiUwjPygu0+LzpfxBpJu+ -n3Ji3gNRH/KFHUYPt0++1T/h6mdiT9OifDvDAgMBAAGjGzAZMBcGA1UdEQQQMA6C -DGZha2Vob3N0bmFtZTANBgkqhkiG9w0BAQsFAAOCAYEARzdkuqa0Hexi/saMkdi3 -bubpQkc7X0RYKWnjy/PgcmbvQXLiWRMZOH9rMWvd5v+ZfkgAtsbOQuP8ycioNIFY -Il5SEmxHEN81z5UNSPLOib6ky13gzrnXRAxnnO7cICG7AaMu1dHv57fqjevcx/n/ -nxPNKwKL+TDpMw7ATVZw7Py7JciKyFAfwtkvt17j/ldvaQvuwmWHzyFVrQniQcQq -QEa4jy/Y/pXHAgCKq1qbe0ush17j1ChyH7l4SkF2xJKcYYQF5ipw8zg6WeOL2NFE -G1KDJN0SsMmM3PMN1e0lLQP3G+UaatervrKXu51QleKL32Xlby+pp1w9KKs39/Tb -RT8EMe9A6cecod6TL0ZUQHow6ykNYBkfSKDLTKWnL9ifZ0C/DvgmS7DpJg3oAa1e -GhIglMrgqJflTHAI/PvEsCKM1O0Un2dVGWsUCzPfhj1cKmagyb0Zd+2Tk9xGSRs9 -2ceXMxRCjOJwEHUCFuTYeqowabdlpi0nyPbSn7JIwCpT +BgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAsgFF43M4FBJ+5pjsAzIv/vsA0cai +QCdn+xfQA8U7jpvhLXaX2K+JgZ/FM/XtyPmYXhuct0GdXXQWAPWE33FC9KJ/FFyz +jgVWQnrcbEF6EgLgzD1dCF3hILWWQ13u0k6ikkHmJOws/lbR3pWfI+6DjnMebCYK +W2g/jhiZz6AHQfJfJ0H8agBuObFuD+oHyyqCS74inLQ3uab4IiQvLb8rYPyIREin +nny2eIsdnTY49hjVGi/pIVOlBLjNZCR3D1Yz6GHPsvH6Kf6oUNIrYQYTtjryfmJj +hXQhOu0sgsFN6fMyaTfl5MGx7bYJjYODkrOx+lexOdvwrEzUwErYQR17camjPr42 +xgEa89haXP5/i8EbezBiEdIFbZ7U4Jz9B6Hh7JIQCHg7Wj62esx3Zd2g8IAHnFXm +0xAU+WD26M/l9ZVnTq0Q9iVt26l0kSDdxOKs+SDLjcd1b4aUkXhWc0ekCG8UKszv +1afdyL8vIT2eljIH0mFrOwhrbuDT+/TyX3TLAgMBAAGjOjA4MBcGA1UdEQQQMA6C +DGZha2Vob3N0bmFtZTAdBgNVHQ4EFgQU5wVOIuQD/Jxmam/97g91+igosWQwDQYJ +KoZIhvcNAQELBQADggGBAFv5gW5x4ET5NXEw6vILlOtwxwplEbU/x6eUVR/AXtEz +jtq9zIk2svX/JIzSLRQnjJmb/nCDCeNcFMkkgIiB64I3yMJT9n50fO4EhSGEaITZ +vYAw0/U6QXw+B1VS1ijNA44X2zvC+aw1q9W+0SKtvnu7l16TQ654ey0Qh9hOF1HS +AZQ46593T9gaZMeexz4CShoBZ80oFOJezfNhyT3FK6tzXNbkVoJDhlLvr/ep81GG +mABUGtKQYYMhuSSp0TDvf7jnXxtQcZI5lQOxZp0fnWUcK4gMVJqFVicwY8NiOhAG +6TlvXYP4COLAvGmqBB+xUhekIS0jVzaMyek+hKK0sT/OE+W/fR5V9YT5QlHFJCf5 +IUIfDCpBZrBpsOTwsUm8eL0krLiBjYf0HgH5oFBc7aF4w1kuUJjlsJ68bzO9mLEF +HXDaOWbe00+7BMMDnyuEyLN8KaAGiN8x0NQRX+nTAjCdPs6E0NftcXtznWBID6tA +j5m7qjsoGurj6TlDsBJb1A== -----END CERTIFICATE----- diff --git a/Lib/test/certdata/keycert3.pem b/Lib/test/certdata/keycert3.pem index f6887ba7a84e1a7..20d9bd14e9678f2 100644 --- a/Lib/test/certdata/keycert3.pem +++ b/Lib/test/certdata/keycert3.pem @@ -1,42 +1,42 @@ -----BEGIN PRIVATE KEY----- -MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQDFtLOteQlQojN7 -ztkux7m0hmGKkP1hh0hbKqTcD87jkLAqAwZWenjZMjCbbZ3vP+AObCIkYIKzPXY7 -Yi+H5M3O2mXIDxoHGjL/GWtoEyDNXvm9UC+MRuSOq2MaLHHQG0Rx2TxcYrMVUM7b -93rpN1LGRrCv1gISXM4EvEJooAR7Aadj0pG/o0fqDAdFjH6QZbhn1iZle+eGbjcf -dgH/H0F8dn1PPGoViHXicbsQ4kB6002Pf+aXP4b2QKAbflyNHEKHPHEOOTXrFjMd -c+bqKW24epEsMZI59qx9hU/4Rvp3/v+vEwTL7Nm7ilptzZn2cvGCW39LC0nNYLOz -kO3H8xwA75h6uykdB+WO/v2CKIK9M/ZO+9QNrmaokfKDamCk39b8hlCwNL6LsVpv -d3XTS5Wn4YWn92EqiltUJJoPo7pc7VTdWCg4zVFn4Q8Zh4NFNn/qTB8lEMgrsNTV -5cyZ7zhoBiUMSO45bmo2NsnE7ce/JUhlqe5uh0PT1MIBgTV+oDMCAwEAAQKCAYEA -udsy4gwblqK0tVnxz0lQqYV+os3EdO/BNHr1Oi7eNg2pngTz603812mYSjUVOHma -vtQmkH3twGQyBoc52Y1dcGzdK+IOfMjDUg7qao840ffL3I1J9ZwbdodlhZBsec94 -W3J1jP/4DDzICf8vm5g3h0+i/9m2Xt7BibAU2dg7/grC+lNUUoxDqaEfIOF/hW0q -muq1c8e0EisAROIh5FzUqhWVnWxU6eM7tuFlkuyu4whLLHB3LI466Lo+CTqT9M+v -jJYlvS5+AZW3qMBp6WOI8C+VIiBL178mo+Igkyyy5AYXcWeNkjp6ygRWvtWXIhCv -CI29mf+BP/54jAY0rQRXJ2UcSHXmM6PTDkE/L2OKeiY1Ou8gLOwun3yBVdbkXJMb -PWmUW4N8qSIJQ+vE2TDqmkqAT6m+ilzOXl1O+LLTvGyMnOiiSLXK9mC4ND3tqaQu -hvKivnI1doErcWUaIf1DHiJmLrGxrTCUKjCEoefqVq2/dDdtCfx7CqUvjl3DYKMB -AoHBAP+Vdi6D07gZFepEGCaJ+YH6cxEyO73CNnea/F1whVAzOv91kHS32jC9PAI3 -/wYlX+DLcN9mVF/q62V4SLZYfOxTPW4vWO0A45URe9s9Z795fdAcQ5jt3QFOVSnk -3XSaCkIOwckuwabGJi4+foiUEOnLLzQi1/g7x12dwejxVNhqhz5KFkOQPv8fQRed -sb5LVLYDeprsB2Vsx0fHwg4z9FvTIxLBeI7+sJD30lNpYZrCl/T9x4e1SV2Rwn2W -bghxgQKBwQDGBx07biZK9RB5g4qPl+G6vz0M+/KBfpwQbMYxSyct7u6gfGD9mWBO -qocIIr39Unac3kUL237Cn3HbgiGCRe7Mwd7XqnSSGWM5oWSlVQxEKTXYUlTbd9O9 -DKuyQGOl/AMEwD4ZbEOfQNmnd1U4nh1AV052FQY8Ry/atGFT9fApA/5X/bbenOwQ -YGDsokLzPf2BIDncpE+VNevUMoMI7EnySgjjfpL+cRld0qpLqBMo2h5VddeJ/5YM -1YcNfMQiw7MCgcEAwXqXuKa7A8aZvHpH/gS9CRRbP01TxFbdfLWrDeE8SnY9111c -Ob9kQTk/0D4rpK9uYXIgxD1m6iWghXQFN2TNTOnGuz7EhsYBgrt1k4Zsn5qND5oV -4hNPFsoB1nEW5EooMdGSCYaHuoSOKrvMdgAAvbu+xC0MaTJ3vfrK7Fik7h/WueTD -7emohuFWGVabU38bZZ5EljrPboxmX4Rs9uuFtG2lQ3GKnlVXvKaeZd6EsO9WsXPc -NHOcUmUhYokaSvIBAoHAGCxGJTsM8Zl4qVylTWH87A7sJOmccLJD2r1sdBf4cGL6 -PhzwugQ+/VtToGqdRo8Ka5u2Ufw5PQi5nVIFRSHERLpluW3VTQBMXHyXDJeVJ7zg -Fcf3E9NMxYcGbnvtrhVVSP8ulWvh1U7VQtwOSxsB9xixOzjVygXmkYvzVYxwBJG4 -OoV+DS6aomUhb8Fe6tJmX5zPc1+bV1t9ril8VVqCrFDdROfuiaDEt+8/Wnzp2dLG -YShBZ1cLugVWtw7D4nqBAoHAF29k64iAxY5Y4OOibVkqjUCPyqG2oxiXqgO7CxZp -FGUat5UtV2mIBlSENs1o5AZ1nPlgWtPtg0xVCaG2t/Rq7ugvUfAnAhUK6zX8FS+T -gCXE+7iKuuIJiCo13/iAwF/CLfuXvj4CZ71ta0wX9w99f1FcPEk0x+ytiyuWJK8K -tyubL34JwNrnkh/8e3LcV3L88Sk9ZmxeTz31f3cA3Fy2ZJOAUMD9dKXeKtY7azzt -MkhXedRsdLSKqMh0VGeGHoLS +MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQCgKihxC+2g7d7M +JfIUBfFWiuMwxg0WhdxPyGUzMAjexbEOHo0ojntxPdH9KYRwiKRKb9jnmzXp2CKT +hqBYJIetq/3LYZp4bvDJ/hVCL9e4jqu1l+wd9DkqhKZ69b6C1/d12JAKvC5TIT+/ +b7EglYU8KMNx2WO5KxIJeVpX68jn49YtUzg0hT0QiXj4eugbDk1L1f99xgvkHaVW +VQxi6MFNWHJq/xXUb8E/hd/Q3oadN1BXMWl9P46D4R+YGKQQdZFkwEJsbqijFvWW +bOoaz7TFxf8n0q616803aXLfaWikfEXLnznEvKo7vyEivtT/y14Nm+SiR3nS6E0y +Dt8gmeHdaHcrmQT+yQ6yNOYDCcfeYM+rBuvOUHPudjMy0k8K/0IPjDo0KActKPH0 +UVbyDBMKDdGQ2+LhRFLsGXHlD9b05PxhqTULe3LeK6KZ+iuGbWtwVLaL5S42WiCA +YXNShE1Ko0Q7wugAippXCf+aWP3Wx9ZTrsfiDBbIfnY5mlfdG90CAwEAAQKCAYAA +ogoE4FoxD5+YyPGa+KcKg4QAVlgI5cCIJC+aMy9lyfw4JRDDv0RnnynsSTS3ySJ1 +FNoTmD5vTSZd1ONfVc2fdxWKrzkQDsgu1C07VLsShKXTEuWg/K0ZKOsLg1scY0Qc +GB4BnNrGA1SgKg3WJiEfqr2S/pvxSGVK2krsHAdwOytGhJStSHWEUjbDLKEsMjNG +AHOBCL5VSXS00aM55NeWuanCGH36l/J4kMvgpHB9wJE1twFGuHCUvtgEHtzPH9fQ +plmI0QDREm6UE6Qh01lxmwx3Xc5ASBURmxs+bxpk94BPRpj8/eF2HPiJalrkJksj +Xk3QQ7k23v6XnmHKV3QqpjUgJTdbuMoTrVMu14cIH6FtXfwVhtthPnCI8rk5Lh8N +cqLC7HT+NE1JyygzuMToOHMmSJTQ8L6BTIaRCZjvGTPYaZfFgeMHvvhAJtP5zAcc +xQzyCyNBU8RdPGT8tJTyDUIRs20poqe7dKrPEIocKJX7tvNSI2QxkQ96Adxo1gEC +gcEAvI8m6QCDGgDWI8yTH9EvZQwq+tF8I+WMC+jbPuDwKg5ZKC7VjRO//9JzPY+c +TxmLnQu64OkECHbu7pswDBbtnPMbodF9inYEY5RkfufEjEMJGEdkoBJWnNx78EkV +bcffWik0wXwdt6jd1CAnjmS9qaPz0T1NV8m5rQQn5JUYXlC9eB2kOojZYLbZBl3g +xUSRbIqHC7h8HuyAU26EPiprHsIxrOpbxABFOdvo2optr50U7X10Eqb4mRQ4z22W +ojJdAoHBANlzJjjEgGVB9W50pJqkTw8wXiTUG8AmhqrVvqEttLPcWpK6QwRkRC+i +5N1iUObf/kOlun2gNfHF6gM68Ja9wb2eGvE5sApq9hPpyYF0LS3g8BbJ9GOs6NU9 +BfM1CkPrDCdc4kzlUpDibvc6Fc9raCqvrZRlKEukqQS8dumVdb74IaPsP6q8sZMz +jibOk0eUrbx2c5vEnd0W8zMeNCuCwO1oXbfenPp/GLX9ZRlolWS/3cQoZYOSQc9J +lFQYkxL3gQKBwQCy3Pwk9AZoqTh4dvtsqArUSImQqRygFIQXXAh1ifxneHrcYijS +jVSIwEHuuIamhe3oyBK6fG8F9IPLtUwLe8hkJDwm8Misiiy5pS77LrFD9+btr/Nk +4GBmpcOveDQqkflt1j6j9y9dY4MhUGsVaLx86fhDmGoAh2tpEtMgwsl91gsUoNGD +cQL6+he+MVkg510nX/Sgipy63M8R1Xj+W1CHueBTTXBE6ZjBPLiSbdOETXZnnaR4 +eQjCdOs64JKOQ0UCgcBZ4kFAYel48aTT/Z801QphCus/afX2nXY5E5Vy5oO1fTZr +RFcDb7bHwhu8bzFl3d0qdUz7NMhXoimzIB/nD5UQHlSgtenQxJnnbVIAEtfCCSL1 +KJG+yfCMhGb7O0d8/6HMe5aHlptkjFS2GOp/DLTIQEoN9yqK6gt7i7PTphY/1C2D +ptpCZzE32a2+2NEEW67dIlFzZ/ihNSVeUfPasHezKtricECPQw4h3BZ4RETMmoq+ +1LvxgPl3B8EqaeYRhwECgcEAjjp/0hu/ukQhiNeR5a9p1ECBFP8qFh6Cpo0Az/DT +1kX0qU8tnT3cYYhwbVGwLxn2HVRdLrbjMj/t88W/LM2IaQ162m7TvvBMxNmr058y +sW/LADp5YWWsY70EJ8AfaTmdQriqKsNiLLpNdgcm1bkwHJ1CNlvEpDs1OOI3cCGi +BEuUmeKxpRhwCaZeaR5tREmbD70My+BMDTDLfrXoKqzl4JrRua4jFTpHeZaFdkkh +gDq3K6+KpVREQFEhyOtIB2kk -----END PRIVATE KEY----- Certificate: Data: @@ -51,34 +51,34 @@ Certificate: Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=localhost Subject Public Key Info: Public Key Algorithm: rsaEncryption - RSA Public-Key: (3072 bit) + Public-Key: (3072 bit) Modulus: - 00:c5:b4:b3:ad:79:09:50:a2:33:7b:ce:d9:2e:c7: - b9:b4:86:61:8a:90:fd:61:87:48:5b:2a:a4:dc:0f: - ce:e3:90:b0:2a:03:06:56:7a:78:d9:32:30:9b:6d: - 9d:ef:3f:e0:0e:6c:22:24:60:82:b3:3d:76:3b:62: - 2f:87:e4:cd:ce:da:65:c8:0f:1a:07:1a:32:ff:19: - 6b:68:13:20:cd:5e:f9:bd:50:2f:8c:46:e4:8e:ab: - 63:1a:2c:71:d0:1b:44:71:d9:3c:5c:62:b3:15:50: - ce:db:f7:7a:e9:37:52:c6:46:b0:af:d6:02:12:5c: - ce:04:bc:42:68:a0:04:7b:01:a7:63:d2:91:bf:a3: - 47:ea:0c:07:45:8c:7e:90:65:b8:67:d6:26:65:7b: - e7:86:6e:37:1f:76:01:ff:1f:41:7c:76:7d:4f:3c: - 6a:15:88:75:e2:71:bb:10:e2:40:7a:d3:4d:8f:7f: - e6:97:3f:86:f6:40:a0:1b:7e:5c:8d:1c:42:87:3c: - 71:0e:39:35:eb:16:33:1d:73:e6:ea:29:6d:b8:7a: - 91:2c:31:92:39:f6:ac:7d:85:4f:f8:46:fa:77:fe: - ff:af:13:04:cb:ec:d9:bb:8a:5a:6d:cd:99:f6:72: - f1:82:5b:7f:4b:0b:49:cd:60:b3:b3:90:ed:c7:f3: - 1c:00:ef:98:7a:bb:29:1d:07:e5:8e:fe:fd:82:28: - 82:bd:33:f6:4e:fb:d4:0d:ae:66:a8:91:f2:83:6a: - 60:a4:df:d6:fc:86:50:b0:34:be:8b:b1:5a:6f:77: - 75:d3:4b:95:a7:e1:85:a7:f7:61:2a:8a:5b:54:24: - 9a:0f:a3:ba:5c:ed:54:dd:58:28:38:cd:51:67:e1: - 0f:19:87:83:45:36:7f:ea:4c:1f:25:10:c8:2b:b0: - d4:d5:e5:cc:99:ef:38:68:06:25:0c:48:ee:39:6e: - 6a:36:36:c9:c4:ed:c7:bf:25:48:65:a9:ee:6e:87: - 43:d3:d4:c2:01:81:35:7e:a0:33 + 00:a0:2a:28:71:0b:ed:a0:ed:de:cc:25:f2:14:05: + f1:56:8a:e3:30:c6:0d:16:85:dc:4f:c8:65:33:30: + 08:de:c5:b1:0e:1e:8d:28:8e:7b:71:3d:d1:fd:29: + 84:70:88:a4:4a:6f:d8:e7:9b:35:e9:d8:22:93:86: + a0:58:24:87:ad:ab:fd:cb:61:9a:78:6e:f0:c9:fe: + 15:42:2f:d7:b8:8e:ab:b5:97:ec:1d:f4:39:2a:84: + a6:7a:f5:be:82:d7:f7:75:d8:90:0a:bc:2e:53:21: + 3f:bf:6f:b1:20:95:85:3c:28:c3:71:d9:63:b9:2b: + 12:09:79:5a:57:eb:c8:e7:e3:d6:2d:53:38:34:85: + 3d:10:89:78:f8:7a:e8:1b:0e:4d:4b:d5:ff:7d:c6: + 0b:e4:1d:a5:56:55:0c:62:e8:c1:4d:58:72:6a:ff: + 15:d4:6f:c1:3f:85:df:d0:de:86:9d:37:50:57:31: + 69:7d:3f:8e:83:e1:1f:98:18:a4:10:75:91:64:c0: + 42:6c:6e:a8:a3:16:f5:96:6c:ea:1a:cf:b4:c5:c5: + ff:27:d2:ae:b5:eb:cd:37:69:72:df:69:68:a4:7c: + 45:cb:9f:39:c4:bc:aa:3b:bf:21:22:be:d4:ff:cb: + 5e:0d:9b:e4:a2:47:79:d2:e8:4d:32:0e:df:20:99: + e1:dd:68:77:2b:99:04:fe:c9:0e:b2:34:e6:03:09: + c7:de:60:cf:ab:06:eb:ce:50:73:ee:76:33:32:d2: + 4f:0a:ff:42:0f:8c:3a:34:28:07:2d:28:f1:f4:51: + 56:f2:0c:13:0a:0d:d1:90:db:e2:e1:44:52:ec:19: + 71:e5:0f:d6:f4:e4:fc:61:a9:35:0b:7b:72:de:2b: + a2:99:fa:2b:86:6d:6b:70:54:b6:8b:e5:2e:36:5a: + 20:80:61:73:52:84:4d:4a:a3:44:3b:c2:e8:00:8a: + 9a:57:09:ff:9a:58:fd:d6:c7:d6:53:ae:c7:e2:0c: + 16:c8:7e:76:39:9a:57:dd:1b:dd Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Alternative Name: @@ -90,75 +90,72 @@ Certificate: X509v3 Basic Constraints: critical CA:FALSE X509v3 Subject Key Identifier: - 85:75:10:25:D0:2C:80:50:24:1A:5B:57:70:DE:B5:CB:71:A9:3B:7B + 3F:B1:E9:4F:A0:BE:30:66:3E:0A:18:C8:0F:47:1A:4F:34:6A:0F:42 X509v3 Authority Key Identifier: - keyid:B3:8A:A0:A2:BA:71:F1:A8:24:79:D4:A4:5B:25:36:15:1E:49:C8:CD + keyid:F3:EC:94:8E:F2:8E:30:C4:8E:68:C2:BF:8E:6A:19:C0:C1:9F:76:65 DirName:/C=XY/O=Python Software Foundation CA/CN=our-ca-server serial:CB:2D:80:99:5A:69:52:5B - Authority Information Access: CA Issuers - URI:http://testca.pythontest.net/testca/pycacert.cer OCSP - URI:http://testca.pythontest.net/testca/ocsp/ - X509v3 CRL Distribution Points: - Full Name: URI:http://testca.pythontest.net/testca/revocation.crl - Signature Algorithm: sha256WithRSAEncryption - 95:f3:56:bb:d5:8c:70:bd:d1:de:da:63:b0:29:d7:db:60:27: - d6:59:fd:61:1b:30:c6:d0:5d:73:7d:34:e1:68:e3:28:a6:89: - e6:60:bd:89:d3:0e:f4:72:ad:72:76:f8:86:21:fd:75:3c:f8: - 6d:be:9c:04:e1:82:03:69:6c:ae:d0:55:ba:5e:f2:ca:f5:0f: - 8e:d6:d9:8d:c8:56:46:f4:f8:ac:74:2a:19:7b:8e:47:70:1f: - fb:fb:bd:69:02:a1:a5:4a:6e:21:1c:04:14:15:55:bf:bf:24: - 43:c8:17:03:be:3e:2c:ea:db:c8:af:1d:fd:52:df:d6:15:49: - 9e:c2:44:69:ef:f1:45:43:83:b2:1e:cf:14:1c:13:3f:fe:9c: - 71:cb:e7:1b:18:56:36:a7:af:44:f1:0b:a1:79:44:46:f9:43: - 46:29:d8:b0:ca:49:4d:65:60:d3:f6:8e:74:bc:62:9e:1e:8d: - 4b:29:9a:b4:0d:f0:a2:77:5b:34:e4:11:2f:a7:25:c5:e5:07: - 76:12:ae:be:75:73:15:e4:0a:7d:53:38:56:3f:79:6d:6e:ca: - ed:80:ab:56:ed:7e:8b:1c:e7:e3:d4:62:30:22:70:e7:29:b2: - 03:3c:fe:fa:3d:f0:36:c0:4d:11:a2:99:d3:29:31:27:b8:c5: - b8:15:a3:3c:4f:9b:73:5e:2b:b2:fb:cb:fd:75:47:b8:17:bd: - 21:d8:e6:c1:b9:ff:73:81:d8:25:08:6d:08:5e:1c:a5:83:50: - de:67:e6:da:d0:8e:5a:d3:f2:2a:b1:3f:b8:80:21:07:6a:71: - 15:6d:05:eb:51:b3:59:8d:d4:15:46:7e:02:a8:13:01:16:99: - bd:03:cc:70:71:2a:23:16:78:af:d1:d5:01:9d:04:b4:63:93: - 9a:04:3a:92:2e:e6:7e:73:93:a5:fe:50:9b:bd:0e:ea:54:86: - 6f:7c:e5:14:77:fe:c2:28:5a:4a:0e:d7:2d:8c:e9:ed:61:29: - b2:53:ff:6c:04:bc + Signature Value: + ca:34:ba:c5:d0:cf:27:31:32:d6:0d:27:30:b8:db:17:df:b7: + 39:a7:bb:b1:3b:86:c4:31:fd:fb:ab:db:63:1a:cc:90:ab:b9: + 4e:ab:34:49:0c:5e:8c:3e:70:a3:a7:6b:2f:a7:9a:25:7b:01: + 5a:18:96:48:76:f8:36:78:74:fa:bc:7d:68:7f:e5:ca:a6:9d: + 7b:dc:72:bd:a3:25:51:17:68:e8:e9:d7:02:86:2c:7d:16:7c: + b5:dc:44:b2:0a:e3:f7:c9:33:a3:51:36:83:bc:d4:70:cd:84: + 91:9f:06:ba:2d:d2:05:0a:65:c3:d9:55:09:a8:b8:09:69:bb: + 93:86:c2:b7:c2:90:74:7c:bf:f0:5d:bc:0e:63:13:8c:eb:fa: + 0f:f1:fa:e5:12:70:4d:0c:eb:8c:2e:a2:42:42:00:04:0f:fc: + f9:1f:41:9c:63:78:f0:66:93:b2:8f:2e:e8:93:1c:50:cb:2d: + 7f:b6:ba:57:6f:52:62:d7:39:0b:09:82:ab:a6:53:4d:cc:05: + 3e:19:f0:d4:c0:ce:a9:ad:10:ce:b9:71:e4:8f:f2:5a:3c:65: + ba:dc:cb:e0:04:90:2b:a5:15:a6:7d:da:dc:a3:b5:b7:bc:a0: + de:30:4e:64:cb:17:0d:3a:a0:52:d2:67:3b:a2:3a:00:d5:39: + aa:61:75:52:9f:fe:9b:c0:e8:a0:69:af:a8:b3:a3:1d:0a:40: + 52:04:e7:3d:c0:00:96:5f:2b:33:06:0c:30:f6:d3:18:72:ee: + 38:d0:64:d3:00:86:37:ec:4f:e9:38:49:e6:01:ff:a2:9a:7c: + dc:6a:d3:cb:a8:ba:58:fb:c3:86:78:47:f1:06:a6:45:e7:53: + de:99:1d:81:e6:bc:63:74:46:7c:70:23:57:29:60:70:9a:cc: + 6f:00:8e:c2:bf:6a:73:7d:6e:b0:62:e6:dc:13:1a:b9:fe:0f: + c2:d1:06:a1:79:62:7f:b6:30:a9:03:d0:47:57:25:db:48:10: + d1:cf:fb:7d:ac:3d -----BEGIN CERTIFICATE----- MIIF8TCCBFmgAwIBAgIJAMstgJlaaVJcMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx NDIzMTZaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMMCWxv -Y2FsaG9zdDCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAMW0s615CVCi -M3vO2S7HubSGYYqQ/WGHSFsqpNwPzuOQsCoDBlZ6eNkyMJttne8/4A5sIiRggrM9 -djtiL4fkzc7aZcgPGgcaMv8Za2gTIM1e+b1QL4xG5I6rYxoscdAbRHHZPFxisxVQ -ztv3euk3UsZGsK/WAhJczgS8QmigBHsBp2PSkb+jR+oMB0WMfpBluGfWJmV754Zu -Nx92Af8fQXx2fU88ahWIdeJxuxDiQHrTTY9/5pc/hvZAoBt+XI0cQoc8cQ45NesW -Mx1z5uopbbh6kSwxkjn2rH2FT/hG+nf+/68TBMvs2buKWm3NmfZy8YJbf0sLSc1g -s7OQ7cfzHADvmHq7KR0H5Y7+/YIogr0z9k771A2uZqiR8oNqYKTf1vyGULA0voux -Wm93ddNLlafhhaf3YSqKW1Qkmg+julztVN1YKDjNUWfhDxmHg0U2f+pMHyUQyCuw -1NXlzJnvOGgGJQxI7jluajY2ycTtx78lSGWp7m6HQ9PUwgGBNX6gMwIDAQABo4IB +Y2FsaG9zdDCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKAqKHEL7aDt +3swl8hQF8VaK4zDGDRaF3E/IZTMwCN7FsQ4ejSiOe3E90f0phHCIpEpv2OebNenY +IpOGoFgkh62r/cthmnhu8Mn+FUIv17iOq7WX7B30OSqEpnr1voLX93XYkAq8LlMh +P79vsSCVhTwow3HZY7krEgl5WlfryOfj1i1TODSFPRCJePh66BsOTUvV/33GC+Qd +pVZVDGLowU1Ycmr/FdRvwT+F39Dehp03UFcxaX0/joPhH5gYpBB1kWTAQmxuqKMW +9ZZs6hrPtMXF/yfSrrXrzTdpct9paKR8RcufOcS8qju/ISK+1P/LXg2b5KJHedLo +TTIO3yCZ4d1odyuZBP7JDrI05gMJx95gz6sG685Qc+52MzLSTwr/Qg+MOjQoBy0o +8fRRVvIMEwoN0ZDb4uFEUuwZceUP1vTk/GGpNQt7ct4ropn6K4Zta3BUtovlLjZa +IIBhc1KETUqjRDvC6ACKmlcJ/5pY/dbH1lOux+IMFsh+djmaV90b3QIDAQABo4IB wDCCAbwwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA4GA1UdDwEB/wQEAwIFoDAdBgNV HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4E -FgQUhXUQJdAsgFAkGltXcN61y3GpO3swfQYDVR0jBHYwdIAUs4qgorpx8agkedSk -WyU2FR5JyM2hUaRPME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29m +FgQUP7HpT6C+MGY+ChjID0caTzRqD0IwfQYDVR0jBHYwdIAU8+yUjvKOMMSOaMK/ +jmoZwMGfdmWhUaRPME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29m dHdhcmUgRm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZlcoIJAMst gJlaaVJbMIGDBggrBgEFBQcBAQR3MHUwPAYIKwYBBQUHMAKGMGh0dHA6Ly90ZXN0 Y2EucHl0aG9udGVzdC5uZXQvdGVzdGNhL3B5Y2FjZXJ0LmNlcjA1BggrBgEFBQcw AYYpaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0Y2Evb2NzcC8wQwYD VR0fBDwwOjA4oDagNIYyaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0 -Y2EvcmV2b2NhdGlvbi5jcmwwDQYJKoZIhvcNAQELBQADggGBAJXzVrvVjHC90d7a -Y7Ap19tgJ9ZZ/WEbMMbQXXN9NOFo4yimieZgvYnTDvRyrXJ2+IYh/XU8+G2+nATh -ggNpbK7QVbpe8sr1D47W2Y3IVkb0+Kx0Khl7jkdwH/v7vWkCoaVKbiEcBBQVVb+/ -JEPIFwO+Pizq28ivHf1S39YVSZ7CRGnv8UVDg7IezxQcEz/+nHHL5xsYVjanr0Tx -C6F5REb5Q0Yp2LDKSU1lYNP2jnS8Yp4ejUspmrQN8KJ3WzTkES+nJcXlB3YSrr51 -cxXkCn1TOFY/eW1uyu2Aq1btfosc5+PUYjAicOcpsgM8/vo98DbATRGimdMpMSe4 -xbgVozxPm3NeK7L7y/11R7gXvSHY5sG5/3OB2CUIbQheHKWDUN5n5trQjlrT8iqx -P7iAIQdqcRVtBetRs1mN1BVGfgKoEwEWmb0DzHBxKiMWeK/R1QGdBLRjk5oEOpIu -5n5zk6X+UJu9DupUhm985RR3/sIoWkoO1y2M6e1hKbJT/2wEvA== +Y2EvcmV2b2NhdGlvbi5jcmwwDQYJKoZIhvcNAQELBQADggGBAMo0usXQzycxMtYN +JzC42xfftzmnu7E7hsQx/fur22MazJCruU6rNEkMXow+cKOnay+nmiV7AVoYlkh2 ++DZ4dPq8fWh/5cqmnXvccr2jJVEXaOjp1wKGLH0WfLXcRLIK4/fJM6NRNoO81HDN +hJGfBrot0gUKZcPZVQmouAlpu5OGwrfCkHR8v/BdvA5jE4zr+g/x+uUScE0M64wu +okJCAAQP/PkfQZxjePBmk7KPLuiTHFDLLX+2uldvUmLXOQsJgqumU03MBT4Z8NTA +zqmtEM65ceSP8lo8Zbrcy+AEkCulFaZ92tyjtbe8oN4wTmTLFw06oFLSZzuiOgDV +OaphdVKf/pvA6KBpr6izox0KQFIE5z3AAJZfKzMGDDD20xhy7jjQZNMAhjfsT+k4 +SeYB/6KafNxq08uoulj7w4Z4R/EGpkXnU96ZHYHmvGN0RnxwI1cpYHCazG8AjsK/ +anN9brBi5twTGrn+D8LRBqF5Yn+2MKkD0EdXJdtIENHP+32sPQ== -----END CERTIFICATE----- diff --git a/Lib/test/certdata/keycert4.pem b/Lib/test/certdata/keycert4.pem index 1003d67fd075ec5..ff4dceac7907c57 100644 --- a/Lib/test/certdata/keycert4.pem +++ b/Lib/test/certdata/keycert4.pem @@ -1,42 +1,42 @@ -----BEGIN PRIVATE KEY----- -MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQC34y3S6iXdmdvd -M/2aFBe6CvRvZwhh1huGl7IQRtdoakPqMLlEdNHJtNeF5M27xLei+p4wt7N1Jyi0 -2keHQb1m9TqH5AruOkE2ti+15zEoKoU9aWydTiH+epKTT0yjg2NcKQjRUaWcbhzB -H4EMKuCIlzIIz8/EIKkOqhCDwq6+Fv3Ays+z7Bz+yR80ixivKu/l7SjxQ7z7R/kC -I7OViRcIO5QBQPj7VLvCTz4VA6u/LdXngK2HNuau6WXm5yNNQbqrB11AEJcYZf/c -VrneV4F+ZjLloAKgSn9GB8eWOyilTQ18TcKd+H2icipRaP/+QR/KPx5GK/SXU3my -qm62QOGI7t/5ktVdjGhs6tHZxw1SRiipiLYWbtVRrSxa4wYlgpgoUwvrvvtC5kAN -nTw1VGWsxcs+6a7+PocYnJiq7k4b5OAUb3Ryvl9DLAMy8NqpRWo4cHD/XQ3FCYwF -HlOSgx/dL5Se0i3dW1KzbP6OvaNg6nl/1EXPUsJ1ATS8nzvzhccCAwEAAQKCAYEA -nD3GvaJ9MeB802JNZBEWZ9jO/6jHknldQeq6POI0PF+t/NoRUH0BkyS4yucxdw0a -CrxulG5BaJUxHRkqFV5iE4zhgnzcXLXamyYJO8GIHtyiASAGTVIJyDNVPxztvTDx -x2iGOXPqBxP4Eo82EqSLywLMXHhVzAsEGZWeGpXb61+Vk62+9Nz1dfZlMTvOaWdO -Fkp/sx8e/1KT3KGBANlOXIxioP4Xj1Tbg6nY0fogf3vud5j52B1pu8xL7PkPIaFq -DEGz3XvWhBF/+Cs5iDeYz8eQpfQig7HdHVn2D8dZmzQgpLw1yGbPAnqrgopWfm7R -MqiyFe82p2t+vfSoG5jz28XxPtzBJV3ljxKxlbnclqu/CAYSjzaYohDzyhjdZOZI -r9DOfWOqu01Ha3EEsApn95fusHHGTH2FOy0u61FSTrfLfqsLw9WRJPWleirKikhf -SZzi223QrmzZMtuCF7VgTx3ghDhBmFD8uzVVQ1SwPZ8CgftRkFcn1llXIAfJ3iHB -AoHBAOg3DOIdtUVgpjMKhpAyuH54fYvGl7afIMNbKRle0kCiP45wtGJ43RPMqiR8 -1rxZB3+iapICI/lnhk3O7vVRkR64yiqQBcl/hXZ1BhyD6iDXWYmm5mcnymcoqfwc -p9TfzEPyGPb3SM2YlI0cSPRqM/jDvGvnDeKIpzEKvUlwJ59WoN2HOHTIXf+XbN5n -unpuTt6YKJvc48DrXsPnUzkCmUfbOmgHfeb9/qBs/8kY4YJMsZEjqf88o7mCJCIy -BtDxTwKBwQDKuOwE8e0GIA01ZHd6RfR+ZCvmp2oauxal4EJsBx+ZZnhEWGaSm1fE -Bf/ih074ghcSKoSrdYpD1xGZ6fGVWMx3jcL11yLDOUiiPDJsm8hUBZ0IW1qXyfCP -l7xy1bUkWwPXdmFuGp1exrcjooKrFNuTdYiK4nQZSKuCfXQRADrmEJmM+gYwhqI7 -4XsYo848B9A4hbY6RLEox4uvo/RmafY0iR0PMhVEc+ydNLKB/4LpahZqBQ4kTpMv -o4+rEvYt1gkCgcB08gx177ozx1nMCLf99N0/LBUmCIytNvR8DfPjyAIg9NUHOjFO -CkpkR0VEfO50Cm4hVD1RbOyLFRzpIJbtSvfHvg5qYv/XG3auUn8Sa0jE408/aKNO -PhbL3wnEYvYO2ep4KXtzHNQ4XmgprJ39IWMtG/5PZRx0ApgYtazgSDBcKXd4OTow -bhwQtUTpuNmMAPONXJnO7O5yYNbn2B7sbiedrYV7kJJSe4X5awtiTjp7sX4XdxuM -5BAcQ7NI2WLfZTcCgcBp/X9hIoATmMRvKwUQx+yJ/KO7Z8KhETpJJdR0mNDbqmit -Cy8t7cxYb+6WqLoQUivv0o0k/EJ7L8JDH76woAnfZB4P3RiOy69/K0wN3vFBhOHS -kbju7aU53lKoE7YuuOtsRrewEng/KlRsbDY3bqNTGLt4KegbpBQQGLmLffxNd1Zh -EAQWcP33ou9yNYrJdihWtQpOssWRlash/O32ceZJF3s7C6t068tFclz2fPocQdxQ -OC5pqy9nU/P0tOhDlMkCgcEAosaBJLIeAYlOU0+2uSx5g5mIqOOTyrDEmqqad6T/ -wkB7vW2QaoDvLL22Yrzdn9vQ0V0rqzhVtan7sq5pn/BQJAueZYN8rFxS3uuW+UQk -Nsc4GLJzU8Az/2DvqEIrnE7zRc5E1FOI9gKLrBlpJB2o0hVcBznDe05Gax6Kjqbm -jHqzyU73SpxpEy3OesClCeCQIMr47HaL9aSqaEX4U9bMpgHi0HgTTHqvJ5pch0hY -dYl+WAE9LAyF1DF29BirEXVw +MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQDGKA1zZDjeNPh2 +J9WHVXXMUf8h5N4/bHCM3CbIaZ1dShkCgfmFWmOtruEihgbfRYaSWZAwCmVAQGjm +gvUfgOIgsFfM8yO+zDByPhza7XvWPZfEe7mNRFe5ZlYntbeM/vuWCM4VzwDq/mqF +TFxNRmwInqE7hx0WnfCoQWe9N41hJyl1K0OjADb+SjlpJ0/UJ63hsB+dowGjaaBv +J8HduQcRqNg8s6FcyJJ8Mjss1uRMFK2j9QrmgbA61XuIPCxzc3J57mW8FN2KsR8D +2HOhe9nsTGlxp+O5Cudf/RBWB443xcoyduwRXOFTdEAU45MS4tKGP2hzezuxMFQn +LKARXVW4/gFxZk7kU8TweZUS6LAYPfYJnlfteb6z37LAbtoDvzKUKBEDf/nmoa7C +uKxSPC5HIKhLbjU/6kuPglSVEfJPJWu2bZJDAkFL85Ot3gPs10EX2lMUy0Jt3tf+ +TaQjEvFZhpKN8KAdYj3eVgOfzIBbQyjotHJjFe9Jkq4q7RoI+ncCAwEAAQKCAYAH +tRsdRh1Z7JmHOasy+tPDsvhVuWLHMaYlScvAYhJh/W65YSKd56+zFKINlX3fYcp5 +Fz67Yy+uWahXVE2QgFou3KX0u+9ucRiLFXfYheWL3xSMXJgRee0LI/T7tRe7uAHu +CnoURqKCulIqzLOO1efx1eKasXmVuhEtmjhVpcmDGv8SChSKTIjzgOjqT7QGE9Xq +eSRhq7mulpq9zWq+/369yG+0SvPs60vTxNovDIaBn/RHSW5FjeDss5QnmYMh/ukN +dggoKllQlkTzHSxHmKrIJuryZC+bsqvEPUFXN0NMUYcZRvt1lwdjzq/A+w4gDDZG +7QqAzYMYQZMw9PJeHqu4mxfUX5hJWuAwG5I2eV3kBRheoFw7MxP0tw40fPlFU+Zh +pRXbKwhMAlIHi0D8NyMn3hkVPyToWVVY3vHRknBB/52RqRq3MjqEFaAZfp0nFkiF +ytv3Dd5aeBb1vraOIREyhxIxE/qY8CtZC+6JI8CpufLmFXB412WPwl0OrVpWYfEC +gcEA486zOI46xRDgDw0jqTpOFHzh+3VZ8UoPoiqCjKzJGnrh2EeLvTsXX/GZOj0m +5zl6RHEGFjm5vKCh2C72Vj/m+AFVy7V9iJRzTYzP8So/3paaqo7ZaROTa6uStxdD +VPnY1uIgVQz9w5coN4dmr+RLBpFvvWaHp1wuC08YIWxcC9HSTQpbi1EP5eo08fOk +8reNkDEHxihDGHr1xW0z0qJqK1IVyLP7wDkmapudMZjkjqJSGJwwefV4qyGMTV2b +suW1AoHBAN6t9n6LBH553MF5iUrNJYxXh/SCom4Zft9aD6W4bZV/xL4XPpKBB4HX +aWdeI0iYZU9U+CZ88tBoQCt+JMrJ9cz03ENOvA/MBMREwbZ2hKmQgnoDZsV0vNry +6UsxeQmeNpGQFUz9foVJQVRdQCceN2YEABdehV1HZoSBbuGZkzqGJXrWwaf/ZhpB +dPYGUGOsczoD2/QLuWy2M7f7v0Ews6Heww3zipWzvdxKE0IpyVs30ZwVi8CRQiWU +bEcleXP6+wKBwAi3xEwJxV39Q1XQHuk+/fXywYMp/oMpXmfKUKypgBivUy0/r61S +MZbOXBrKdE6s+GzeFmmLU/xP+WGYinzKfUBIbMwa6e7sH218UgjcoQ0Xnlugk9ld +kmqwajDvhvgdh5rRlIMsuBlgE33shJV+mxBpSGlrHw3cjTaJlFbTGsKpCO9B0jcG +pyEZUWVg+ZMASz6VYcLHj6nEKtufTjhlVsLJpWPE34F/rmSuB9n6C+UZeSLP91rz +dea2pfPf/TFfcQKBwF4DSj9Qx/vxzS7t9fXbuM+QoPitMpCTOQppRpPr0nA8uj6b +J7LIwPejj3+xsenTVWpx8DanqAgvC3CRWE05iQoYEupj0mhE9Xo7oSE81nOUbFHB +H+GbkKRLzA0P/Q7/egBouWWA3Kq/K9LHb+9UBYWPiM5U/K9OFs04rCyZHxylSCud +gbNA08Wf/xZjwgri4t8KhBF75bQtFJbHtY57Vkuv9d/tA4SCl1Tq/UiAxd86KMfi +HNeXPDsLd89t1eIOgwKBwQDJkqwZXkhwkhoNuHRdzPO/1f5FyKpQxFs+x+OBulzG +zuwVKIawsLlUR4TBtF7PChOSZSH50VZaBI5kVti79kEtfNjfAzg4kleHrY8jQ/eq +HludZ3nmiPqqlbH4MH8NWczPEjee6z4ODROsAe31pz3S8YQK7KVoEuSf0+usJ894 +FtzS5wl6POAXTo2QeSNg9zTbb6JjVYcq6KCTnflDm4YEvFKI+ARqAXQHxm05wEOe +DbKC6hxxQbDaNOvXEAda8wU= -----END PRIVATE KEY----- Certificate: Data: @@ -51,34 +51,34 @@ Certificate: Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=fakehostname Subject Public Key Info: Public Key Algorithm: rsaEncryption - RSA Public-Key: (3072 bit) + Public-Key: (3072 bit) Modulus: - 00:b7:e3:2d:d2:ea:25:dd:99:db:dd:33:fd:9a:14: - 17:ba:0a:f4:6f:67:08:61:d6:1b:86:97:b2:10:46: - d7:68:6a:43:ea:30:b9:44:74:d1:c9:b4:d7:85:e4: - cd:bb:c4:b7:a2:fa:9e:30:b7:b3:75:27:28:b4:da: - 47:87:41:bd:66:f5:3a:87:e4:0a:ee:3a:41:36:b6: - 2f:b5:e7:31:28:2a:85:3d:69:6c:9d:4e:21:fe:7a: - 92:93:4f:4c:a3:83:63:5c:29:08:d1:51:a5:9c:6e: - 1c:c1:1f:81:0c:2a:e0:88:97:32:08:cf:cf:c4:20: - a9:0e:aa:10:83:c2:ae:be:16:fd:c0:ca:cf:b3:ec: - 1c:fe:c9:1f:34:8b:18:af:2a:ef:e5:ed:28:f1:43: - bc:fb:47:f9:02:23:b3:95:89:17:08:3b:94:01:40: - f8:fb:54:bb:c2:4f:3e:15:03:ab:bf:2d:d5:e7:80: - ad:87:36:e6:ae:e9:65:e6:e7:23:4d:41:ba:ab:07: - 5d:40:10:97:18:65:ff:dc:56:b9:de:57:81:7e:66: - 32:e5:a0:02:a0:4a:7f:46:07:c7:96:3b:28:a5:4d: - 0d:7c:4d:c2:9d:f8:7d:a2:72:2a:51:68:ff:fe:41: - 1f:ca:3f:1e:46:2b:f4:97:53:79:b2:aa:6e:b6:40: - e1:88:ee:df:f9:92:d5:5d:8c:68:6c:ea:d1:d9:c7: - 0d:52:46:28:a9:88:b6:16:6e:d5:51:ad:2c:5a:e3: - 06:25:82:98:28:53:0b:eb:be:fb:42:e6:40:0d:9d: - 3c:35:54:65:ac:c5:cb:3e:e9:ae:fe:3e:87:18:9c: - 98:aa:ee:4e:1b:e4:e0:14:6f:74:72:be:5f:43:2c: - 03:32:f0:da:a9:45:6a:38:70:70:ff:5d:0d:c5:09: - 8c:05:1e:53:92:83:1f:dd:2f:94:9e:d2:2d:dd:5b: - 52:b3:6c:fe:8e:bd:a3:60:ea:79:7f:d4:45:cf:52: - c2:75:01:34:bc:9f:3b:f3:85:c7 + 00:c6:28:0d:73:64:38:de:34:f8:76:27:d5:87:55: + 75:cc:51:ff:21:e4:de:3f:6c:70:8c:dc:26:c8:69: + 9d:5d:4a:19:02:81:f9:85:5a:63:ad:ae:e1:22:86: + 06:df:45:86:92:59:90:30:0a:65:40:40:68:e6:82: + f5:1f:80:e2:20:b0:57:cc:f3:23:be:cc:30:72:3e: + 1c:da:ed:7b:d6:3d:97:c4:7b:b9:8d:44:57:b9:66: + 56:27:b5:b7:8c:fe:fb:96:08:ce:15:cf:00:ea:fe: + 6a:85:4c:5c:4d:46:6c:08:9e:a1:3b:87:1d:16:9d: + f0:a8:41:67:bd:37:8d:61:27:29:75:2b:43:a3:00: + 36:fe:4a:39:69:27:4f:d4:27:ad:e1:b0:1f:9d:a3: + 01:a3:69:a0:6f:27:c1:dd:b9:07:11:a8:d8:3c:b3: + a1:5c:c8:92:7c:32:3b:2c:d6:e4:4c:14:ad:a3:f5: + 0a:e6:81:b0:3a:d5:7b:88:3c:2c:73:73:72:79:ee: + 65:bc:14:dd:8a:b1:1f:03:d8:73:a1:7b:d9:ec:4c: + 69:71:a7:e3:b9:0a:e7:5f:fd:10:56:07:8e:37:c5: + ca:32:76:ec:11:5c:e1:53:74:40:14:e3:93:12:e2: + d2:86:3f:68:73:7b:3b:b1:30:54:27:2c:a0:11:5d: + 55:b8:fe:01:71:66:4e:e4:53:c4:f0:79:95:12:e8: + b0:18:3d:f6:09:9e:57:ed:79:be:b3:df:b2:c0:6e: + da:03:bf:32:94:28:11:03:7f:f9:e6:a1:ae:c2:b8: + ac:52:3c:2e:47:20:a8:4b:6e:35:3f:ea:4b:8f:82: + 54:95:11:f2:4f:25:6b:b6:6d:92:43:02:41:4b:f3: + 93:ad:de:03:ec:d7:41:17:da:53:14:cb:42:6d:de: + d7:fe:4d:a4:23:12:f1:59:86:92:8d:f0:a0:1d:62: + 3d:de:56:03:9f:cc:80:5b:43:28:e8:b4:72:63:15: + ef:49:92:ae:2a:ed:1a:08:fa:77 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Alternative Name: @@ -90,75 +90,72 @@ Certificate: X509v3 Basic Constraints: critical CA:FALSE X509v3 Subject Key Identifier: - C8:BD:A8:B4:C0:F2:32:10:73:47:9C:48:81:32:F8:BA:BB:26:84:97 + 1C:70:14:B0:20:DD:08:76:A4:3B:56:59:FA:5F:34:F8:36:66:E8:56 X509v3 Authority Key Identifier: - keyid:B3:8A:A0:A2:BA:71:F1:A8:24:79:D4:A4:5B:25:36:15:1E:49:C8:CD + keyid:F3:EC:94:8E:F2:8E:30:C4:8E:68:C2:BF:8E:6A:19:C0:C1:9F:76:65 DirName:/C=XY/O=Python Software Foundation CA/CN=our-ca-server serial:CB:2D:80:99:5A:69:52:5B - Authority Information Access: CA Issuers - URI:http://testca.pythontest.net/testca/pycacert.cer OCSP - URI:http://testca.pythontest.net/testca/ocsp/ - X509v3 CRL Distribution Points: - Full Name: URI:http://testca.pythontest.net/testca/revocation.crl - Signature Algorithm: sha256WithRSAEncryption - 76:87:76:4d:e4:0f:88:bf:2c:f3:58:67:c0:97:6c:cd:59:18: - 82:83:4c:04:19:a5:6d:aa:fa:64:3d:49:32:3e:e1:56:95:b2: - 13:f7:cf:d3:11:b0:72:b7:5b:e7:d7:85:69:51:3c:b6:54:80: - 45:2f:28:10:21:20:b9:ba:e9:27:5a:b7:3f:82:b7:69:f5:46: - f5:bf:a2:8b:17:7f:f2:14:d1:46:97:b5:8b:47:fb:9f:e8:5c: - 05:0e:9d:11:bd:7c:9a:03:84:0b:ca:29:66:4a:ca:0d:6f:09: - 1e:7a:27:c1:7f:03:96:70:8d:18:a5:2f:a4:98:a5:19:aa:8c: - 5d:1e:8c:3e:bb:6d:3b:c0:33:c0:15:e1:bd:09:3d:9f:e8:dc: - 12:d4:cb:44:1d:06:f5:e8:d6:4e:a1:2d:5c:9f:5d:1f:5b:2a: - c3:4d:40:8d:da:d1:78:80:d0:c6:31:72:10:48:8a:e9:10:7a: - 13:30:11:b2:9e:67:0e:ed:a1:aa:ec:73:2d:f0:b8:8a:22:75: - 0f:30:69:5c:50:7e:91:ce:da:91:c7:70:8c:65:ff:f6:58:fb: - 00:bd:45:cc:e2:e4:e3:e5:16:36:7d:f3:a2:4a:9c:45:ff:d9: - a5:16:e0:2f:b5:5b:6c:e6:8a:13:15:48:73:bd:7c:80:33:c3: - d4:3b:3a:1d:85:0e:a4:f7:f7:fb:48:0c:e9:a0:4b:5e:8a:5c: - 67:f8:25:02:6f:cd:72:c1:aa:5a:93:64:7c:14:20:43:e0:13: - 7f:0d:e1:0d:61:5e:2e:2c:cd:7a:2e:2a:ae:b6:75:6a:5f:a0: - 1a:9b:b6:67:2d:b0:a5:1c:54:bc:8c:70:7e:15:2b:c0:50:e3: - 03:bb:a4:a5:fc:45:01:c9:3f:a7:b8:18:dc:3e:08:07:a1:9b: - f5:bd:95:bd:49:e8:10:7c:91:7d:2d:c4:c2:98:b6:b7:51:69: - d7:0a:68:40:b5:0f:85:a0:a9:67:77:c6:68:cb:0e:58:34:b3: - 58:e7:c8:7c:09:67 + Signature Value: + 75:14:e5:68:45:8d:ed:6c:f1:27:1e:0e:f3:35:ae:0e:60:c1: + 65:36:62:b8:07:78:e1:b9:8d:7a:50:70:af:06:c9:d4:ee:50: + ef:d2:76:b2:a2:b6:cb:dc:a6:18:b5:3d:d2:f7:eb:0e:ec:b7: + 95:cd:2e:b1:36:6f:a8:9f:b8:4d:ff:ce:8a:c4:8e:62:37:32: + 80:3e:05:4a:4d:39:87:69:09:00:e8:40:64:d2:9d:f9:1f:9f: + ab:67:1f:f9:c6:84:ba:7e:17:6c:8b:8d:08:ee:fb:8a:d7:cd: + 06:25:72:9f:4e:1a:c2:71:e1:1b:cf:a2:d7:1c:05:12:95:d6: + 49:4b:e9:95:95:89:cf:68:18:46:a3:ea:0d:9d:8e:ca:1c:28: + 55:49:6b:c0:4b:58:f5:42:b9:0a:ec:0e:6e:21:a4:ff:60:c0: + 1b:6e:40:72:d0:a5:c5:b5:db:4e:87:67:3a:31:70:cb:32:84: + 70:a9:e2:ff:e0:f2:db:cd:03:b4:85:45:d3:07:cc:0f:c7:49: + d8:c2:17:eb:73:f7:4a:c0:d9:8c:59:ef:c0:0a:ce:13:0b:84: + c9:aa:0d:11:14:b4:e5:74:aa:ec:18:de:5f:26:18:98:4a:76: + f0:7f:cd:e6:c4:b5:58:03:03:f5:10:01:5d:8f:63:88:ba:65: + d7:b4:7f:5a:1a:51:0e:ed:e5:68:fa:18:03:72:15:a1:ec:27: + 1f:ea:ac:24:46:18:6e:f1:97:db:4a:f4:d6:a1:91:a0:8c:b0: + 2f:be:87:3b:44:b0:8d:2a:89:85:5f:f2:d9:e3:2e:66:b2:88: + 98:04:2c:96:32:38:99:19:a9:83:fd:94:0c:dd:63:d4:1b:60: + 9d:43:98:35:ac:b4:23:38:de:7f:85:52:57:a0:37:df:a5:cf: + be:54:2c:3c:50:27:2b:d4:54:a9:9d:a3:d4:a5:b3:c0:ea:3d: + 0e:e2:70:6b:fb:cb:a5:56:05:ec:64:72:f0:1a:db:64:01:cb: + 5d:27:c4:a1:c4:63 -----BEGIN CERTIFICATE----- MIIF9zCCBF+gAwIBAgIJAMstgJlaaVJdMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx NDIzMTZaMGIxCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xFTATBgNVBAMMDGZh -a2Vob3N0bmFtZTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBALfjLdLq -Jd2Z290z/ZoUF7oK9G9nCGHWG4aXshBG12hqQ+owuUR00cm014XkzbvEt6L6njC3 -s3UnKLTaR4dBvWb1OofkCu46QTa2L7XnMSgqhT1pbJ1OIf56kpNPTKODY1wpCNFR -pZxuHMEfgQwq4IiXMgjPz8QgqQ6qEIPCrr4W/cDKz7PsHP7JHzSLGK8q7+XtKPFD -vPtH+QIjs5WJFwg7lAFA+PtUu8JPPhUDq78t1eeArYc25q7pZebnI01BuqsHXUAQ -lxhl/9xWud5XgX5mMuWgAqBKf0YHx5Y7KKVNDXxNwp34faJyKlFo//5BH8o/HkYr -9JdTebKqbrZA4Yju3/mS1V2MaGzq0dnHDVJGKKmIthZu1VGtLFrjBiWCmChTC+u+ -+0LmQA2dPDVUZazFyz7prv4+hxicmKruThvk4BRvdHK+X0MsAzLw2qlFajhwcP9d -DcUJjAUeU5KDH90vlJ7SLd1bUrNs/o69o2DqeX/URc9SwnUBNLyfO/OFxwIDAQAB +a2Vob3N0bmFtZTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAMYoDXNk +ON40+HYn1YdVdcxR/yHk3j9scIzcJshpnV1KGQKB+YVaY62u4SKGBt9FhpJZkDAK +ZUBAaOaC9R+A4iCwV8zzI77MMHI+HNrte9Y9l8R7uY1EV7lmVie1t4z++5YIzhXP +AOr+aoVMXE1GbAieoTuHHRad8KhBZ703jWEnKXUrQ6MANv5KOWknT9QnreGwH52j +AaNpoG8nwd25BxGo2DyzoVzIknwyOyzW5EwUraP1CuaBsDrVe4g8LHNzcnnuZbwU +3YqxHwPYc6F72exMaXGn47kK51/9EFYHjjfFyjJ27BFc4VN0QBTjkxLi0oY/aHN7 +O7EwVCcsoBFdVbj+AXFmTuRTxPB5lRLosBg99gmeV+15vrPfssBu2gO/MpQoEQN/ ++eahrsK4rFI8LkcgqEtuNT/qS4+CVJUR8k8la7ZtkkMCQUvzk63eA+zXQRfaUxTL +Qm3e1/5NpCMS8VmGko3woB1iPd5WA5/MgFtDKOi0cmMV70mSrirtGgj6dwIDAQAB o4IBwzCCAb8wFwYDVR0RBBAwDoIMZmFrZWhvc3RuYW1lMA4GA1UdDwEB/wQEAwIF oDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAd -BgNVHQ4EFgQUyL2otMDyMhBzR5xIgTL4ursmhJcwfQYDVR0jBHYwdIAUs4qgorpx -8agkedSkWyU2FR5JyM2hUaRPME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRo +BgNVHQ4EFgQUHHAUsCDdCHakO1ZZ+l80+DZm6FYwfQYDVR0jBHYwdIAU8+yUjvKO +MMSOaMK/jmoZwMGfdmWhUaRPME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRo b24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZl coIJAMstgJlaaVJbMIGDBggrBgEFBQcBAQR3MHUwPAYIKwYBBQUHMAKGMGh0dHA6 Ly90ZXN0Y2EucHl0aG9udGVzdC5uZXQvdGVzdGNhL3B5Y2FjZXJ0LmNlcjA1Bggr BgEFBQcwAYYpaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0Y2Evb2Nz cC8wQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5l -dC90ZXN0Y2EvcmV2b2NhdGlvbi5jcmwwDQYJKoZIhvcNAQELBQADggGBAHaHdk3k -D4i/LPNYZ8CXbM1ZGIKDTAQZpW2q+mQ9STI+4VaVshP3z9MRsHK3W+fXhWlRPLZU -gEUvKBAhILm66Sdatz+Ct2n1RvW/oosXf/IU0UaXtYtH+5/oXAUOnRG9fJoDhAvK -KWZKyg1vCR56J8F/A5ZwjRilL6SYpRmqjF0ejD67bTvAM8AV4b0JPZ/o3BLUy0Qd -BvXo1k6hLVyfXR9bKsNNQI3a0XiA0MYxchBIiukQehMwEbKeZw7toarscy3wuIoi -dQ8waVxQfpHO2pHHcIxl//ZY+wC9Rczi5OPlFjZ986JKnEX/2aUW4C+1W2zmihMV -SHO9fIAzw9Q7Oh2FDqT39/tIDOmgS16KXGf4JQJvzXLBqlqTZHwUIEPgE38N4Q1h -Xi4szXouKq62dWpfoBqbtmctsKUcVLyMcH4VK8BQ4wO7pKX8RQHJP6e4GNw+CAeh -m/W9lb1J6BB8kX0txMKYtrdRadcKaEC1D4WgqWd3xmjLDlg0s1jnyHwJZw== +dC90ZXN0Y2EvcmV2b2NhdGlvbi5jcmwwDQYJKoZIhvcNAQELBQADggGBAHUU5WhF +je1s8SceDvM1rg5gwWU2YrgHeOG5jXpQcK8GydTuUO/SdrKitsvcphi1PdL36w7s +t5XNLrE2b6ifuE3/zorEjmI3MoA+BUpNOYdpCQDoQGTSnfkfn6tnH/nGhLp+F2yL +jQju+4rXzQYlcp9OGsJx4RvPotccBRKV1klL6ZWVic9oGEaj6g2djsocKFVJa8BL +WPVCuQrsDm4hpP9gwBtuQHLQpcW1206HZzoxcMsyhHCp4v/g8tvNA7SFRdMHzA/H +SdjCF+tz90rA2YxZ78AKzhMLhMmqDREUtOV0quwY3l8mGJhKdvB/zebEtVgDA/UQ +AV2PY4i6Zde0f1oaUQ7t5Wj6GANyFaHsJx/qrCRGGG7xl9tK9NahkaCMsC++hztE +sI0qiYVf8tnjLmayiJgELJYyOJkZqYP9lAzdY9QbYJ1DmDWstCM43n+FUlegN9+l +z75ULDxQJyvUVKmdo9Sls8DqPQ7icGv7y6VWBexkcvAa22QBy10nxKHEYw== -----END CERTIFICATE----- diff --git a/Lib/test/certdata/keycertecc.pem b/Lib/test/certdata/keycertecc.pem index 81daa4ccb94217b..bd1099218980440 100644 --- a/Lib/test/certdata/keycertecc.pem +++ b/Lib/test/certdata/keycertecc.pem @@ -1,8 +1,8 @@ -----BEGIN PRIVATE KEY----- -MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBcNwE+cm17mmr7Yg6d -0DNCnheGFOjkYH4tYzTyCkcZGShkmF/tKhIqb3imKz0Kx9+hZANiAATyp8ws6CuN -OI2/3MC4jZVSkmoDzm/X/ZrkEm4TVHKPSZ6kzZRpmmUlLS9l7SQZSLYyDAFBFzoG -JJYHhZNQXEO7HFszn6KnvLjhwS6ddzlaHPziEknrSr0OKhJmdJHrQAQ= +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDRUbCeT3hMph4Y/ahL +1sy9Qfy4DYotuAP06UetzG6syv+EoQ02kX3xvazqwiJDrEyhZANiAAQef97STEPn +4Nk6C153VEx24MNkJUcmLe771u6lr3Q8Em3J/YPaA1i9Ys7KZA3WvoKBPoWaaikn +4yLQbd/6YE6AAjMuaThlR1/cqH5QnmS3DXHUjmxnLjWy/dZl0CJG1qo= -----END PRIVATE KEY----- Certificate: Data: @@ -19,13 +19,13 @@ Certificate: Public Key Algorithm: id-ecPublicKey Public-Key: (384 bit) pub: - 04:f2:a7:cc:2c:e8:2b:8d:38:8d:bf:dc:c0:b8:8d: - 95:52:92:6a:03:ce:6f:d7:fd:9a:e4:12:6e:13:54: - 72:8f:49:9e:a4:cd:94:69:9a:65:25:2d:2f:65:ed: - 24:19:48:b6:32:0c:01:41:17:3a:06:24:96:07:85: - 93:50:5c:43:bb:1c:5b:33:9f:a2:a7:bc:b8:e1:c1: - 2e:9d:77:39:5a:1c:fc:e2:12:49:eb:4a:bd:0e:2a: - 12:66:74:91:eb:40:04 + 04:1e:7f:de:d2:4c:43:e7:e0:d9:3a:0b:5e:77:54: + 4c:76:e0:c3:64:25:47:26:2d:ee:fb:d6:ee:a5:af: + 74:3c:12:6d:c9:fd:83:da:03:58:bd:62:ce:ca:64: + 0d:d6:be:82:81:3e:85:9a:6a:29:27:e3:22:d0:6d: + df:fa:60:4e:80:02:33:2e:69:38:65:47:5f:dc:a8: + 7e:50:9e:64:b7:0d:71:d4:8e:6c:67:2e:35:b2:fd: + d6:65:d0:22:46:d6:aa ASN1 OID: secp384r1 NIST CURVE: P-384 X509v3 extensions: @@ -38,69 +38,66 @@ Certificate: X509v3 Basic Constraints: critical CA:FALSE X509v3 Subject Key Identifier: - 79:11:98:86:15:4F:48:F4:31:0B:D2:CC:C8:26:3A:09:07:5D:96:40 + 45:ED:32:14:6D:51:A2:3B:B0:80:55:E0:A6:9B:74:4C:A5:56:88:B1 X509v3 Authority Key Identifier: - keyid:B3:8A:A0:A2:BA:71:F1:A8:24:79:D4:A4:5B:25:36:15:1E:49:C8:CD + keyid:F3:EC:94:8E:F2:8E:30:C4:8E:68:C2:BF:8E:6A:19:C0:C1:9F:76:65 DirName:/C=XY/O=Python Software Foundation CA/CN=our-ca-server serial:CB:2D:80:99:5A:69:52:5B - Authority Information Access: CA Issuers - URI:http://testca.pythontest.net/testca/pycacert.cer OCSP - URI:http://testca.pythontest.net/testca/ocsp/ - X509v3 CRL Distribution Points: - Full Name: URI:http://testca.pythontest.net/testca/revocation.crl - Signature Algorithm: sha256WithRSAEncryption - 6e:42:e8:a2:2d:28:14:e3:25:5c:c1:7e:54:e9:3a:ff:30:db: - 94:ba:b2:f6:5f:ae:9a:c1:90:b3:4f:ce:65:1d:84:64:c0:71: - 2c:44:8e:7e:00:79:f5:8c:4a:1d:34:13:44:de:99:2e:db:53: - ee:ec:74:97:4d:59:1a:09:82:4f:98:75:91:a7:a0:b9:da:5e: - 68:f5:32:85:be:36:3d:83:d4:ee:f9:87:67:31:85:41:53:9a: - e7:05:96:13:1c:88:2e:7f:33:b1:ee:bd:f9:50:52:24:ed:3d: - 92:95:6e:30:c3:af:74:a9:ee:15:bb:da:7c:14:50:8e:e3:99: - ea:ba:b4:37:8a:50:61:26:de:01:93:b8:a2:6b:d9:c7:38:5e: - b2:f8:96:3d:a8:9f:7d:0c:71:d4:7e:cc:a0:57:af:7e:ce:3f: - a7:a7:27:68:c1:28:d7:4f:44:c1:b4:93:c3:c7:35:2b:50:c3: - 8e:2c:d0:46:c1:3f:e1:67:d3:f0:81:ae:f3:5c:3e:4f:d5:a8: - 07:8f:e0:eb:ef:d8:dc:47:e0:3d:58:eb:de:0e:7f:b2:58:cb: - 5c:f1:2f:65:7e:0f:0d:cc:ca:ba:83:53:63:bc:dd:18:0c:ee: - ed:ec:96:88:d0:38:c5:d7:ab:e7:55:79:7b:6d:ba:c0:a0:e9: - 5c:ca:7c:fb:f8:70:c7:fb:f5:b2:b5:74:cb:f7:c0:0d:20:9f: - 1d:b7:4c:bf:8a:8d:cd:e3:bc:4e:30:78:02:12:a0:9b:d5:8f: - 49:3c:95:91:76:6e:7c:54:dc:61:7a:2e:20:ed:35:25:e0:c5: - 17:50:02:83:00:74:8f:f0:1c:97:96:08:fc:2e:63:a4:f7:97: - 87:43:2a:32:04:2d:4c:f9:1a:07:bf:68:91:fc:50:21:a1:3c: - 8d:8f:fb:83:57:83:1f:b6:55:5c:55:2f:58:64:ad:f3:27:ba: - d0:e3:cd:58:01:a3:c9:ba:1d:95:dc:30:d5:af:b9:20:ad:d9: - 48:ba:8d:9a:66:ee + Signature Value: + 07:e4:91:0b:d3:ed:4b:52:7f:50:68:c7:8d:80:48:9f:b7:4a: + 13:66:bf:9d:4c:2d:18:19:68:a0:da:3b:12:85:05:16:fa:8d: + 9c:58:c6:81:b3:96:ba:11:62:65:d3:76:f1:1c:ab:95:e4:d8: + 2a:e0:1f:7b:c5:20:2e:7c:8f:de:87:7a:2b:52:54:ca:d1:41: + b0:5e:20:72:df:44:00:4a:69:1a:ef:10:63:52:13:ed:49:02: + ee:dc:9d:f3:c8:ba:c4:01:81:5a:a9:1c:15:12:b6:21:de:44: + a5:fd:7e:f9:22:d1:3e:ee:22:dd:31:55:32:4e:41:68:27:c5: + 95:1b:7e:6b:18:74:f9:22:d6:b7:b9:31:72:51:a0:5a:2c:ff: + 62:76:e9:a0:55:8d:78:33:52:4a:58:b2:f4:4b:0c:43:82:2f: + a9:84:68:05:dd:11:47:70:24:fe:5c:92:fd:17:21:63:bb:fa: + 93:fa:54:54:05:72:48:ed:81:48:ab:95:fc:6d:a8:62:96:f9: + 3b:e2:71:18:05:3e:76:bb:df:95:17:7b:81:4b:1f:7f:e1:67: + 76:c4:07:cb:65:a7:f2:cf:e6:b4:fb:75:7c:ee:df:a1:f5:34: + 20:2b:48:fd:2e:49:ff:f3:a6:3b:00:49:6c:88:79:ed:9c:16: + 2a:04:72:e2:93:e4:7e:3f:2a:dd:30:47:9a:99:84:2a:b9:c4: + 40:31:a6:68:f3:20:d1:75:f1:1e:c8:18:64:5b:f8:4c:ce:9a: + 3c:57:2c:e3:63:64:29:0a:c2:b6:8e:20:01:55:9f:fe:10:ba: + 12:42:38:0a:9b:53:01:a5:b4:08:76:ec:e8:a6:fc:69:2c:f7: + 7f:5e:0f:44:07:55:e1:7c:2e:58:e5:d6:fc:6f:c2:4d:83:65: + bd:f3:32:e3:14:48:22:8d:80:18:ea:44:f8:24:79:ff:ff:c6: + 04:c2:e9:90:34:40:d6:59:3f:59:1e:4a:9a:58:60:ce:ab:f9: + 76:0e:ef:f7:05:17 -----BEGIN CERTIFICATE----- MIIEyzCCAzOgAwIBAgIJAMstgJlaaVJeMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx NDIzMTZaMGMxCzAJBgNVBAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEj MCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24xFjAUBgNVBAMMDWxv -Y2FsaG9zdC1lY2MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATyp8ws6CuNOI2/3MC4 -jZVSkmoDzm/X/ZrkEm4TVHKPSZ6kzZRpmmUlLS9l7SQZSLYyDAFBFzoGJJYHhZNQ -XEO7HFszn6KnvLjhwS6ddzlaHPziEknrSr0OKhJmdJHrQASjggHEMIIBwDAYBgNV +Y2FsaG9zdC1lY2MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQef97STEPn4Nk6C153 +VEx24MNkJUcmLe771u6lr3Q8Em3J/YPaA1i9Ys7KZA3WvoKBPoWaaikn4yLQbd/6 +YE6AAjMuaThlR1/cqH5QnmS3DXHUjmxnLjWy/dZl0CJG1qqjggHEMIIBwDAYBgNV HREEETAPgg1sb2NhbGhvc3QtZWNjMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAU -BggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUeRGY -hhVPSPQxC9LMyCY6CQddlkAwfQYDVR0jBHYwdIAUs4qgorpx8agkedSkWyU2FR5J -yM2hUaRPME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUg +BggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQURe0y +FG1RojuwgFXgppt0TKVWiLEwfQYDVR0jBHYwdIAU8+yUjvKOMMSOaMK/jmoZwMGf +dmWhUaRPME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUg Rm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZlcoIJAMstgJlaaVJb MIGDBggrBgEFBQcBAQR3MHUwPAYIKwYBBQUHMAKGMGh0dHA6Ly90ZXN0Y2EucHl0 aG9udGVzdC5uZXQvdGVzdGNhL3B5Y2FjZXJ0LmNlcjA1BggrBgEFBQcwAYYpaHR0 cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0Y2Evb2NzcC8wQwYDVR0fBDww OjA4oDagNIYyaHR0cDovL3Rlc3RjYS5weXRob250ZXN0Lm5ldC90ZXN0Y2EvcmV2 -b2NhdGlvbi5jcmwwDQYJKoZIhvcNAQELBQADggGBAG5C6KItKBTjJVzBflTpOv8w -25S6svZfrprBkLNPzmUdhGTAcSxEjn4AefWMSh00E0TemS7bU+7sdJdNWRoJgk+Y -dZGnoLnaXmj1MoW+Nj2D1O75h2cxhUFTmucFlhMciC5/M7HuvflQUiTtPZKVbjDD -r3Sp7hW72nwUUI7jmeq6tDeKUGEm3gGTuKJr2cc4XrL4lj2on30McdR+zKBXr37O -P6enJ2jBKNdPRMG0k8PHNStQw44s0EbBP+Fn0/CBrvNcPk/VqAeP4Ovv2NxH4D1Y -694Of7JYy1zxL2V+Dw3MyrqDU2O83RgM7u3slojQOMXXq+dVeXttusCg6VzKfPv4 -cMf79bK1dMv3wA0gnx23TL+Kjc3jvE4weAISoJvVj0k8lZF2bnxU3GF6LiDtNSXg -xRdQAoMAdI/wHJeWCPwuY6T3l4dDKjIELUz5Gge/aJH8UCGhPI2P+4NXgx+2VVxV -L1hkrfMnutDjzVgBo8m6HZXcMNWvuSCt2Ui6jZpm7g== +b2NhdGlvbi5jcmwwDQYJKoZIhvcNAQELBQADggGBAAfkkQvT7UtSf1Box42ASJ+3 +ShNmv51MLRgZaKDaOxKFBRb6jZxYxoGzlroRYmXTdvEcq5Xk2CrgH3vFIC58j96H +eitSVMrRQbBeIHLfRABKaRrvEGNSE+1JAu7cnfPIusQBgVqpHBUStiHeRKX9fvki +0T7uIt0xVTJOQWgnxZUbfmsYdPki1re5MXJRoFos/2J26aBVjXgzUkpYsvRLDEOC +L6mEaAXdEUdwJP5ckv0XIWO7+pP6VFQFckjtgUirlfxtqGKW+TvicRgFPna735UX +e4FLH3/hZ3bEB8tlp/LP5rT7dXzu36H1NCArSP0uSf/zpjsASWyIee2cFioEcuKT +5H4/Kt0wR5qZhCq5xEAxpmjzINF18R7IGGRb+EzOmjxXLONjZCkKwraOIAFVn/4Q +uhJCOAqbUwGltAh27Oim/Gks939eD0QHVeF8Lljl1vxvwk2DZb3zMuMUSCKNgBjq +RPgkef//xgTC6ZA0QNZZP1keSppYYM6r+XYO7/cFFw== -----END CERTIFICATE----- diff --git a/Lib/test/certdata/leaf-missing-aki.ca.pem b/Lib/test/certdata/leaf-missing-aki.ca.pem new file mode 100644 index 000000000000000..36b202ae02ee54d --- /dev/null +++ b/Lib/test/certdata/leaf-missing-aki.ca.pem @@ -0,0 +1,13 @@ +# Taken from x509-limbo's `rfc5280::aki::leaf-missing-aki` testcase. +# See: https://x509-limbo.com/testcases/rfc5280/#rfc5280akileaf-missing-aki +-----BEGIN CERTIFICATE----- +MIIBkDCCATWgAwIBAgIUGjIb/aYm9u9fBh2o4GAYRJwk5XIwCgYIKoZIzj0EAwIw +GjEYMBYGA1UEAwwPeDUwOS1saW1iby1yb290MCAXDTcwMDEwMTAwMDAwMVoYDzI5 +NjkwNTAzMDAwMDAxWjAaMRgwFgYDVQQDDA94NTA5LWxpbWJvLXJvb3QwWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAARUzBhjMOkO911U65Fvs4YmL1YPNj63P9Fa+g9U +KrUqiIy8WjaDXdIe8g8Zj0TalpbU1gYCs3atteMxgIp6qxwHo1cwVTAPBgNVHRMB +Af8EBTADAQH/MAsGA1UdDwQEAwICBDAWBgNVHREEDzANggtleGFtcGxlLmNvbTAd +BgNVHQ4EFgQUcv1fyqgezMGzmo+lhmUkdUuAbIowCgYIKoZIzj0EAwIDSQAwRgIh +AIOErPSRlWpnyMub9UgtPF/lSzdvnD4Q8KjLQppHx6oPAiEA373p4L/HvUbs0xg8 +6/pLyn0RT02toKKJcMV3ChohLtM= +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/leaf-missing-aki.keycert.pem b/Lib/test/certdata/leaf-missing-aki.keycert.pem new file mode 100644 index 000000000000000..0fd2ab39bf2c706 --- /dev/null +++ b/Lib/test/certdata/leaf-missing-aki.keycert.pem @@ -0,0 +1,18 @@ +# Taken from x509-limbo's `rfc5280::aki::leaf-missing-aki` testcase. +# See: https://x509-limbo.com/testcases/rfc5280/#rfc5280akileaf-missing-aki +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIF5Re+/FP3rg+7c1odKEQPXhb9V65kXnlZIWHDG9gKrLoAoGCCqGSM49 +AwEHoUQDQgAE1WAQMdC7ims7T9lpK9uzaCuKqHb/oNMbGjh1f10pOHv3Z+oAvsqF +Sv3hGzreu69YLy01afA6sUCf1AA/95dKkg== +-----END EC PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIBjjCCATWgAwIBAgIUVlBgclml+OXlrWzZfcgYCiNm96UwCgYIKoZIzj0EAwIw +GjEYMBYGA1UEAwwPeDUwOS1saW1iby1yb290MCAXDTcwMDEwMTAwMDAwMVoYDzI5 +NjkwNTAzMDAwMDAxWjAWMRQwEgYDVQQDDAtleGFtcGxlLmNvbTBZMBMGByqGSM49 +AgEGCCqGSM49AwEHA0IABNVgEDHQu4prO0/ZaSvbs2griqh2/6DTGxo4dX9dKTh7 +92fqAL7KhUr94Rs63ruvWC8tNWnwOrFAn9QAP/eXSpKjWzBZMB0GA1UdDgQWBBS3 +yYRQQwo3syjGVQ8Yw7/XRZHbpzALBgNVHQ8EBAMCB4AwEwYDVR0lBAwwCgYIKwYB +BQUHAwEwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20wCgYIKoZIzj0EAwIDRwAwRAIg +BVq7lw4Y5MPEyisPhowMWd4KnERupdM5qeImDO+dD7ICIE/ksd6Wz1b8rMAfllNV +yiYst9lfwTd2SkFgdDNUDFud +-----END CERTIFICATE----- diff --git a/Lib/test/certdata/make_ssl_certs.py b/Lib/test/certdata/make_ssl_certs.py index 94a35a64ab1abe3..6626b93976a585c 100644 --- a/Lib/test/certdata/make_ssl_certs.py +++ b/Lib/test/certdata/make_ssl_certs.py @@ -109,7 +109,8 @@ subjectKeyIdentifier=hash authorityKeyIdentifier=keyid:always,issuer - basicConstraints = CA:true + basicConstraints = critical, CA:true + keyUsage = critical, digitalSignature, keyCertSign, cRLSign """ diff --git a/Lib/test/certdata/nosan.pem b/Lib/test/certdata/nosan.pem index ec10cdcabb9e355..c6ff8ea31bfa2ec 100644 --- a/Lib/test/certdata/nosan.pem +++ b/Lib/test/certdata/nosan.pem @@ -1,42 +1,42 @@ -----BEGIN PRIVATE KEY----- -MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQCv3sUoOE4F7Pye -AT2Q6XpXrGUOu1fYgdnItLLLhvn7ACuHMj7TA5UKXxsepJn5m2Ji9LvAbksr1IWd -LZAvNgjwsUR+E4HbY108BhVt9sk3HFkvE0OOFbAa14ICtYPe18P/4Hv6Zfu/GJDU -rwXHNCUu0p6i/mospZ5O3sx5MgVaShknGAEC3Kp7zOgusMmE8XSbkNQa3ARMkW4o -kTqWKAeAHDjVFVyyhzZQmo+gaLzhWfJVSZhlJsuiLoZGGrVTq85EiXsE4l8rPaI+ -mKkVzWP13IZW+Fx1tiIktumdHWb1OQWrvm8AiT9b8PcFCUUrvhQFcLDSCZjKlQ0t -RWrSSKrrVsSldOreqRLtpjGzFJpGnTcvslL7rP5pg5DjBsYmVcDjrmRuJuhGq52X -/6HEC97GouVK8tT1LVMv1wufVPn+i9TzwxOuRWeUvVqLAJgWQ9N3yKdymH+VrpZk -/oB9ScyDakGezZBW5CeOQbNJ8WoX58jNxefGjtqKxmyztu43r3ECAwEAAQKCAYBQ -fVoqYCqFV8L95X9x1QljGsldhqxbsIIl811o/KtoDtndFEfgd2E8z+4vhhHaRR0w -QOW02kWZF7jXCMVWdhp9XgQE15S0/bLsB7TDERFiIZ1HiD+AxbhFcKBV8REbahCQ -CQN0xDwFZ47RaBDy7JCf71EfM+UP7fSYECvww83jVspQNBIyZx+3bT5OMCbqqz88 -+3m3mT52dJDADEeN9WAJZ+Ey1IYKRwu6tCJLvePEF1BrbDVNBgZogXZ+mzalxpjr -4RpGPMMa+VWc8HmDVd+LtpwKJcQD00GvUP4fNywn+5jvNWl54FdQiTLPrieTWxas -XUQ2crxP7Aqr2/vsU5Ruru5uF7H+ssMHp9YQDhpJ2+SVhQ9P+/loXCuKGt+BrB2Z -MlitO3f+vfRtzATmJ8G0qFrOqZK1A/qsiyIze240C1hAl3oy2xpZqTDGp4gRWwoi -OIN0HmH9UbP7bbNQY1x/zstTbza4/7rGb1+DZKeZIMu7QjBCU0rtsJpGtUvcQGEC -gcEA42GMYSL/HljZMF1LsDhTX/cmP8FDNgONhWYxT+w0Csnj1usLNBaT63dYnEiW -QKydRR4casAR1Kdy4Yfcy2lCy1kCfwqkQYk8fxSsOSHRjUfwC1SnfdYlwKFMxw4a -oZF0R4oVCBYrfP+8kqrj+5gs/gXblsw72XkYtbCdIriKKdmUzTx7MegzSqh2PVRi -rJzuwCZQ/O0NfhwdOHxLQDo0dgD+vv9e+KOSoJ9FDv8HH1tnolpRMdkSA8AJR/Nk -DXt1AoHBAMYBfTKQZ2jqLKybe4tP+YKjvjVp8vJx0iNUXFN/P6hBaSBOgq85uxXL -X3s7N/pkOCjyE95B8QusIkbnbfdyEP89O4bTbUHPXyAkHyRkR7Vny49HYuaR/aXQ -mXC0J2z5bXVpCQ514l/R/Io3wBph+hbG3To7pp9pMOV4qzvibUZaTZFwH+q+xDwf -SKSFy3fcomgH4/K5/QuKVj0jOUQsYjQQWb8GukS2KZK3zYJIAG1bBcsCVpSuBdW0 -eCZgqjnwjQKBwCUyUwWc9QEg5b68tGIKhNEhHDe3xOf0ItWcxxpc+JJ/Pm9tGfMW -cnJFntBKK5I+6qdg6qMn8oLINcnhMORxvsSHNhpUQlSaP7RGTHo4JxCmoQUpfxDd -1GUzvdyeWQrvQYdmdlRRVCHpsA6KOCtzVIDlsmtz06Ka5cjrMHl6mNeJyYbdiwW6 -B5ICBv23bUDxlzkFy5/ko51qufkAlErYeraHKSVTn1SrZZQzGdf/LkoZ6NUtUzUF -XqYQZzRHA6oU9QKBwDslzLljC5D6ivfQxln6POV6dmJMUOd9erFVDPNgSqq/R2EA -MueXDjzXcKFGMlWYxHHuxmKZPiEnfWHC1kWZjFxCdVq0I6oKATd/stHTJtyYseUO -BQwtRiDXLE7PcguKgtkU1EC+lC3dc1vyhW8cH3HYW9N+aCqsaI/TuQr9e3kNlqhA -XzhnXgU7rx5+XSZkARukZ8JlLqLY4yQGNqAXxgoZbEW1A8VsyQRr5XbqfT4td5CK -FUT6qwGIlG+aZp9CLQKBwQCQkwdW9A/Q4Ffq8+XTL1hJ24m/q11OLAPODUypOhWw -OCbX2fkv59pSBe6niZDBls1NpHB9mzalBrJCfU+yKC667gKcKULOnWULIoOQvmcg -Ka3hkkW28gTnCjfDIYm3IdsLjc67zJplOixaKgxhO8NtJZGtg0oLIrofG8EYRInv -OmtGw+XE+s4TVs6WgXnEg9zWQ5ZYtqQVn6PT5jsz+Nrvipi61HWHVBd7g+78ojps -3suWxl0FvgzTW5HD16WRXeI= +MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQC99xEYPTwFN/ji +i0lm11ckEGhcxciSsIgTgior54CLgQy7JXllTYmAWFTTg2zNBvDMexGI0h+xtZ4q +1Renghgt33N3Y6CT3v/L7JkE1abQbFveKW/ydlxH0+jLlsENSWjySwC80+f9L3bX +TcD8T4Fu9Uty2Rg1a/Eyekng5RmfkmLNgxfnX5R5nWhh0Aia7h3Ax2zCALfxqZIm +fxwavEgHsW/yZi+T+eoJwe0i7a6LaUoLqsPV9ZhagziNDaappPHH42NW39WlRhx1 +UjtiRm2Jihnzxcfs+90zLXSp5pxo/cE9Ia4d8ieq3Rxd/XgjlF6FXXFJjwfL36Dw +ehy8m3PKKAuO+fyMgPPPMQb7oaRy/MBG0NayRreTwyKILS2zafIW/iKpgICbxrWJ +r/H1b3S6PBKYUE2uQs0/ZPnRjjh0VeNnue7JcRoNbe27I2d56KUBsVEPdokjU59v +NYi6Se+ViZXtUbM1u/I0kvDMprAiobwtJFYgcE86N1lFJjHSwDMCAwEAAQKCAYBb +lvnJBA0iPwBiyeFUElNTcg2/XST9hNu2/DU1AeM6X7gxqznCnAXFudD8Qgt9NvF2 +xYeIvjbFydk+sYs8Gj9qLqhPUdukMAqI2cRVTmWla/lHPhdZgbOwdf1x23es3k4Z +NAxg/pKFwhK8cCKyA+tWAjKkZwODDk42ljt0kUEvbLbye1hVGAJQOJKRRmo/uLrj +rcNELnCBtc5ffT2hrlHUU7qz1ozt/brXhYa+JnbXhKZMxcKyMD2KtmXXrFNEy99o +jXbrpDCos82bzQfPDo8IpCbVbEd2J00aFmrNjQWhZuXX5dXflrujW4J0nzeHrZ78 +rNAz2/YuZ543BTB3XbogeFuLC5RqBgAMmw2WJ96Oa/UG8nZNvEw54N5r6dhfXj6A +VlJFLVwlfBQdAdaM3P4uZ6WECrH3EerQa27qyUdRrcDaGPLt7wG9FmMivnW1KQsy +5ow/gM0CsxFj2xNoGw1S5jtclbgSy8HNJaBsNk4XMQ+ORABZdG1MTTE+GMSjD/EC +gcEA+6JYiZEo+QrvItIZYB6Go4suu/F8df1pEjJlxwp2GmObitRhtV6r9g9IySFv +5SL7ZxARr4aQxvM7fNp57p9ssmkBtY0ofMjJAxhvs4T37bAcGK/2xCegNSmbqh24 +FAWpRDMgE5PjtuWC5jTvSOYFeUxwI/cu0HxWdxJl2dPUSL1nI2jP+ok3pZobEQk9 +E2+MlHpKmU+s/lAkuQiP+AW9a4M+ZJNWxocJjmtwj4OjJXPm7GddA/5x622DxFe6 +4K2vAoHBAMFC0An25bwGoFrCV/96s45K4qZcZcJ660+aK3xXaq6/8KfiusJnWds2 +nc0B6jYjKs8A7yTAGXke6fmyVsoLosZiXsbpW2m16g8jL79Tc85O9oDNmDIGk1uT +tRLZc2BvmHmy/dNrdbT/EHC3FKNWQVqWc2sHhPeB6F3hIEXDSUO/GB0njMZNXrPJ +547RlhN0xCLb3vTzzGHwNmwfI81YjV/XI4vpJjq1YceN8Xyd1r5ZOFfU8woIACO3 +I4dvBQ1avQKBwQCLDs9wzohfAFzg2Exvos7y6AKemDgYmD8NcE5wbWaQ9MTLNsz8 +RuIu64lkpRbKAMf/z5CGeI3fdCFGwRGq/e06tu7b3rMmKmtzS3jHM08zyiPsvKlZ +AzD00BaXLy8/2VUOPFaYmxy3QSRShaRKm9sgik5agcocKuo5iTBB7V8eB5VMqyps +IJJg8MXOZ1WaPQXqM56wFKjcLXvtyT6OaNWh6Xh8ajQFKDDuxI8CsFNjaiaONBzi +DSX1XaL4ySab7T8CgcEAsI+7xP6+EDP1mDVpc8zD8kHUI6zSgwUNqiHtjKHIo3JU +CO2JNkZ5v158eGlBcshaOdheozKlkxR9KlSWGezbf2crs4pKq585AS9iVeeGK3vU +lQRAAaQkSEv/6AKl9/q8UKMIZnkMhploibGZt0f8WSiOtb+e6QjUI8CjXVj2vF// +RdN2N01EMflKBh7Qf2H0NuytGxkJJojxD4K7kMVQE7lXjmEpPgWsGUZC01jYcfrN +EOFKUWXRys9sNDVnZjX5AoHAFRyOC1BlmVEtcOsgzum4+JEDWvRnO1hP1tm68eTZ +ijB/XppDtVESpq3+1+gx2YOmzlUNEhKlcn6eHPWEJwdVxJ87Gdh03rIV/ZQUKe46 +3+j6l/5coN4WfCBloy4b+Tcj+ZTL4sKaLm33RoD2UEWS5mmItfZuoEFQB958h3JD +1Ka1tgsLnuYGjcrg+ALvbM5nQlefzPqPJh0C8UV3Ny/4Gd02YgHw7Yoe4m6OUqQv +hctFUL/gjIGg9PVqTWzVVKaI -----END PRIVATE KEY----- Certificate: Data: @@ -51,80 +51,81 @@ Certificate: Subject: C=XY, L=Castle Anthrax, O=Python Software Foundation, CN=nosan Subject Public Key Info: Public Key Algorithm: rsaEncryption - RSA Public-Key: (3072 bit) + Public-Key: (3072 bit) Modulus: - 00:af:de:c5:28:38:4e:05:ec:fc:9e:01:3d:90:e9: - 7a:57:ac:65:0e:bb:57:d8:81:d9:c8:b4:b2:cb:86: - f9:fb:00:2b:87:32:3e:d3:03:95:0a:5f:1b:1e:a4: - 99:f9:9b:62:62:f4:bb:c0:6e:4b:2b:d4:85:9d:2d: - 90:2f:36:08:f0:b1:44:7e:13:81:db:63:5d:3c:06: - 15:6d:f6:c9:37:1c:59:2f:13:43:8e:15:b0:1a:d7: - 82:02:b5:83:de:d7:c3:ff:e0:7b:fa:65:fb:bf:18: - 90:d4:af:05:c7:34:25:2e:d2:9e:a2:fe:6a:2c:a5: - 9e:4e:de:cc:79:32:05:5a:4a:19:27:18:01:02:dc: - aa:7b:cc:e8:2e:b0:c9:84:f1:74:9b:90:d4:1a:dc: - 04:4c:91:6e:28:91:3a:96:28:07:80:1c:38:d5:15: - 5c:b2:87:36:50:9a:8f:a0:68:bc:e1:59:f2:55:49: - 98:65:26:cb:a2:2e:86:46:1a:b5:53:ab:ce:44:89: - 7b:04:e2:5f:2b:3d:a2:3e:98:a9:15:cd:63:f5:dc: - 86:56:f8:5c:75:b6:22:24:b6:e9:9d:1d:66:f5:39: - 05:ab:be:6f:00:89:3f:5b:f0:f7:05:09:45:2b:be: - 14:05:70:b0:d2:09:98:ca:95:0d:2d:45:6a:d2:48: - aa:eb:56:c4:a5:74:ea:de:a9:12:ed:a6:31:b3:14: - 9a:46:9d:37:2f:b2:52:fb:ac:fe:69:83:90:e3:06: - c6:26:55:c0:e3:ae:64:6e:26:e8:46:ab:9d:97:ff: - a1:c4:0b:de:c6:a2:e5:4a:f2:d4:f5:2d:53:2f:d7: - 0b:9f:54:f9:fe:8b:d4:f3:c3:13:ae:45:67:94:bd: - 5a:8b:00:98:16:43:d3:77:c8:a7:72:98:7f:95:ae: - 96:64:fe:80:7d:49:cc:83:6a:41:9e:cd:90:56:e4: - 27:8e:41:b3:49:f1:6a:17:e7:c8:cd:c5:e7:c6:8e: - da:8a:c6:6c:b3:b6:ee:37:af:71 + 00:bd:f7:11:18:3d:3c:05:37:f8:e2:8b:49:66:d7: + 57:24:10:68:5c:c5:c8:92:b0:88:13:82:2a:2b:e7: + 80:8b:81:0c:bb:25:79:65:4d:89:80:58:54:d3:83: + 6c:cd:06:f0:cc:7b:11:88:d2:1f:b1:b5:9e:2a:d5: + 17:a7:82:18:2d:df:73:77:63:a0:93:de:ff:cb:ec: + 99:04:d5:a6:d0:6c:5b:de:29:6f:f2:76:5c:47:d3: + e8:cb:96:c1:0d:49:68:f2:4b:00:bc:d3:e7:fd:2f: + 76:d7:4d:c0:fc:4f:81:6e:f5:4b:72:d9:18:35:6b: + f1:32:7a:49:e0:e5:19:9f:92:62:cd:83:17:e7:5f: + 94:79:9d:68:61:d0:08:9a:ee:1d:c0:c7:6c:c2:00: + b7:f1:a9:92:26:7f:1c:1a:bc:48:07:b1:6f:f2:66: + 2f:93:f9:ea:09:c1:ed:22:ed:ae:8b:69:4a:0b:aa: + c3:d5:f5:98:5a:83:38:8d:0d:a6:a9:a4:f1:c7:e3: + 63:56:df:d5:a5:46:1c:75:52:3b:62:46:6d:89:8a: + 19:f3:c5:c7:ec:fb:dd:33:2d:74:a9:e6:9c:68:fd: + c1:3d:21:ae:1d:f2:27:aa:dd:1c:5d:fd:78:23:94: + 5e:85:5d:71:49:8f:07:cb:df:a0:f0:7a:1c:bc:9b: + 73:ca:28:0b:8e:f9:fc:8c:80:f3:cf:31:06:fb:a1: + a4:72:fc:c0:46:d0:d6:b2:46:b7:93:c3:22:88:2d: + 2d:b3:69:f2:16:fe:22:a9:80:80:9b:c6:b5:89:af: + f1:f5:6f:74:ba:3c:12:98:50:4d:ae:42:cd:3f:64: + f9:d1:8e:38:74:55:e3:67:b9:ee:c9:71:1a:0d:6d: + ed:bb:23:67:79:e8:a5:01:b1:51:0f:76:89:23:53: + 9f:6f:35:88:ba:49:ef:95:89:95:ed:51:b3:35:bb: + f2:34:92:f0:cc:a6:b0:22:a1:bc:2d:24:56:20:70: + 4f:3a:37:59:45:26:31:d2:c0:33 Exponent: 65537 (0x10001) Signature Algorithm: sha256WithRSAEncryption - 91:42:c2:15:57:42:47:77:e7:0f:c5:55:26:b1:5b:c3:5e:ba: - 81:db:e1:a4:9f:b8:42:5a:21:c9:8c:18:ae:0f:90:ab:9a:24: - e7:d2:78:fc:bd:97:29:b1:5c:46:1f:5b:b8:d2:a7:87:f1:50: - 53:5b:d3:be:57:74:bd:e5:75:db:50:81:f7:37:95:0b:69:ef: - 39:8c:5c:82:d5:64:62:d5:8b:e9:e0:31:e1:73:d2:5a:2c:de: - 43:5a:06:e5:d3:4d:d0:35:e0:9f:c2:73:31:bc:35:69:d4:fb: - 7d:f0:1a:33:f7:f6:25:72:9c:a6:84:05:08:f6:b5:e8:04:10: - f1:1f:f2:95:ad:a1:f8:d8:80:a5:eb:75:43:99:33:90:0c:79: - fc:c0:87:08:95:20:aa:c2:81:0b:22:6f:56:f4:8f:2a:23:f8: - 40:47:1c:03:a5:b1:04:0a:04:4a:df:d0:88:a8:bc:31:f2:42: - 9b:d8:11:14:9e:e3:68:ea:07:2c:15:de:d2:36:5a:15:38:ed: - d2:af:0e:b4:b6:1d:a0:57:94:ea:c3:c7:4c:14:57:81:00:57: - 94:d3:b0:27:69:d7:48:02:6c:e5:97:f7:be:22:7c:38:24:af: - b2:b0:7b:08:75:1e:ca:2e:c7:41:ef:8b:74:cf:c9:c3:6f:39: - b9:52:41:18:c6:70:24:54:51:04:fe:5f:88:70:35:e5:1c:8e: - d6:67:69:44:44:33:9b:8c:fe:a5:b9:95:48:66:84:f3:1a:04: - ab:a3:57:c1:b6:b4:2f:28:12:45:2b:cb:42:d3:f4:a5:ce:7b: - 6c:1f:e4:c8:a9:e7:d4:6d:c8:27:2d:69:26:c5:e8:73:10:54: - 1f:c3:bf:fd:aa:f5:95:6f:f6:ca:d5:06:8f:1b:79:93:e3:86: - ba:8d:fe:a8:10:8f:95:3e:14:09:bf:ca:88:59:e2:93:b6:ec: - 03:a9:7e:dd:1f:5f:13:d3:29:b3:a6:f3:6a:df:30:53:44:c8: - cd:e5:82:57:bc:9c + Signature Value: + 7e:dd:64:64:92:6c:b9:41:ce:f3:e3:f8:e6:9f:c8:5b:32:39: + 8c:03:5b:5e:7e:b3:23:ca:6c:d1:99:2f:53:af:9d:3c:84:cd: + c6:ce:0a:ee:94:de:ff:a7:06:81:7e:e2:38:a5:05:39:58:22: + dc:13:83:53:e7:f8:16:cb:93:dc:cf:4b:e6:1b:9f:9e:71:ef: + ee:ba:ea:b6:68:5c:32:22:7e:54:4f:46:a6:0b:11:8f:ef:05: + 6e:d3:0b:d0:a8:be:95:23:a2:e4:e7:a8:a2:a4:7d:98:52:86: + a4:15:fb:74:7a:9a:89:23:43:20:26:3a:56:9e:a3:6e:54:02: + 76:4e:25:9c:a1:8c:03:99:e5:eb:a6:61:b4:9c:2a:b1:ed:eb: + 94:f9:14:aa:a4:c3:f0:f7:7a:03:a3:b1:f8:c0:83:79:ab:8a: + 93:7f:0a:95:08:50:ff:55:19:ac:28:a2:c8:9f:a6:77:72:a3: + da:37:a9:ff:f3:57:70:c8:65:d9:55:14:84:b4:b3:78:86:82: + da:84:2c:48:19:51:ec:9d:20:b1:4d:18:fb:82:9f:7b:a7:80: + 22:69:25:83:4d:bf:ac:31:64:f5:39:11:f1:ed:53:fb:67:ab: + 91:86:c5:4d:87:e8:6b:fe:9a:84:fe:6a:92:6b:62:c1:ae:d2: + f0:cb:06:6e:f3:50:f4:8d:6d:fa:7d:6a:1c:64:c3:98:91:da: + c9:8c:a9:79:e5:48:4c:a2:de:42:28:e8:0e:9f:52:6a:a4:e0: + c7:ac:11:9c:ba:5d:d6:84:93:56:28:f1:6d:83:aa:62:b2:b7: + 56:c6:64:d9:96:4e:97:ab:4e:8f:ba:f6:ab:b9:17:52:98:32: + 7f:b5:12:fa:39:d7:34:2a:f3:ed:40:90:6f:66:7b:b6:c1:9d: + b9:53:d0:e3:e9:69:8c:cf:7a:fd:08:0a:62:47:d4:ce:72:f7: + 6f:80:b4:1d:18:7a:ba:a2:a9:45:49:ef:9c:0b:99:89:03:ab: + 5f:7a:9d:c5:77:b7 -----BEGIN CERTIFICATE----- MIIEJDCCAowCCQDLLYCZWmlSYTANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQGEwJY WTEmMCQGA1UECgwdUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24gQ0ExFjAUBgNV BAMMDW91ci1jYS1zZXJ2ZXIwHhcNMTgwODI5MTQyMzE2WhcNMzcxMDI4MTQyMzE2 WjBbMQswCQYDVQQGEwJYWTEXMBUGA1UEBwwOQ2FzdGxlIEFudGhyYXgxIzAhBgNV BAoMGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMQ4wDAYDVQQDDAVub3NhbjCC -AaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAK/exSg4TgXs/J4BPZDpeles -ZQ67V9iB2ci0ssuG+fsAK4cyPtMDlQpfGx6kmfmbYmL0u8BuSyvUhZ0tkC82CPCx -RH4TgdtjXTwGFW32yTccWS8TQ44VsBrXggK1g97Xw//ge/pl+78YkNSvBcc0JS7S -nqL+aiylnk7ezHkyBVpKGScYAQLcqnvM6C6wyYTxdJuQ1BrcBEyRbiiROpYoB4Ac -ONUVXLKHNlCaj6BovOFZ8lVJmGUmy6IuhkYatVOrzkSJewTiXys9oj6YqRXNY/Xc -hlb4XHW2IiS26Z0dZvU5Bau+bwCJP1vw9wUJRSu+FAVwsNIJmMqVDS1FatJIqutW -xKV06t6pEu2mMbMUmkadNy+yUvus/mmDkOMGxiZVwOOuZG4m6EarnZf/ocQL3sai -5Ury1PUtUy/XC59U+f6L1PPDE65FZ5S9WosAmBZD03fIp3KYf5WulmT+gH1JzINq -QZ7NkFbkJ45Bs0nxahfnyM3F58aO2orGbLO27jevcQIDAQABMA0GCSqGSIb3DQEB -CwUAA4IBgQCRQsIVV0JHd+cPxVUmsVvDXrqB2+Gkn7hCWiHJjBiuD5CrmiTn0nj8 -vZcpsVxGH1u40qeH8VBTW9O+V3S95XXbUIH3N5ULae85jFyC1WRi1Yvp4DHhc9Ja -LN5DWgbl003QNeCfwnMxvDVp1Pt98Boz9/YlcpymhAUI9rXoBBDxH/KVraH42ICl -63VDmTOQDHn8wIcIlSCqwoELIm9W9I8qI/hARxwDpbEECgRK39CIqLwx8kKb2BEU -nuNo6gcsFd7SNloVOO3Srw60th2gV5Tqw8dMFFeBAFeU07AnaddIAmzll/e+Inw4 -JK+ysHsIdR7KLsdB74t0z8nDbzm5UkEYxnAkVFEE/l+IcDXlHI7WZ2lERDObjP6l -uZVIZoTzGgSro1fBtrQvKBJFK8tC0/SlzntsH+TIqefUbcgnLWkmxehzEFQfw7/9 -qvWVb/bK1QaPG3mT44a6jf6oEI+VPhQJv8qIWeKTtuwDqX7dH18T0ymzpvNq3zBT -RMjN5YJXvJw= +AaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAL33ERg9PAU3+OKLSWbXVyQQ +aFzFyJKwiBOCKivngIuBDLsleWVNiYBYVNODbM0G8Mx7EYjSH7G1nirVF6eCGC3f +c3djoJPe/8vsmQTVptBsW94pb/J2XEfT6MuWwQ1JaPJLALzT5/0vdtdNwPxPgW71 +S3LZGDVr8TJ6SeDlGZ+SYs2DF+dflHmdaGHQCJruHcDHbMIAt/GpkiZ/HBq8SAex +b/JmL5P56gnB7SLtrotpSguqw9X1mFqDOI0Npqmk8cfjY1bf1aVGHHVSO2JGbYmK +GfPFx+z73TMtdKnmnGj9wT0hrh3yJ6rdHF39eCOUXoVdcUmPB8vfoPB6HLybc8oo +C475/IyA888xBvuhpHL8wEbQ1rJGt5PDIogtLbNp8hb+IqmAgJvGtYmv8fVvdLo8 +EphQTa5CzT9k+dGOOHRV42e57slxGg1t7bsjZ3nopQGxUQ92iSNTn281iLpJ75WJ +le1RszW78jSS8MymsCKhvC0kViBwTzo3WUUmMdLAMwIDAQABMA0GCSqGSIb3DQEB +CwUAA4IBgQB+3WRkkmy5Qc7z4/jmn8hbMjmMA1tefrMjymzRmS9Tr508hM3Gzgru +lN7/pwaBfuI4pQU5WCLcE4NT5/gWy5Pcz0vmG5+ece/uuuq2aFwyIn5UT0amCxGP +7wVu0wvQqL6VI6Lk56iipH2YUoakFft0epqJI0MgJjpWnqNuVAJ2TiWcoYwDmeXr +pmG0nCqx7euU+RSqpMPw93oDo7H4wIN5q4qTfwqVCFD/VRmsKKLIn6Z3cqPaN6n/ +81dwyGXZVRSEtLN4hoLahCxIGVHsnSCxTRj7gp97p4AiaSWDTb+sMWT1ORHx7VP7 +Z6uRhsVNh+hr/pqE/mqSa2LBrtLwywZu81D0jW36fWocZMOYkdrJjKl55UhMot5C +KOgOn1JqpODHrBGcul3WhJNWKPFtg6pisrdWxmTZlk6Xq06PuvaruRdSmDJ/tRL6 +Odc0KvPtQJBvZnu2wZ25U9Dj6WmMz3r9CApiR9TOcvdvgLQdGHq6oqlFSe+cC5mJ +A6tfep3Fd7c= -----END CERTIFICATE----- diff --git a/Lib/test/certdata/pycacert.pem b/Lib/test/certdata/pycacert.pem index 360cd57426a5a87..0a48bf7d23539cf 100644 --- a/Lib/test/certdata/pycacert.pem +++ b/Lib/test/certdata/pycacert.pem @@ -11,89 +11,92 @@ Certificate: Subject: C=XY, O=Python Software Foundation CA, CN=our-ca-server Subject Public Key Info: Public Key Algorithm: rsaEncryption - RSA Public-Key: (3072 bit) + Public-Key: (3072 bit) Modulus: - 00:b1:84:d3:4f:5c:04:80:91:4f:82:49:ba:30:0b: - f7:e8:cb:f9:14:ef:3d:9f:0b:3f:0a:62:fc:1b:20: - a5:20:d1:60:5f:87:5a:1f:16:d1:ed:97:70:a6:da: - 1b:03:2c:7e:a0:5b:3c:4e:2f:16:7e:0e:89:29:89: - e1:10:0d:38:da:6a:77:5f:37:13:b3:28:8f:7b:5c: - 76:ad:9e:e8:d3:f5:9e:f5:83:aa:10:07:8d:e6:51: - 98:f0:7c:0d:52:f2:0c:21:1e:d8:b9:99:26:a9:25: - 03:27:bb:5c:ab:2e:33:27:a2:d6:23:a8:83:87:44: - 29:9f:97:b5:24:6f:d7:b9:0a:fd:28:ee:bb:fb:41: - 58:ea:1d:99:dd:44:86:ab:98:be:1c:dc:cb:a9:89: - 1d:36:5c:a9:e8:47:b5:f4:52:48:aa:b5:a4:67:ef: - 3e:d7:e2:d3:33:de:98:29:d8:7a:b0:59:5c:e7:b1: - 0e:cc:fd:9f:eb:f6:d5:3a:0e:0b:cf:fe:0b:3d:a2: - bf:45:18:ce:94:e7:a9:55:60:88:d4:d8:84:50:79: - 05:2e:41:03:74:ae:67:26:f6:5b:12:08:98:ce:0a: - 97:ed:01:0f:89:4f:17:5c:fa:3e:1d:35:24:47:92: - 32:bf:f7:a4:18:2b:3c:d0:48:99:e1:a2:cd:a3:cc: - 50:53:20:b5:c6:e3:66:85:7b:57:10:ec:33:4f:c1: - 77:e7:1b:7e:81:c6:c4:f3:45:20:c0:91:dd:13:76: - 7b:03:af:f6:76:8e:a2:83:63:57:dd:63:bc:bb:5a: - 1c:17:52:8a:d6:06:48:cc:0f:c7:d3:4f:e8:da:22: - 6c:86:f9:4e:5c:a6:29:07:3b:d8:56:4c:59:b3:20: - 49:07:7b:94:84:cf:2b:c3:1c:1a:4e:87:64:92:ba: - 42:e1:e6:ad:7d:1d:f6:54:90:6f:2b:e9:b3:cc:4b: - 2b:33:26:23:fd:65:c0:3c:f0:79:ad:c9:c1:81:ef: - 37:04:e0:27:3e:b0:ee:15:be:51 + 00:d0:a0:9b:b1:b9:3b:79:ee:31:2f:b8:51:1c:01: + 75:ed:09:59:4c:ac:c8:bf:6a:0a:f8:7a:08:f0:25: + e3:25:7b:6a:f8:c4:d8:aa:a0:60:07:25:b0:cf:73: + 71:05:52:68:bf:06:93:ae:10:61:96:bc:b3:69:81: + 1a:7d:19:fc:20:86:8f:9a:68:1b:ed:cd:e2:6c:61: + 67:c7:4e:55:ea:43:06:21:1b:e9:b9:be:84:5f:c2: + da:eb:89:88:e0:42:a6:45:49:2a:d0:b9:6b:8c:93: + c9:44:3b:ca:fc:3c:94:7f:dd:70:c5:ad:d8:4b:3b: + dc:1e:f8:55:4b:b5:27:86:f8:df:b0:83:cf:f7:16: + 37:bf:13:17:34:d4:c9:55:ed:6b:16:c9:7f:ad:20: + 4e:f0:1e:d9:1b:bf:8d:ba:cd:ca:96:ef:1e:69:cb: + 51:66:61:48:0d:e8:79:a3:59:61:d4:10:8d:e0:0d: + 3b:0b:85:b6:d5:85:12:dd:a5:07:c5:4b:fa:23:fa: + 54:39:f7:97:0b:c9:44:47:1e:56:77:3c:3c:13:01: + 0b:6d:77:d6:14:ba:44:f4:53:35:56:d9:3d:b9:3e: + 35:5f:db:33:9a:4f:5a:b9:38:44:c9:32:49:fe:66: + f6:1f:1a:97:aa:ca:4c:4a:f6:b8:5e:40:7f:fb:0b: + 1d:f8:5b:a1:dc:f8:c0:2e:d0:6d:49:f5:d2:46:d4: + 90:57:fe:92:81:34:ae:2d:38:bb:a8:17:0c:e1:e5: + 3f:e2:f7:26:05:54:50:f5:64:b3:1c:6e:44:ff:6f: + a9:b4:03:96:e9:0e:c2:88:d8:72:52:90:99:c6:41: + 0f:46:90:59:b8:3e:6f:d2:e2:9e:1d:36:82:95:d3: + 58:8a:12:f3:e2:d8:0d:20:51:23:f0:90:2d:9a:3e: + 7d:26:86:b2:a7:d7:f9:28:60:03:e3:77:c7:88:04: + c9:fe:89:77:70:10:4f:c3:a0:8a:3b:f4:ab:42:7b: + e3:14:92:d8:ae:16:d6:a1:de:7d Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Key Identifier: - B3:8A:A0:A2:BA:71:F1:A8:24:79:D4:A4:5B:25:36:15:1E:49:C8:CD + F3:EC:94:8E:F2:8E:30:C4:8E:68:C2:BF:8E:6A:19:C0:C1:9F:76:65 X509v3 Authority Key Identifier: - keyid:B3:8A:A0:A2:BA:71:F1:A8:24:79:D4:A4:5B:25:36:15:1E:49:C8:CD - - X509v3 Basic Constraints: + F3:EC:94:8E:F2:8E:30:C4:8E:68:C2:BF:8E:6A:19:C0:C1:9F:76:65 + X509v3 Basic Constraints: critical CA:TRUE + X509v3 Key Usage: critical + Digital Signature, Certificate Sign, CRL Sign Signature Algorithm: sha256WithRSAEncryption - 6b:32:2f:e7:05:18:ea:5c:c9:95:f4:e0:c2:0c:41:5f:1a:0a: - 95:c9:c7:7d:05:ee:8a:56:29:35:50:40:b7:fe:9f:7b:5b:1c: - c3:69:2f:a0:cb:d2:b8:91:2f:50:19:62:f7:27:18:6d:95:7b: - 53:16:15:a2:5a:dc:14:e3:fb:b1:32:a9:69:db:a6:33:47:3c: - bb:1f:d2:dc:70:f9:6a:2e:0c:d8:8c:6d:e5:5d:1d:43:3c:4e: - 91:de:a0:c8:da:a0:4b:0e:9d:5e:b6:0f:4a:49:f0:7b:b6:53: - 9e:fd:35:14:5b:e3:4d:b4:18:a6:36:61:e8:8f:33:9b:d4:05: - f9:54:66:df:e0:cb:18:a3:4e:dc:17:a8:a0:b3:c1:a8:f4:d6: - 9d:ca:7f:68:53:1a:d7:95:da:e8:d3:9e:48:00:71:95:99:11: - 07:cf:96:c0:7d:ce:7d:30:e8:4f:e1:83:16:33:a1:ff:59:9b: - 3e:4c:e7:3a:38:01:9f:0f:67:4c:fd:2d:8b:4a:d4:01:46:37: - 33:e8:13:6b:15:a9:1d:68:76:45:a2:82:33:69:26:30:60:05: - c8:8f:bd:b4:75:ab:be:7a:8b:48:68:70:40:b4:1b:51:c5:e6: - 7a:ad:6b:4f:db:17:c0:60:67:2e:63:61:9b:2c:48:99:b8:76: - 45:a0:9e:cc:ef:33:1e:50:4e:ab:72:c3:65:c8:b2:79:b3:35: - 83:21:78:d3:8b:6c:3a:18:e8:65:32:39:b8:c0:9d:71:2f:35: - 36:8a:c0:17:62:d8:8b:3e:e1:22:18:2b:4c:63:a6:0e:9d:0a: - fa:ab:5b:35:fb:88:91:77:4c:8d:8c:9d:a9:cf:fc:ab:c2:e6: - 5a:05:7b:7e:04:6e:39:cf:93:ce:67:3b:7a:cb:af:b6:36:e1: - fb:71:64:45:d4:a6:f0:ce:ef:75:04:99:69:9a:e5:88:0a:10: - 02:74:89:ec:75:84:44:80:48:df:c1:f7:e9:37:ce:ce:92:92: - 5c:89:22:08:73:1f + Signature Value: + 8b:00:54:72:b3:8d:eb:f3:af:34:9f:d6:60:ea:de:84:3f:8c: + 04:8f:19:a6:be:02:67:c4:63:c5:74:e3:47:37:59:83:94:06: + f1:45:19:e8:07:2f:d6:4e:4b:4f:a8:3d:c7:07:07:27:92:f4: + 7e:73:4f:8b:32:19:94:46:7a:25:c4:d9:c4:27:b0:11:63:3a: + 60:8b:85:e1:73:4f:34:3b:6b:a4:34:8c:49:8e:cd:cf:4f:b2: + 65:27:41:19:b0:fc:80:31:78:f2:73:6a:9b:7d:71:34:50:fc: + 78:a8:da:05:b4:9c:5b:3a:99:7a:6b:5d:ef:3b:d3:e9:3b:33: + 01:12:65:cf:5e:07:d8:19:af:d5:53:ea:f0:10:ac:c4:b6:26: + 3c:34:2e:74:ee:64:dd:1d:36:75:89:44:00:b0:0d:fd:2f:b3: + 01:cc:1a:8b:02:cd:6c:e8:80:82:ca:bf:82:d7:00:9d:d8:36: + 15:d2:07:37:fc:6c:73:1d:da:a8:1c:e8:20:8e:32:7a:fe:6d: + 27:16:e4:58:6c:eb:3e:f0:fe:24:52:29:71:b8:96:7b:53:4b: + 45:20:55:40:5e:86:1b:ec:c9:46:91:92:ee:ac:93:65:91:2e: + 94:b6:b6:ac:e8:a3:34:89:a4:1a:12:0d:4d:44:a5:52:ed:8b: + 91:ee:2f:a6:af:a4:95:25:f9:ce:c7:5b:a7:00:d3:93:ca:b4: + 3c:5d:4d:f7:b1:3c:cc:6d:b4:45:be:82:ed:18:90:c8:86:d1: + 75:51:50:04:4c:e8:4f:d2:d6:50:aa:75:e7:5e:ff:a1:7b:27: + 19:1c:6b:49:2c:6c:4d:6b:63:cc:3b:73:00:f3:99:26:d0:82: + 87:d3:a9:36:9c:b4:3d:b9:48:68:a8:92:f0:27:8e:0c:cd:15: + 76:42:84:80:c9:3f:a3:7e:b4:dd:d7:f8:ac:76:ba:60:97:3c: + 5a:1f:4e:de:71:ee:09:39:10:dd:31:d5:68:57:5d:1a:e5:42: + ba:0e:68:e6:45:d3 -----BEGIN CERTIFICATE----- -MIIEbTCCAtWgAwIBAgIJAMstgJlaaVJbMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV +MIIEgDCCAuigAwIBAgIJAMstgJlaaVJbMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNV BAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUgRm91bmRhdGlvbiBDQTEW MBQGA1UEAwwNb3VyLWNhLXNlcnZlcjAeFw0xODA4MjkxNDIzMTZaFw0zNzEwMjgx NDIzMTZaME0xCzAJBgNVBAYTAlhZMSYwJAYDVQQKDB1QeXRob24gU29mdHdhcmUg Rm91bmRhdGlvbiBDQTEWMBQGA1UEAwwNb3VyLWNhLXNlcnZlcjCCAaIwDQYJKoZI -hvcNAQEBBQADggGPADCCAYoCggGBALGE009cBICRT4JJujAL9+jL+RTvPZ8LPwpi -/BsgpSDRYF+HWh8W0e2XcKbaGwMsfqBbPE4vFn4OiSmJ4RANONpqd183E7Moj3tc -dq2e6NP1nvWDqhAHjeZRmPB8DVLyDCEe2LmZJqklAye7XKsuMyei1iOog4dEKZ+X -tSRv17kK/Sjuu/tBWOodmd1EhquYvhzcy6mJHTZcqehHtfRSSKq1pGfvPtfi0zPe -mCnYerBZXOexDsz9n+v21ToOC8/+Cz2iv0UYzpTnqVVgiNTYhFB5BS5BA3SuZyb2 -WxIImM4Kl+0BD4lPF1z6Ph01JEeSMr/3pBgrPNBImeGizaPMUFMgtcbjZoV7VxDs -M0/Bd+cbfoHGxPNFIMCR3RN2ewOv9naOooNjV91jvLtaHBdSitYGSMwPx9NP6Noi -bIb5TlymKQc72FZMWbMgSQd7lITPK8McGk6HZJK6QuHmrX0d9lSQbyvps8xLKzMm -I/1lwDzwea3JwYHvNwTgJz6w7hW+UQIDAQABo1AwTjAdBgNVHQ4EFgQUs4qgorpx -8agkedSkWyU2FR5JyM0wHwYDVR0jBBgwFoAUs4qgorpx8agkedSkWyU2FR5JyM0w -DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAYEAazIv5wUY6lzJlfTgwgxB -XxoKlcnHfQXuilYpNVBAt/6fe1scw2kvoMvSuJEvUBli9ycYbZV7UxYVolrcFOP7 -sTKpadumM0c8ux/S3HD5ai4M2Ixt5V0dQzxOkd6gyNqgSw6dXrYPSknwe7ZTnv01 -FFvjTbQYpjZh6I8zm9QF+VRm3+DLGKNO3BeooLPBqPTWncp/aFMa15Xa6NOeSABx -lZkRB8+WwH3OfTDoT+GDFjOh/1mbPkznOjgBnw9nTP0ti0rUAUY3M+gTaxWpHWh2 -RaKCM2kmMGAFyI+9tHWrvnqLSGhwQLQbUcXmeq1rT9sXwGBnLmNhmyxImbh2RaCe -zO8zHlBOq3LDZciyebM1gyF404tsOhjoZTI5uMCdcS81NorAF2LYiz7hIhgrTGOm -Dp0K+qtbNfuIkXdMjYydqc/8q8LmWgV7fgRuOc+Tzmc7esuvtjbh+3FkRdSm8M7v -dQSZaZrliAoQAnSJ7HWERIBI38H36TfOzpKSXIkiCHMf +hvcNAQEBBQADggGPADCCAYoCggGBANCgm7G5O3nuMS+4URwBde0JWUysyL9qCvh6 +CPAl4yV7avjE2KqgYAclsM9zcQVSaL8Gk64QYZa8s2mBGn0Z/CCGj5poG+3N4mxh +Z8dOVepDBiEb6bm+hF/C2uuJiOBCpkVJKtC5a4yTyUQ7yvw8lH/dcMWt2Es73B74 +VUu1J4b437CDz/cWN78TFzTUyVXtaxbJf60gTvAe2Ru/jbrNypbvHmnLUWZhSA3o +eaNZYdQQjeANOwuFttWFEt2lB8VL+iP6VDn3lwvJREceVnc8PBMBC2131hS6RPRT +NVbZPbk+NV/bM5pPWrk4RMkySf5m9h8al6rKTEr2uF5Af/sLHfhbodz4wC7QbUn1 +0kbUkFf+koE0ri04u6gXDOHlP+L3JgVUUPVksxxuRP9vqbQDlukOwojYclKQmcZB +D0aQWbg+b9Linh02gpXTWIoS8+LYDSBRI/CQLZo+fSaGsqfX+ShgA+N3x4gEyf6J +d3AQT8Ogijv0q0J74xSS2K4W1qHefQIDAQABo2MwYTAdBgNVHQ4EFgQU8+yUjvKO +MMSOaMK/jmoZwMGfdmUwHwYDVR0jBBgwFoAU8+yUjvKOMMSOaMK/jmoZwMGfdmUw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggGBAIsAVHKzjevzrzSf1mDq3oQ/jASPGaa+AmfEY8V040c3WYOUBvFFGegHL9ZO +S0+oPccHByeS9H5zT4syGZRGeiXE2cQnsBFjOmCLheFzTzQ7a6Q0jEmOzc9PsmUn +QRmw/IAxePJzapt9cTRQ/Hio2gW0nFs6mXprXe870+k7MwESZc9eB9gZr9VT6vAQ +rMS2Jjw0LnTuZN0dNnWJRACwDf0vswHMGosCzWzogILKv4LXAJ3YNhXSBzf8bHMd +2qgc6CCOMnr+bScW5Fhs6z7w/iRSKXG4lntTS0UgVUBehhvsyUaRku6sk2WRLpS2 +tqzoozSJpBoSDU1EpVLti5HuL6avpJUl+c7HW6cA05PKtDxdTfexPMxttEW+gu0Y +kMiG0XVRUARM6E/S1lCqdede/6F7Jxkca0ksbE1rY8w7cwDzmSbQgofTqTactD25 +SGiokvAnjgzNFXZChIDJP6N+tN3X+Kx2umCXPFofTt5x7gk5EN0x1WhXXRrlQroO +aOZF0w== -----END CERTIFICATE----- diff --git a/Lib/test/certdata/pycakey.pem b/Lib/test/certdata/pycakey.pem index 819bdef1ff9bfc7..a6bf7356f4f6849 100644 --- a/Lib/test/certdata/pycakey.pem +++ b/Lib/test/certdata/pycakey.pem @@ -1,40 +1,40 @@ -----BEGIN PRIVATE KEY----- -MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQCxhNNPXASAkU+C -SbowC/foy/kU7z2fCz8KYvwbIKUg0WBfh1ofFtHtl3Cm2hsDLH6gWzxOLxZ+Dokp -ieEQDTjaandfNxOzKI97XHatnujT9Z71g6oQB43mUZjwfA1S8gwhHti5mSapJQMn -u1yrLjMnotYjqIOHRCmfl7Ukb9e5Cv0o7rv7QVjqHZndRIarmL4c3MupiR02XKno -R7X0UkiqtaRn7z7X4tMz3pgp2HqwWVznsQ7M/Z/r9tU6DgvP/gs9or9FGM6U56lV -YIjU2IRQeQUuQQN0rmcm9lsSCJjOCpftAQ+JTxdc+j4dNSRHkjK/96QYKzzQSJnh -os2jzFBTILXG42aFe1cQ7DNPwXfnG36BxsTzRSDAkd0TdnsDr/Z2jqKDY1fdY7y7 -WhwXUorWBkjMD8fTT+jaImyG+U5cpikHO9hWTFmzIEkHe5SEzyvDHBpOh2SSukLh -5q19HfZUkG8r6bPMSyszJiP9ZcA88HmtycGB7zcE4Cc+sO4VvlECAwEAAQKCAYB7 -gUnzALYxLOgAYYMkQm9si9zz768TpCNr+ooj5YZ9Wq6OSAEveBT+FErQCxaYErDW -qCNA0gn4Eezj9YWcQVa4vzHmEM+n6iRJU39ONC0Qqua5Ma10EY1sHIEnb2dlufku -YeOu3RrEu3eCgRxsDGySuvv5OxinV4kN++KPQzD3EOopPE+U81YFLCsMgsyfPlmm -gwc/IKIuXDHp5Vp2bXkZK98CYLV8RddjUw7SrkZNwx6cI9eET0CgTs7y4SrevoOy -jCdnA0j1HvL8AbLQuYoXo9fdGYDeq55hyYlxSMYLaEToZG3DJ0UAldrT+r7x52D8 -2QMnJUo2XHzVYPlXPJIAkFJisZZ36TkBvywCgXZMMLibPo9U6V0nfkybTtXKoory -nmgBv+XSGSNrVWMiygpDPqpX1G6bBgqUX3CiTlxtSkYYz1M4Vgj2cux5XEPTnVCq -CLVzvNIXZt1RyzXPxGWpPidCjOaiWBRT4u1Dol9fs3PmVvDaRxcKo9nspiUHCfEC -gcEA4GgxZ+IJwpAMHkdYId0oxjKgTqIg+Ua+EwfUoQT10ERl/k/V4cDwJRHT8lML -rKhTNQJMEE040jq+6mPJDl1KqMb/v05Q7fF22ToGw1HkZwK52O6CeEiJW4/J6bR1 -pZGN0irsa6GvzV65Y6gZVFEUl0JPRf8wPvQHXsWAw8/2LuXkXjV0ieIMq4pbWJf4 -kaid7dYLHnobiP9RVk7BGr7ifmCshoPjWp4TRMwYf6iIZrqMxUSX0QY8Xsqx6bch -LLx/AoHBAMqCvvwUKTrF4gKh5jyl6T6DTZ/Dujaz7BuAJdsSSHvuTa/Y1EfsQHZN -jABn89ZqHYDiyyCuVFO3dqhLtsPjhyFMSXj+98JYcL3FGKnqQqRTwtzzx2P2lV5X -U0WhrNRb3iLu79Tr8pE/2EPnvTr+J5b0DHEeRyM72LWs43zrDYHorH0/Aa5Qd37F -gDLCTBEl8jO5irRuAIq/KV9ZFnn8JDjNGVpXgHPW3354ON1YaMLnPASk7FQizSOQ -QZAsyxtdLwKBwGUosvTYYXvygXP4x1LkpmfKFJe94E1exXpAsmovmTvcSXn9tTXC -Sr77LWb0ZrPbYT7pHS7QEMg8MSnp941hIrG4mzs666KHkgLUdI4B0YtaIDsZMXlV -gY3j4KpYbhxH4/2U2eSfC2fxxnKVKW3n6vdQrfmo0q/eQ6BGOgiLK7fybCLHyBQL -8Zg2k3z5bNUEhMTdE0AW3WjBZ4IXmFcdK26616r/szJ7RcZilrydVXexqpmWlTVl -sTst9kucAPlwswKBwQCwf7my/GNezR8Jik+fZj7edBQQfcdra+8JnOvhfpLcKLte -2s1RjjA0q6usou1bYAsszP2bEzV97XWmgq7dFg4tUE7s/NO1d91zGDhBx2Gj1TkN -2A5dKonOuq9iDeITB6qYqcUvvyEfxRRZQr2jj+WzZCr/4BLCO6PJ29A9jKOuKLtF -QcfWRF2RiNMN6lffzkHFIR4p2YHxa2DEsGGtmbt8Ig3Jtl/HFmydzmxJRoev71dY -+ODdB6PhLhZmcRPoWpMCgcEAhGArwL68GwwRMqAX79gMv8tVT0CJnDyGk5mD/ZIB -Nzt0yQFO7rTEa1l1vAtOiVJ9IpAak2lgbEwodOfGnQst7lujNYDFzTRPTFt/lID1 -u6JBxmqawOSlqa00bt4l2YsTZV+BfSznBP6XO1PK4iR3o5G3NkoKJjZWm3e3asHk -6eTeMLcsIJ+Fp7gG0ve2EdQwhVSVMFEu4Q4C2FcJeU++L4kYpY7sTnAjUtiLvtHn -yp3jllEn3CBD8Uhs4B+sL/6p +MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQDQoJuxuTt57jEv +uFEcAXXtCVlMrMi/agr4egjwJeMle2r4xNiqoGAHJbDPc3EFUmi/BpOuEGGWvLNp +gRp9Gfwgho+aaBvtzeJsYWfHTlXqQwYhG+m5voRfwtrriYjgQqZFSSrQuWuMk8lE +O8r8PJR/3XDFrdhLO9we+FVLtSeG+N+wg8/3Fje/Exc01MlV7WsWyX+tIE7wHtkb +v426zcqW7x5py1FmYUgN6HmjWWHUEI3gDTsLhbbVhRLdpQfFS/oj+lQ595cLyURH +HlZ3PDwTAQttd9YUukT0UzVW2T25PjVf2zOaT1q5OETJMkn+ZvYfGpeqykxK9rhe +QH/7Cx34W6Hc+MAu0G1J9dJG1JBX/pKBNK4tOLuoFwzh5T/i9yYFVFD1ZLMcbkT/ +b6m0A5bpDsKI2HJSkJnGQQ9GkFm4Pm/S4p4dNoKV01iKEvPi2A0gUSPwkC2aPn0m +hrKn1/koYAPjd8eIBMn+iXdwEE/DoIo79KtCe+MUktiuFtah3n0CAwEAAQKCAYAD +iUK0/k2ZRqXJHXKBKy8rWjYMHCj3lvMM/M3g+tYWS7i88w00cIJ1geM006FDSf8i +LxjatvFd2OCg9ay+w8LSbvrJJGGbeXAQjo1v7ePRPttAPWphQ8RCS+8NAKhJcNJu +UzapZ13WJKfL2HLw1+VbziORXjMlLKRnAVDkzHMZO70C5MEQ0EIX+C6zrmBOl2HH +du6LPy8crSaDQg8YxFCI7WWnvRKp+Gp8aIfYnR+7ifT1qr5o9sEUw8GAReyooJ3a +yJ9uBUbcelO8fNjEABf9xjx+jOmOVsQfig2KuBEi0qXlQSpilZfUdYJhtNke9ADu +Hui6MBn04D4RIzeKXV+OLjiLwqkJyNlPuxJ2EGpIHNMcx3gpjXIApAwc47BQwLKJ +VhMWMXS0EWhCLtEzf5UrbMNX+Io3J7noEUu6jxmJV1BKhrnlYeoo4JryN0DUpkSb +rOAOJLOkpfj7+gvqmWI4MT6SQXSr6BK+3m4J5bVSq4pej9uG5NR3Utghi5hF7DEC +gcEA3cYNPYPFSTj9YAR3GUZvwUPVL3ZEFcwjrIeg87JhuZOH/hSQ33SgeEoAtaqL +cLbimj7YzUYx3FOUCp/7yK/bAF1dhAbFab1yZE46Qv2Vi4e+/KEBBftqxyJl5KyV +vc/HE1dXZGZIO1X5Z5MX8nO3rz/YayiozYVmMibrbHxgTEDC4BrbWtPJQNkflWEb +FXNjkm0s2+J3kFANpL94NUKMGtArxQV3hWydGN8wS3Fn7LDnHDoM5mOt/naeKRES +fwwpAoHBAPDTKsKs2LEe4YFzO1EClycDelppjxh5pHSnzTWSq40aKx533SG4aLyI +DmghzoA3OmY0xpAy1WpT9FeiDNbYpiFCH3qBkArQR2QCu+WGUQ9tDoeN0C2Dje4e +Yix49BjcGSWzSNvh+tU9PzRc/9eVBMAQuaCm3yNEL+Z7hFTzkrCWK23+jP/OzIIC +XhnKdOveIYVAjlVgv8CoWIy3xhwXyqPAcstcPmlv9sDAYn37Ot7rGIS7e0WyQxvg +gxnOxFzKNQKBwQDOPOn/NNV5HKh0bHKdbKVs4zoT4zW515etUIvbVR4QSCSFonZ/ +d6PreVZjmvAFp+3fZ2aSrx6bOJZJszGhFfjhw/G9X9aiWO1SXnVL6yrxERIJOWkM +ORy5h0GegOjYFauaTvUUhxHRLEi9i0sPy5EcRpFqReuFBPNe3Fa/EoMzJl6TriYj +tyRHTCNU9XMMZbxJZYH8EgUCjY/Cj9SoIvTL0p+Bn23hBHqrsJLm9dWhhXnHBC0O +68/Y/lJi+l9rCtECgcEAt6PfTJovl0j8HxF23vyBtK9TQtSR2NERlh9LPZn9lViq +Hs66YndT7sg1bDSzWlRDBSMjc1xAH5erkJOzBLYqYNwiUvGvnH9coSfwjkMRVxkL +ZlS+taZGuZiTtmP5h2d3CaegXIQDGU5d/xkXwxYQjEF0u8vkBel+OVxg+cLPTjcF +IRhl/r98dXtGtJYM+LvnhcxHfVWMg2YcOBn/SPbfgGVFZEuQECjf2fYaZQUJzGkr +xjOM+gXIZN6cOjbQyA0tAoHADgR5/bMbcf6Jk0w56c/khFZz/pusne5cjXw5a6qq +fClAqnqjGBpkRxs7HoCR3aje0Pd0pCS93a6Wiqneo4x4HDrpo+pWR2KGAAF4MeO3 +3K94hncmiLAiZo8iqULLKCqJW2EGB2b7QzGpY7jCPiI1g80KuYPesf4ZohSfrr1w +DoqGoNrcIVdVmUgX47lLqIiWarbbDRY0Am9j58dovmNINYr5wCYGbeh2RuUmHr4u +E2bb0CdekSHf05HPiF9QpK1z -----END PRIVATE KEY----- diff --git a/Lib/test/certdata/revocation.crl b/Lib/test/certdata/revocation.crl index 621675eb5c183dc..431a831c4f196a1 100644 --- a/Lib/test/certdata/revocation.crl +++ b/Lib/test/certdata/revocation.crl @@ -1,14 +1,14 @@ -----BEGIN X509 CRL----- MIICJjCBjwIBATANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQGEwJYWTEmMCQGA1UE CgwdUHl0aG9uIFNvZnR3YXJlIEZvdW5kYXRpb24gQ0ExFjAUBgNVBAMMDW91ci1j -YS1zZXJ2ZXIXDTIxMDMxNzA4NDgyMFoXDTQwMDUxNjA4NDgyMFqgDjAMMAoGA1Ud -FAQDAgEAMA0GCSqGSIb3DQEBCwUAA4IBgQCd2GrHb4zr2R8eK7YMHwlkgICxbWP1 -4nuEi55yzUcmMcCZJ6ZQV3yYqTlAULGQ9qWAUdhsyH+yu3hRKFKHQv0DAdKKxgow -66YasAQQ99DskXOPxmRoIA7qtIWZbLtBwHQJWh+uUFlTdUXitGIX5Xie74xu5YIr -moa3QeuZyG5+gigSTUyst5T/J/cHfBzlAJLc2k3Ty4EPYXKHCVnrZWJbRmxq199l -A7S+eBb9qWXSYXCn6v+EZ76pUS3u/66kZ86PO3h9294BzdhxbCJ27dQXNHw6owe2 -Iyiv0aWx+TNSGSf4yCqaYTH6RtEoviI3h/inVFHNGgjlMzdaGw/0I3bkB0rt2WSR -Vck37HnXvQvVEkgO/39C0WKZus6m4gmOgZcbJbXaR8uIR5Hmw3SEyGEPEIBu6tXV -BLJOSOSu2vVUH5GUIrpvK9FTySKYa+MGryoPasuqZNfwpaXK+ON2G6QsmcXPWZY0 -Dry6t0w2geW6UYVGmb831i8ZP3JVVVwcwi0= +YS1zZXJ2ZXIXDTIzMTEyNTA0MjEzNloXDTQzMDEyNDA0MjEzNlqgDjAMMAoGA1Ud +FAQDAgEAMA0GCSqGSIb3DQEBCwUAA4IBgQDMZ4XLQlzUrqBbszEq9I/nXK3jN8/p +VZ2aScU2le0ySJqIthe0yXEYuoFu+I4ZULyNkCA79baStIl8/Lt48DOHfBVv8SVx +ZqF7/fdUZBCLJV1kuhuSSknbtNmja5NI4/lcRRXrodRWDMcOmqlKbAC6RMQz/gMG +vpewGPX1oj5AQnqqd9spKtHbeqeDiyyWYr9ZZFO/433lP7GdsoriTPggYJJMWJvs +819buE0iGwWf+rTLB51VyGluhcz2pqimej6Ra2cdnYh5IztZlDFR99HywzWhVz/A +2fwUA91GR7zATerweXVKNd59mcgF4PZWiXmQMwcE0qQOMqMmAqYPLim1mretZsAs +t1X+nDM0Ak3sKumIjteQF7I6VpSsG4NCtq23G8KpNHnBZVOt0U065lQEvx0ZmB94 +1z7SzjfSZMVXYxBjSXljwuoc1keGpNT5xCmHyrOIxaHsmizzwNESW4dGVLu7/JfK +w40uGbwH09w4Cfbwuo7w6sRWDWPnlW2mkoc= -----END X509 CRL----- diff --git a/Lib/test/certdata/ssl_cert.pem b/Lib/test/certdata/ssl_cert.pem index de596717bd855f0..427948252b786e0 100644 --- a/Lib/test/certdata/ssl_cert.pem +++ b/Lib/test/certdata/ssl_cert.pem @@ -1,26 +1,27 @@ -----BEGIN CERTIFICATE----- -MIIEWTCCAsGgAwIBAgIJAJinz4jHSjLtMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV -BAYTAlhZMRcwFQYDVQQHDA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9u -IFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xODA4 -MjkxNDIzMTVaFw0yODA4MjYxNDIzMTVaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQH -DA5DYXN0bGUgQW50aHJheDEjMCEGA1UECgwaUHl0aG9uIFNvZnR3YXJlIEZvdW5k -YXRpb24xEjAQBgNVBAMMCWxvY2FsaG9zdDCCAaIwDQYJKoZIhvcNAQEBBQADggGP -ADCCAYoCggGBALKUqUtopT6E68kN+uJNEt34i2EbmG/bwjcD8IaMsgJPSsMO2Bpd -3S6qWgkCeOyCfmAwBxK2kNbxGb63ouysEv7l8GCTJTWv3hG/HQcejJpnAEGi6K1U -fDbyE/db6yZ12SoHVTGkadN4vYGCPd1Wj9ZO1F877SHQ8rDWX3xgTWkxN2ojBw44 -T8RHSDiG8D/CvG4uEy+VUszL+Uvny5y2poNSqvI3J56sptWSrh8nIIbkPZPBdUne -LYMOHTFK3ZjXSmhlXgziTxK71nnzM3Y9K9gxPnRqoXbvu/wFo55hQCkETiRkYgmm -jXcBMZ0TClQVnQWuLjMthRnWFZs4Lfmwqjs7FZD/61581R2BYehvpWbLvvuOJhwv -DFzexL2sXcAl7SsxbzeQKRHqGbIDfbnQTXfs3/VC6Ye5P82P2ucj+XC32N9piRmO -gCBP8L3ub+YzzdxikZN2gZXXE2jsb3QyE/R2LkWdWyshpKe+RsZP1SBRbHShUyOh -yJ90baoiEwj2mwIDAQABoxgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZI -hvcNAQELBQADggGBAHRUO/UIHl3jXQENewYayHxkIx8t7nu40iO2DXbicSijz5bo -5//xAB6RxhBAlsDBehgQP1uoZg+WJW+nHu3CIVOU3qZNZRaozxiCl2UFKcNqLOmx -R3NKpo1jYf4REQIeG8Yw9+hSWLRbshNteP6bKUUf+vanhg9+axyOEOH/iOQvgk/m -b8wA8wNa4ujWljPbTQnj7ry8RqhTM0GcAN5LSdSvcKcpzLcs3aYwh+Z8e30sQWna -F40sa5u7izgBTOrwpcDm/w5kC46vpRQ5fnbshVw6pne2by0mdMECASid/p25N103 -jMqTFlmO7kpf/jpCSmamp3/JSEE1BJKHwQ6Ql4nzRA2N1mnvWH7Zxcv043gkHeAu -0x8evpvwuhdIyproejNFlBpKmW8OX7yKTCPPMC/VkX8Q1rVkxU0DQ6hmvwZlhoKa -9Wc2uXpw9xF8itV4Uvcdr3dwqByvIqn7iI/gB+4l41e0u8OmH2MKOx4Nxlly5TNW -HcVKQHyOeyvnINuBAQ== +MIIEgzCCAuugAwIBAgIUU+FIM/dUbCklbdDwNPd2xemDAEwwDQYJKoZIhvcNAQEL +BQAwXzELMAkGA1UEBhMCWFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYD +VQQKDBpQeXRob24gU29mdHdhcmUgRm91bmRhdGlvbjESMBAGA1UEAwwJbG9jYWxo +b3N0MB4XDTIzMTEyNTA0MjEzNloXDTQzMDEyNDA0MjEzNlowXzELMAkGA1UEBhMC +WFkxFzAVBgNVBAcMDkNhc3RsZSBBbnRocmF4MSMwIQYDVQQKDBpQeXRob24gU29m +dHdhcmUgRm91bmRhdGlvbjESMBAGA1UEAwwJbG9jYWxob3N0MIIBojANBgkqhkiG +9w0BAQEFAAOCAY8AMIIBigKCAYEAzXTIl1su11AGu6sDPsoxqcRGyAX0yjxIcswF +vj+eW/fBs2GcBby95VEOKpJPKRYYB7fAEAjAKK59zFdsDX/ynxPZLqyLQocBkFVq +tclhCRZu//KZND+uQuHSx3PjGkSvK/nrGjg5T0bkM4SFeb0YdLb+0aDTKGozUC82 +oBAilNcrFz1VXpEF0qUe9QeKQhyd0MaW5T1oSn+U3RAj2MXm3TGExyZeaicpIM5O +HFlnwUxsYSDZo0jUj342MbPOZh8szZDWi042jdtSA3i8uMSplEf4O8ZPmX0JCtrz +fVjRVdaKXIjrhMNWB8K44q6AeyhqJcVHtOmPYoHDm0qIjcrurt0LZaGhmCuKimNd +njcPxW0VQmDIS/mO5+s24SK+Mpznm5q/clXEwyD8FbrtrzV5cHCE8eNkxjuQjkmi +wW9uadK1s54tDwRWMl6DRWRyxoF0an885UQWmbsgEB5aRmEx2L0JeD0/q6Iw1Nta +As8DG4AaWuYMrgZXz7XvyiMq3IxVAgMBAAGjNzA1MBQGA1UdEQQNMAuCCWxvY2Fs +aG9zdDAdBgNVHQ4EFgQUl2wd7iWE1JTZUVq2yFBKGm9N36owDQYJKoZIhvcNAQEL +BQADggGBAF0f5x6QXFbgdyLOyeAPD/1DDxNjM68fJSmNM/6vxHJeDFzK0Pja+iJo +xv54YiS9F2tiKPpejk4ujvLQgvrYrTQvliIE+7fUT0dV74wZKPdLphftT9uEo1dH +TeIld+549fqcfZCJfVPE2Ka4vfyMGij9hVfY5FoZL1Xpnq/ZGYyWZNAPbkG292p8 +KrfLZm/0fFYAhq8tG/6DX7+2btxeX4MP/49tzskcYWgOjlkknyhJ76aMG9BJ1D7F +/TIEh5ihNwRTmyt023RBz/xWiN4xBLyIlpQ6d5ECKmFNFr0qnEui6UovfCHUF6lZ +qcAQ5VFQQ2CayNlVmQ+UGmWIqANlacYWBt7Q6VqpGg24zTMec1/Pqd6X07ScSfrm +MAtywrWrU7p1aEkN5lBa4n/XKZHGYMjor/YcMdF5yjdSrZr274YYO1pafmTFwRwH +5o16c8WPc0aPvTFbkGIFT5ddxYstw+QwsBtLKE2lJ4Qfmxt0Ew/0L7xkbK1BaCOo +EGD2IF7VDQ== -----END CERTIFICATE----- diff --git a/Lib/test/certdata/ssl_key.passwd.pem b/Lib/test/certdata/ssl_key.passwd.pem index 46de61ab85b1369..6ab7d57d003a35c 100644 --- a/Lib/test/certdata/ssl_key.passwd.pem +++ b/Lib/test/certdata/ssl_key.passwd.pem @@ -1,42 +1,42 @@ -----BEGIN ENCRYPTED PRIVATE KEY----- -MIIHbTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI072N7W+PDDMCAggA -MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBA/AuaRNi4vE4KGqI4In+70BIIH -ENGS5Vex5NID873frmd1UZEHZ+O/Bd0wDb+NUpIqesHkRYf7kKi6Gnr+nKQ/oVVn -Lm3JjE7c8ECP0OkOOXmiXuWL1SkzBBWqCI4stSGUPvBiHsGwNnvJAaGjUffgMlcC -aJOA2+dnejLkzblq4CB2LQdm06N3Xoe9tyqtQaUHxfzJAf5Ydd8uj7vpKN2MMhY7 -icIPJwSyh0N7S6XWVtHEokr9Kp4y2hS5a+BgCWV1/1z0aF7agnSVndmT1VR+nWmc -lM14k+lethmHMB+fsNSjnqeJ7XOPlOTHqhiZ9bBSTgF/xr5Bck/NiKRzHjdovBox -TKg+xchaBhpRh7wBPBIlNJeHmIjv+8obOKjKU98Ig/7R9+IryZaNcKAH0PuOT+Sw -QHXiCGQbOiYHB9UyhDTWiB7YVjd8KHefOFxfHzOQb/iBhbv1x3bTl3DgepvRN6VO -dIsPLoIZe42sdf9GeMsk8mGJyZUQ6AzsfhWk3grb/XscizPSvrNsJ2VL1R7YTyT3 -3WA4ZXR1EqvXnWL7N/raemQjy62iOG6t7fcF5IdP9CMbWP+Plpsz4cQW7FtesCTq -a5ZXraochQz361ODFNIeBEGU+0qqXUtZDlmos/EySkZykSeU/L0bImS62VGE3afo -YXBmznTTT9kkFkqv7H0MerfJsrE/wF8puP3GM01DW2JRgXRpSWlvbPV/2LnMtRuD -II7iH4rWDtTjCN6BWKAgDOnPkc9sZ4XulqT32lcUeV6LTdMBfq8kMEc8eDij1vUT -maVCRpuwaq8EIT3lVgNLufHiG96ojlyYtj3orzw22IjkgC/9ee8UDik9CqbMVmFf -fVHhsw8LNSg8Q4bmwm5Eg2w2it2gtI68+mwr75oCxuJ/8OMjW21Prj8XDh5reie2 -c0lDKQOFZ9UnLU1bXR/6qUM+JFKR4DMq+fOCuoQSVoyVUEOsJpvBOYnYZN9cxsZm -vh9dKafMEcKZ8flsbr+gOmOw7+Py2ifSlf25E/Frb1W4gtbTb0LQVHb6+drutrZj -8HEu4CnHYFCD4ZnOJb26XlZCb8GFBddW86yJYyUqMMV6Q1aJfAOAglsTo1LjIMOZ -byo0BTAmwUevU/iuOXQ4qRBXXcoidDcTCrxfUSPG9wdt9l+m5SdQpWqfQ+fx5O7m -SLlrHyZCiPSFMtC9DxqjIklHjf5W3wslGLgaD30YXa4VDYkRihf3CNsxGQ+tVvef -l0ZjoAitF7Gaua06IESmKnpHe23dkr1cjYq+u2IV+xGH8LeExdwsQ9kpuTeXPnQs -JOA99SsFx1ct32RrwjxnDDsiNkaViTKo9GDkV3jQTfoFgAVqfSgg9wGXpqUqhNG7 -TiSIHCowllLny2zn4XrXCy2niD3VDt0skb3l/PaegHE2z7S5YY85nQtYwpLiwB9M -SQ08DYKxPBZYKtS2iZ/fsA1gjSRQDPg/SIxMhUC3M3qH8iWny1Lzl25F2Uq7VVEX -LdTUtaby49jRTT3CQGr5n6z7bMbUegiY7h8WmOekuThGDH+4xZp6+rDP4GFk4FeK -JcF70vMQYIjQZhadic6olv+9VtUP42ltGG/yP9a3eWRkzfAf2eCh6B1rYdgEWwE8 -rlcZzwM+y6eUmeNF2FVWB8iWtTMQHy+dYNPM+Jtus1KQKxiiq/yCRs7nWvzWRFWA -HRyqV0J6/lqgm4FvfktFt1T0W+mDoLJOR2/zIwMy2lgL5zeHuR3SaMJnCikJbqKS -HB3UvrhAWUcZqdH29+FhVWeM7ybyF1Wccmf+IIC/ePLa6gjtqPV8lG/5kbpcpnB6 -UQY8WWaKMxyr3jJ9bAX5QKshchp04cDecOLZrpFGNNQngR8RxSEkiIgAqNxWunIu -KrdBDrupv/XAgEOclmgToY3iywLJSV5gHAyHWDUhRH4cFCLiGPl4XIcnXOuTze3H -3j+EYSiS3v3DhHjp33YU2pXlJDjiYsKzAXejEh66++Y8qaQdCAad3ruWRCzW3kgk -Md0A1VGzntTnQsewvExQEMZH2LtYIsPv3KCYGeSAuLabX4tbGk79PswjnjLLEOr0 -Ghf6RF6qf5/iFyJoG4vrbKT8kx6ywh0InILCdjUunuDskIBxX6tEcr9XwajoIvb2 -kcmGdjam5kKLS7QOWQTl8/r/cuFes0dj34cX5Qpq+Gd7tRq/D+b0207926Cxvftv -qQ1cVn8HiLxKkZzd3tpf2xnoV1zkTL0oHrNg+qzxoxXUTUcwtIf1d/HRbYEAhi/d -bBBoFeftEHWNq+sJgS9bH+XNzo/yK4u04B5miOq8v4CSkJdzu+ZdF22d4cjiGmtQ -8BTmcn0Unzm+u5H0+QSZe54QBHJGNXXOIKMTkgnOdW27g4DbI1y7fCqJiSMbRW6L -oHmMfbdB3GWqGbsUkhY8i6h9op0MU6WOX7ea2Rxyt4t6 +MIIHbTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIsc9l0YPybNICAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBDxb9ekR9MERvIff73hFLc6BIIH +ENhkFePApZj7ZqpjBltINRnaZhu8sEfG1/y3ejDBOa5Sq3C/UPykPfJh0IXsraAB +STZO22UQEDpJzDnf1aLCo2cJpdz4Mr+Uj8OUdPiX83OlhC36gMrkgSYUdhSFQEas +MLiBnXU6Z5Mv1Lxe7TJrnMyA4A8JYXXu5XVTErJrC0YT6iCPQh7eAoEtml9a/tJM +OPg6kn58zmzVDp8LAau4Th1yhdD/cUQM09wg2i5JHLeC9akD+CkNlujVoAirLMTh +xoMXTy2dkv/lIwI9QVx6WE/VKIngBAPIi3Q+YCIm0PaTgWj5U10C8j4t7kW2AEZK +z82+vDOpLRGLo/ItNCO9F/a9e4PK4xxwFCOfR80tQNhs5gjKnbDz5IQv2p+pUfUX +u+AIO0rBb3M9Yya1MC2pc5VLAeQ3UF6YPrNyNjoDsQOytY3YtRVyxiKW72QzeUcX +Vpc3U6u8ZyHhkxK6bMv3dkPHGW1MOBd9/U5z+9lhHOfCGFStIQ9M8N48ZCWEGyty +oZT3UApxgqiBAi1h14ZyagA2mjsMNtTmmkSa3v26WUfrwnjm7LD1/0Vm+ptBOFH2 +CkP/aAvr8Ie+ehWobXGpqwB6rlOAwdpPrePtEZiZtdt58anmCquRgE5GIYtVz30f +flRABM8waJ196RDGkNAmDA3p/sqHy4vbsIOMl8faZ3QxvGVZlPbUEwPhiTIetA5Q +95fT/uIcuBLfpbaN23j/Av3LiJAeABSmGZ+dA+NXC5UMvuX8COyBU0YF2V6ofpIu +gP3UC7Tn4yV3Pbes81LEDCskaN6qVRil47l0G+dNcEHVkrGKcSaRCN+joBSCbuin +Rol34ir9azh8DqHRKdVlLlzTmDQcOwmi0Vx0ASgBXx4UI3IfK45gLJVoz6dkUz+3 +GIPrnh5cw2DvIgIApwmuCQUXPbWZwUW0zuyzhtny9W6S72GUE/P5oUCV+kGYBsup +FNiAyR9+n/xUuzB5HqIosj4rX+M4il4Ovt+KaCO6/COi+YjAO/9EnSttu8OTxsXl +wvgblsT7Y1d+iUfmIVNGtbc5NX46ktrbGiqgPX7oR7YDy5/+FQlnPS1YL0ThUiAC +2RbItu6b0uUyfu2jfWaGqy+SiRZ81rLwKPU3vJSEPfooVcJTG49EE006ZC4TvRzu +fNkId+P+BxvhEpUM4+VKzfzViEPuzR1u/DuwLAavS7nr5qb+zaUq+Fte5vDQmjjC +fflT8hS0BGpYEGndeZT4k+mZunHgs3NVUQ4/HW0nflf1j6qAn4+yIB79dH9d/ubt +RyBG29K+rN0TI/kH9BQZfsAcbnmhpT/ud0mJfeHZ0Lknn6mdJ/k4LXN0T1IlLKz3 +cSleOWY3zjKaOsbuju1o5IiVIr+AF/w+M4nzzDX6DDVpBPAt9iUnDGqjh6mJ3QWQ +CyCJDLNP0X8rZ8va2KOPorIBhmfDwJKEtIoXkb2hqWURTE0chC444QqiMsMXsX6+ +mOmiWGkdBFnEpGITISFTGERCjEfqOgTMweCANpquiLymJXgDURL603N2WexSgwnu +Gy1Ws1cA+1cT65ZLqjSqayZ6WdQvsKBBAnGW5LbwBhoCkX0vahs5nZiw0KnskP60 +wNMnyxaS1SuDJ65n+vuLUl7WeysRyz10RWliYZFiUE7jIXfWeYGonAo4eyCEeV/f +HInxxpswsg/na8BGBPMsx2SfBIiIvSIT4VNxHrL3sIfDrnb2HH/ut/oSLBgSKzY5 +DdkPz309kMM5dqnHANAgRrtVhqzLQE3kNGZ9mO/X1FAyXx8eB7NSeB6ysD8CAHvm +lkyfsGTzVsnuWWpeHqplds0wx5+XouVtFRI5J3RGa39mbpM1hMyIbS0O24CBKW6K +7n2UunbABwepL1hSa4e01OPdz4Zx/oayOevTtlfVqh68cEEc6ePdzf7z69pjot7B +eqlNaqa1POOmkuygL+fiP1BAR3rGEoQKXqb+6JjzLM9CnhCQHHPR2UdqukkEYwsa +bh9CU8AlfAJ19KFDria4JZXtl8LLMLLqWIO8fmQx7VqkEkEkl8jecO8YMaZTzFEb +bW7QtIZ1qHWH0UIHH3Qlav72NJTKvGIbtp1JNrLdsHcYNcojLZkEeA83UPaiTB2R +udltVUd016cktRVzLOKrust8kzPq3iSjpoIXFyFqIYHvWxGHgc7qD5gVBlazqSsV +qudDv+0PCBjLWLjS6HkFI8BfyXd3ME2wvSmTzSSgSh4nVJNNrZ/RVTtQ5MLVcdh0 +sJ3qsq2Pokf61XXjsTiQorX+cgI9zF6zETXHvnLf9FL+G/VSlcLUsQ0wC584qwQt +OSASYTbM79xgmjRmolZOptcYXGktfi2C4iq6V6zpFJuNMVgzZ+SbaQw9bvzUo2jG +VMwrTuQQ+fsAyn66WZvtkSGAdp58+3PNq31ZjafJXBzN -----END ENCRYPTED PRIVATE KEY----- diff --git a/Lib/test/certdata/ssl_key.pem b/Lib/test/certdata/ssl_key.pem index 1ea4578d81ecc4c..ee927210511dfc5 100644 --- a/Lib/test/certdata/ssl_key.pem +++ b/Lib/test/certdata/ssl_key.pem @@ -1,40 +1,40 @@ -----BEGIN PRIVATE KEY----- -MIIG/wIBADANBgkqhkiG9w0BAQEFAASCBukwggblAgEAAoIBgQCylKlLaKU+hOvJ -DfriTRLd+IthG5hv28I3A/CGjLICT0rDDtgaXd0uqloJAnjsgn5gMAcStpDW8Rm+ -t6LsrBL+5fBgkyU1r94Rvx0HHoyaZwBBouitVHw28hP3W+smddkqB1UxpGnTeL2B -gj3dVo/WTtRfO+0h0PKw1l98YE1pMTdqIwcOOE/ER0g4hvA/wrxuLhMvlVLMy/lL -58uctqaDUqryNyeerKbVkq4fJyCG5D2TwXVJ3i2DDh0xSt2Y10poZV4M4k8Su9Z5 -8zN2PSvYMT50aqF277v8BaOeYUApBE4kZGIJpo13ATGdEwpUFZ0Fri4zLYUZ1hWb -OC35sKo7OxWQ/+tefNUdgWHob6Vmy777jiYcLwxc3sS9rF3AJe0rMW83kCkR6hmy -A3250E137N/1QumHuT/Nj9rnI/lwt9jfaYkZjoAgT/C97m/mM83cYpGTdoGV1xNo -7G90MhP0di5FnVsrIaSnvkbGT9UgUWx0oVMjocifdG2qIhMI9psCAwEAAQKCAYBT -sHmaPmNaZj59jZCqp0YVQlpHWwBYQ5vD3pPE6oCttm0p9nXt/VkfenQRTthOtmT1 -POzDp00/feP7zeGLmqSYUjgRekPw4gdnN7Ip2PY5kdW77NWwDSzdLxuOS8Rq1MW9 -/Yu+ZPe3RBlDbT8C0IM+Atlh/BqIQ3zIxN4g0pzUlF0M33d6AYfYSzOcUhibOO7H -j84r+YXBNkIRgYKZYbutRXuZYaGuqejRpBj3voVu0d3Ntdb6lCWuClpB9HzfGN0c -RTv8g6UYO4sK3qyFn90ibIR/1GB9watvtoWVZqggiWeBzSWVWRsGEf9O+Cx4oJw1 -IphglhmhbgNksbj7bD24on/icldSOiVkoUemUOFmHWhCm4PnB1GmbD8YMfEdSbks -qDr1Ps1zg4mGOinVD/4cY7vuPFO/HCH07wfeaUGzRt4g0/yLr+XjVofOA3oowyxv -JAzr+niHA3lg5ecj4r7M68efwzN1OCyjMrVJw2RAzwvGxE+rm5NiT08SWlKQZnkC -gcEA4wvyLpIur/UB84nV3XVJ89UMNBLm++aTFzld047BLJtMaOhvNqx6Cl5c8VuW -l261KHjiVzpfNM3/A2LBQJcYkhX7avkqEXlj57cl+dCWAVwUzKmLJTPjfaTTZnYJ -xeN3dMYjJz2z2WtgvfvDoJLukVwIMmhTY8wtqqYyQBJ/l06pBsfw5TNvmVIOQHds -8ASOiFt+WRLk2bl9xrGGayqt3VV93KVRzF27cpjOgEcG74F3c0ZW9snERN7vIYwB -JfrlAoHBAMlahPwMP2TYylG8OzHe7EiehTekSO26LGh0Cq3wTGXYsK/q8hQCzL14 -kWW638vpwXL6L9ntvrd7hjzWRO3vX/VxnYEA6f0bpqHq1tZi6lzix5CTUN5McpDg -QnjenSJNrNjS1zEF8WeY9iLEuDI/M/iUW4y9R6s3WpgQhPDXpSvd2g3gMGRUYhxQ -Xna8auiJeYFq0oNaOxvJj+VeOfJ3ZMJttd+Y7gTOYZcbg3SdRb/kdxYki0RMD2hF -4ZvjJ6CTfwKBwQDiMqiZFTJGQwYqp4vWEmAW+I4r4xkUpWatoI2Fk5eI5T9+1PLX -uYXsho56NxEU1UrOg4Cb/p+TcBc8PErkGqR0BkpxDMOInTOXSrQe6lxIBoECVXc3 -HTbrmiay0a5y5GfCgxPKqIJhfcToAceoVjovv0y7S4yoxGZKuUEe7E8JY2iqRNAO -yOvKCCICv/hcN235E44RF+2/rDlOltagNej5tY6rIFkaDdgOF4bD7f9O5eEni1Bg -litfoesDtQP/3rECgcEAkQfvQ7D6tIPmbqsbJBfCr6fmoqZllT4FIJN84b50+OL0 -mTGsfjdqC4tdhx3sdu7/VPbaIqm5NmX10bowWgWSY7MbVME4yQPyqSwC5NbIonEC -d6N0mzoLR0kQ+Ai4u+2g82gicgAq2oj1uSNi3WZi48jQjHYFulCbo246o1NgeFFK -77WshYe2R1ioQfQDOU1URKCR0uTaMHClgfu112yiGd12JAD+aF3TM0kxDXz+sXI5 -SKy311DFxECZeXRLpcC3AoHBAJkNMJWTyPYbeVu+CTQkec8Uun233EkXa2kUNZc/ -5DuXDaK+A3DMgYRufTKSPpDHGaCZ1SYPInX1Uoe2dgVjWssRL2uitR4ENabDoAOA -ICVYXYYNagqQu5wwirF0QeaMXo1fjhuuHQh8GsMdXZvYEaAITZ9/NG5x/oY08+8H -kr78SMBOPy3XQn964uKG+e3JwpOG14GKABdAlrHKFXNWchu/6dgcYXB87mrC/GhO -zNwzC+QhFTZoOomFoqMgFWujng== +MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQDNdMiXWy7XUAa7 +qwM+yjGpxEbIBfTKPEhyzAW+P55b98GzYZwFvL3lUQ4qkk8pFhgHt8AQCMAorn3M +V2wNf/KfE9kurItChwGQVWq1yWEJFm7/8pk0P65C4dLHc+MaRK8r+esaODlPRuQz +hIV5vRh0tv7RoNMoajNQLzagECKU1ysXPVVekQXSpR71B4pCHJ3QxpblPWhKf5Td +ECPYxebdMYTHJl5qJykgzk4cWWfBTGxhINmjSNSPfjYxs85mHyzNkNaLTjaN21ID +eLy4xKmUR/g7xk+ZfQkK2vN9WNFV1opciOuEw1YHwrjiroB7KGolxUe06Y9igcOb +SoiNyu6u3QtloaGYK4qKY12eNw/FbRVCYMhL+Y7n6zbhIr4ynOebmr9yVcTDIPwV +uu2vNXlwcITx42TGO5COSaLBb25p0rWzni0PBFYyXoNFZHLGgXRqfzzlRBaZuyAQ +HlpGYTHYvQl4PT+rojDU21oCzwMbgBpa5gyuBlfPte/KIyrcjFUCAwEAAQKCAYAO +M1r0+TCy4Z1hhceu5JdLql0RELZTbxi71IW2GVwW87gv75hy3hGLAs/1mdC+YIBP +MkBka1JqzWq0/7rgcP5CSAMsInFqqv2s7fZ286ERGXuZFbnInnkrNsQUlJo3E9W+ +tqKtGIM/i0EVHX0DRdJlqMtSjmjh43tB+M1wAUV+n6OjEtJue5wZK+AIpBmGicdP +qZY+6IBnm8tcfzPXFRCoq7ZHdIu0jxnc4l2MQJK3DdL04KoiStOkSl8xDsI+lTtq +D3qa41LE0TY8X2jJ/w6KK3cUeK7F4DQYs+kfCKWMVPpn0/5u6TbC1F7gLvkrseph +7cIgrruNNs9iKacnR1w3U72R+hNxHsNfo4RGHFa192p/Mfc+kiBd5RNR/M9oHdeq +U6T/+KM+QyF5dDOyonY0QjwfAcEx+ZsV72nj8AerjM907I6dgHo/9YZ2S1Dt/xuG +ntD+76GDzmrOvXmmpF0DsTn+Wql7AC4uzaOjv6PVziqz03pR61RpjPDemyJEWMkC +gcEA7BkGGX3enBENs3X6BYFoeXfGO/hV7/aNpA6ykLzw657dqwy2b6bWLiIaqZdZ +u0oiY6+SpOtavkZBFTq4bTVD58FHL0n73Yvvaft507kijpYBrxyDOfTJOETv+dVG +XiY8AUSAE6GjPi0ebuYIVUxoDnMeWDuRJNvTck4byn1hJ1aVlEhwXNxt/nAjq48s +5QDuR6Z9F8lqEACRYCHSMQYFm35c7c1pPsHJnElX8a7eZ9lT7HGPXHaf/ypMkOzo +dvJNAoHBAN7GhDomff/kSgQLyzmqKqQowTZlyihnReapygwr8YpNcqKDqq6VlnfH +Jl1+qtSMSVI0csmccwJWkz1WtSjDsvY+oMdv4gUK3028vQAMQZo+Sh7OElFPFET3 +UmL+Nh73ACPgpiommsdLZQPcIqpWNT5NzO+Jm5xa+U9ToVZgQ7xjrqee5NUiMutr +r7UWAz7vDWu3x7bzYRRdUJxU18NogGbFGWJ1KM0c67GUXu2E7wBQdjVdS78UWs+4 +XBxKQkG2KQKBwQCtO+M82x122BB8iGkulvhogBjlMd8klnzxTpN5HhmMWWH+uvI1 +1G29Jer4WwRNJyU6jb4E4mgPyw7AG/jssLOlniy0Jw32TlIaKpoGXwZbJvgPW9Vx +tgnbDsIiR3o9ZMKMj42GWgike4ikCIc+xzRmvdMbHIHwUJfCfEtp9TtPGPnh9pDz +og3XLsMNg52GXnt3+VI6HOCE41XH+qj2rZt5r2tSVXEOyjQ7R5mOzSeFfXJVwDFX +v/a/zHKnuB0OAdUCgcBLrxPTEaqy2eMPdtZHM/mipbnmejRw/4zu7XYYJoG7483z +SlodT/K7pKvzDYqKBVMPm4P33K/x9mm1aBTJ0ZqmL+a9etRFtEjjByEKuB89gLX7 +uzTb7MrNF10lBopqgK3KgpLRNSZWWNXrtskMJ5eVICdkpdJ5Dyst+RKR3siEYzU9 ++yxxAFpeQsqB8gWORva/RsOR8yNjIMS3J9fZqlIdGA8ktPr0nEOyo96QQR5VdACE +5rpKI2cqtM6OSegynOkCgcAnr2Xzjef6tdcrxrQrq0DjEFTMoCAxQRa6tuF/NYHV +AK70Y4hBNX84Bvym4hmfbMUEuOCJU+QHQf/iDQrHXPhtX3X2/t8M+AlIzmwLKf2o +VwCYnZ8SqiwSaWVg+GANWLh0JuKn/ZYyR8urR79dAXFfp0UK+N39vIxNoBisBf+F +G8mca7zx3UtK2eOW8WgGHz+Y20VZy0m/nkNekd1ZTXoSGhL+iN4XsTRn1YQIn69R +kNdcwhtZZ3dpChUdf+w/LIc= -----END PRIVATE KEY----- diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index 168f6f73f6186fa..58ffc0ad4ab88b3 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -5004,12 +5004,16 @@ Test_property_set_impl(TestObj *self, PyObject *value); static int Test_property_set(TestObj *self, PyObject *value, void *Py_UNUSED(context)) { - return Test_property_set_impl(self, value); + int return_value; + + return_value = Test_property_set_impl(self, value); + + return return_value; } static int Test_property_set_impl(TestObj *self, PyObject *value) -/*[clinic end generated code: output=9797cd03c5204ddb input=3bc3f46a23c83a88]*/ +/*[clinic end generated code: output=d51023f17c4ac3a1 input=3bc3f46a23c83a88]*/ /*[clinic input] output push @@ -5327,52 +5331,6 @@ Test__pyarg_parsestackandkeywords_impl(TestObj *self, PyTypeObject *cls, /*[clinic end generated code: output=4fda8a7f2547137c input=fc72ef4b4cfafabc]*/ -/*[clinic input] -Test.__init__ -> long -Test overriding the __init__ return converter -[clinic start generated code]*/ - -PyDoc_STRVAR(Test___init____doc__, -"Test()\n" -"--\n" -"\n" -"Test overriding the __init__ return converter"); - -static long -Test___init___impl(TestObj *self); - -static int -Test___init__(PyObject *self, PyObject *args, PyObject *kwargs) -{ - int return_value = -1; - PyTypeObject *base_tp = TestType; - long _return_value; - - if ((Py_IS_TYPE(self, base_tp) || - Py_TYPE(self)->tp_new == base_tp->tp_new) && - !_PyArg_NoPositional("Test", args)) { - goto exit; - } - if ((Py_IS_TYPE(self, base_tp) || - Py_TYPE(self)->tp_new == base_tp->tp_new) && - !_PyArg_NoKeywords("Test", kwargs)) { - goto exit; - } - _return_value = Test___init___impl((TestObj *)self); - if ((_return_value == -1) && PyErr_Occurred()) { - goto exit; - } - return_value = PyLong_FromLong(_return_value); - -exit: - return return_value; -} - -static long -Test___init___impl(TestObj *self) -/*[clinic end generated code: output=daf6ee12c4e443fb input=311af0dc7f17e8e9]*/ - - /*[clinic input] fn_with_default_binop_expr arg: object(c_default='CONST_A + CONST_B') = a+b diff --git a/Lib/test/libregrtest/cmdline.py b/Lib/test/libregrtest/cmdline.py index 608b12bb6f2a381..876b1bcd2ca4066 100644 --- a/Lib/test/libregrtest/cmdline.py +++ b/Lib/test/libregrtest/cmdline.py @@ -164,6 +164,7 @@ def __init__(self, **kwargs) -> None: self.match_tests: TestFilter = [] self.pgo = False self.pgo_extended = False + self.tsan = False self.worker_json = None self.start = None self.timeout = None @@ -333,6 +334,8 @@ def _create_parser(): help='enable Profile Guided Optimization (PGO) training') group.add_argument('--pgo-extended', action='store_true', help='enable extended PGO training (slower training)') + group.add_argument('--tsan', dest='tsan', action='store_true', + help='run a subset of test cases that are proper for the TSAN test') group.add_argument('--fail-env-changed', action='store_true', help='if a test file alters the environment, mark ' 'the test as failed') diff --git a/Lib/test/libregrtest/filter.py b/Lib/test/libregrtest/filter.py index 817624d79e92632..41372e427ffd037 100644 --- a/Lib/test/libregrtest/filter.py +++ b/Lib/test/libregrtest/filter.py @@ -27,6 +27,11 @@ def _is_full_match_test(pattern): return ('.' in pattern) and (not re.search(r'[?*\[\]]', pattern)) +def get_match_tests(): + global _test_patterns + return _test_patterns + + def set_match_tests(patterns): global _test_matchers, _test_patterns diff --git a/Lib/test/libregrtest/main.py b/Lib/test/libregrtest/main.py index 6fbe3d00981ddd2..70f723a92eb44ae 100644 --- a/Lib/test/libregrtest/main.py +++ b/Lib/test/libregrtest/main.py @@ -18,6 +18,7 @@ from .runtests import RunTests, HuntRefleak from .setup import setup_process, setup_test_dir from .single import run_single_test, PROGRESS_MIN_TIME +from .tsan import setup_tsan_tests from .utils import ( StrPath, StrJSON, TestName, TestList, TestTuple, TestFilter, strip_py_suffix, count, format_duration, @@ -56,6 +57,7 @@ def __init__(self, ns: Namespace, _add_python_opts: bool = False): self.quiet: bool = ns.quiet self.pgo: bool = ns.pgo self.pgo_extended: bool = ns.pgo_extended + self.tsan: bool = ns.tsan # Test results self.results: TestResults = TestResults() @@ -183,6 +185,9 @@ def find_tests(self, tests: TestList | None = None) -> tuple[TestTuple, TestList # add default PGO tests if no tests are specified setup_pgo_tests(self.cmdline_args, self.pgo_extended) + if self.tsan: + setup_tsan_tests(self.cmdline_args) + exclude_tests = set() if self.exclude: for arg in self.cmdline_args: @@ -384,15 +389,10 @@ def run_tests_sequentially(self, runtests) -> None: result = self.run_test(test_name, runtests, tracer) - # Unload the newly imported test modules (best effort - # finalization). To work around gh-115490, don't unload - # test.support.interpreters and its submodules even if they - # weren't loaded before. - keep = "test.support.interpreters" + # Unload the newly imported test modules (best effort finalization) new_modules = [module for module in sys.modules if module not in save_modules and - module.startswith(("test.", "test_")) - and not module.startswith(keep)] + module.startswith(("test.", "test_"))] for module in new_modules: sys.modules.pop(module, None) # Remove the attribute of the parent module. diff --git a/Lib/test/libregrtest/refleak.py b/Lib/test/libregrtest/refleak.py index 71a70af6882d16f..f582c0d3e7ff134 100644 --- a/Lib/test/libregrtest/refleak.py +++ b/Lib/test/libregrtest/refleak.py @@ -88,9 +88,12 @@ def get_pooled_int(value): rc_before = alloc_before = fd_before = interned_before = 0 if not quiet: - print("beginning", repcount, "repetitions", file=sys.stderr) - print(("1234567890"*(repcount//10 + 1))[:repcount], file=sys.stderr, - flush=True) + print("beginning", repcount, "repetitions. Showing number of leaks " + "(. for 0 or less, X for 10 or more)", + file=sys.stderr) + numbers = ("1234567890"*(repcount//10 + 1))[:repcount] + numbers = numbers[:warmups] + ':' + numbers[warmups:] + print(numbers, file=sys.stderr, flush=True) results = None dash_R_cleanup(fs, ps, pic, zdc, abcs) @@ -116,13 +119,27 @@ def get_pooled_int(value): rc_after = gettotalrefcount() - interned_after * 2 fd_after = fd_count() - if not quiet: - print('.', end='', file=sys.stderr, flush=True) - rc_deltas[i] = get_pooled_int(rc_after - rc_before) alloc_deltas[i] = get_pooled_int(alloc_after - alloc_before) fd_deltas[i] = get_pooled_int(fd_after - fd_before) + if not quiet: + # use max, not sum, so total_leaks is one of the pooled ints + total_leaks = max(rc_deltas[i], alloc_deltas[i], fd_deltas[i]) + if total_leaks <= 0: + symbol = '.' + elif total_leaks < 10: + symbol = ( + '.', '1', '2', '3', '4', '5', '6', '7', '8', '9', + )[total_leaks] + else: + symbol = 'X' + if i == warmups: + print(' ', end='', file=sys.stderr, flush=True) + print(symbol, end='', file=sys.stderr, flush=True) + del total_leaks + del symbol + alloc_before = alloc_after rc_before = rc_after fd_before = fd_after @@ -158,14 +175,20 @@ def check_fd_deltas(deltas): ]: # ignore warmup runs deltas = deltas[warmups:] - if checker(deltas): + failing = checker(deltas) + suspicious = any(deltas) + if failing or suspicious: msg = '%s leaked %s %s, sum=%s' % ( test_name, deltas, item_name, sum(deltas)) - print(msg, file=sys.stderr, flush=True) - with open(filename, "a", encoding="utf-8") as refrep: - print(msg, file=refrep) - refrep.flush() - failed = True + print(msg, end='', file=sys.stderr) + if failing: + print(file=sys.stderr, flush=True) + with open(filename, "a", encoding="utf-8") as refrep: + print(msg, file=refrep) + refrep.flush() + failed = True + else: + print(' (this is fine)', file=sys.stderr, flush=True) return (failed, results) diff --git a/Lib/test/libregrtest/run_workers.py b/Lib/test/libregrtest/run_workers.py index 18a0342f0611cf0..9cfe1b9d6fd07dc 100644 --- a/Lib/test/libregrtest/run_workers.py +++ b/Lib/test/libregrtest/run_workers.py @@ -209,7 +209,7 @@ def _run_process(self, runtests: WorkerRunTests, output_fd: int, self._popen = None def create_stdout(self, stack: contextlib.ExitStack) -> TextIO: - """Create stdout temporay file (file descriptor).""" + """Create stdout temporary file (file descriptor).""" if MS_WINDOWS: # gh-95027: When stdout is not a TTY, Python uses the ANSI code diff --git a/Lib/test/libregrtest/tsan.py b/Lib/test/libregrtest/tsan.py new file mode 100644 index 000000000000000..dd18ae2584f5d8a --- /dev/null +++ b/Lib/test/libregrtest/tsan.py @@ -0,0 +1,33 @@ +# Set of tests run by default if --tsan is specified. The tests below were +# chosen because they use threads and run in a reasonable amount of time. + +TSAN_TESTS = [ + # TODO: enable more of test_capi once bugs are fixed (GH-116908, GH-116909). + 'test_capi.test_mem', + 'test_capi.test_pyatomic', + 'test_code', + 'test_enum', + 'test_functools', + 'test_httpservers', + 'test_imaplib', + 'test_importlib', + 'test_io', + 'test_logging', + 'test_queue', + 'test_signal', + 'test_socket', + 'test_sqlite3', + 'test_ssl', + 'test_syslog', + 'test_thread', + 'test_threadedtempfile', + 'test_threading', + 'test_threading_local', + 'test_threadsignals', + 'test_weakref', +] + + +def setup_tsan_tests(cmdline_args): + if not cmdline_args: + cmdline_args[:] = TSAN_TESTS[:] diff --git a/Lib/test/libregrtest/utils.py b/Lib/test/libregrtest/utils.py index b30025d962413c1..837f73b28b40184 100644 --- a/Lib/test/libregrtest/utils.py +++ b/Lib/test/libregrtest/utils.py @@ -276,6 +276,15 @@ def clear_caches(): pass else: inspect._shadowed_dict_from_mro_tuple.cache_clear() + inspect._filesbymodname.clear() + inspect.modulesbyfile.clear() + + try: + importlib_metadata = sys.modules['importlib.metadata'] + except KeyError: + pass + else: + importlib_metadata.FastPath.__new__.cache_clear() def get_build_info(): @@ -422,7 +431,7 @@ def get_work_dir(parent_dir: StrPath, worker: bool = False) -> StrPath: # the tests. The name of the dir includes the pid to allow parallel # testing (see the -j option). # Emscripten and WASI have stubbed getpid(), Emscripten has only - # milisecond clock resolution. Use randint() instead. + # millisecond clock resolution. Use randint() instead. if support.is_emscripten or support.is_wasi: nounce = random.randint(0, 1_000_000) else: diff --git a/Lib/test/libregrtest/win_utils.py b/Lib/test/libregrtest/win_utils.py index 5736cdfd3c72926..b51fde0af57d1ff 100644 --- a/Lib/test/libregrtest/win_utils.py +++ b/Lib/test/libregrtest/win_utils.py @@ -24,6 +24,10 @@ class WindowsLoadTracker(): """ def __init__(self): + # make __del__ not fail if pre-flight test fails + self._running = None + self._stopped = None + # Pre-flight test for access to the performance data; # `PermissionError` will be raised if not allowed winreg.QueryInfoKey(winreg.HKEY_PERFORMANCE_DATA) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 1d03ec0f5bd12be..ce693e51aab31cf 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -520,7 +520,7 @@ def requires_debug_ranges(reason='requires co_positions / debug_ranges'): # Is not actually used in tests, but is kept for compatibility. is_jython = sys.platform.startswith('java') -is_android = hasattr(sys, 'getandroidapilevel') +is_android = sys.platform == "android" if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos"}: unix_shell = '/system/bin/sh' if is_android else '/bin/sh' @@ -532,24 +532,33 @@ def requires_debug_ranges(reason='requires co_positions / debug_ranges'): is_emscripten = sys.platform == "emscripten" is_wasi = sys.platform == "wasi" -# Apple mobile platforms (iOS/tvOS/watchOS) are POSIX-like but do not -# have subprocess or fork support. is_apple_mobile = sys.platform in {"ios", "tvos", "watchos"} is_apple = is_apple_mobile or sys.platform == "darwin" has_fork_support = hasattr(os, "fork") and not ( + # WASM and Apple mobile platforms do not support subprocesses. is_emscripten or is_wasi or is_apple_mobile + + # Although Android supports fork, it's unsafe to call it from Python because + # all Android apps are multi-threaded. + or is_android ) def requires_fork(): return unittest.skipUnless(has_fork_support, "requires working os.fork()") has_subprocess_support = not ( + # WASM and Apple mobile platforms do not support subprocesses. is_emscripten or is_wasi or is_apple_mobile + + # Although Android supports subproceses, they're almost never useful in + # practice (see PEP 738). And most of the tests that use them are calling + # sys.executable, which won't work when Python is embedded in an Android app. + or is_android ) def requires_subprocess(): @@ -788,6 +797,16 @@ def disable_gc(): if have_gc: gc.enable() +@contextlib.contextmanager +def gc_threshold(*args): + import gc + old_threshold = gc.get_threshold() + gc.set_threshold(*args) + try: + yield + finally: + gc.set_threshold(*old_threshold) + def python_is_optimized(): """Find if Python was built with optimizations.""" @@ -1144,8 +1163,9 @@ def refcount_test(test): def requires_limited_api(test): try: import _testcapi + import _testlimitedcapi except ImportError: - return unittest.skip('needs _testcapi module')(test) + return unittest.skip('needs _testcapi and _testlimitedcapi modules')(test) return test def requires_specialization(test): @@ -2381,6 +2401,46 @@ def sleeping_retry(timeout, err_msg=None, /, delay = min(delay * 2, max_delay) +class CPUStopwatch: + """Context manager to roughly time a CPU-bound operation. + + Disables GC. Uses CPU time if it can (i.e. excludes sleeps & time of + other processes). + + N.B.: + - This *includes* time spent in other threads. + - Some systems only have a coarse resolution; check + stopwatch.clock_info.rseolution if. + + Usage: + + with ProcessStopwatch() as stopwatch: + ... + elapsed = stopwatch.seconds + resolution = stopwatch.clock_info.resolution + """ + def __enter__(self): + get_time = time.process_time + clock_info = time.get_clock_info('process_time') + if get_time() <= 0: # some platforms like WASM lack process_time() + get_time = time.monotonic + clock_info = time.get_clock_info('monotonic') + self.context = disable_gc() + self.context.__enter__() + self.get_time = get_time + self.clock_info = clock_info + self.start_time = get_time() + return self + + def __exit__(self, *exc): + try: + end_time = self.get_time() + finally: + result = self.context.__exit__(*exc) + self.seconds = end_time - self.start_time + return result + + @contextlib.contextmanager def adjust_int_max_str_digits(max_digits): """Temporarily change the integer string conversion length limit.""" diff --git a/Lib/test/support/import_helper.py b/Lib/test/support/import_helper.py index 3d804f2b590108a..29c6f535b403425 100644 --- a/Lib/test/support/import_helper.py +++ b/Lib/test/support/import_helper.py @@ -268,6 +268,18 @@ def modules_cleanup(oldmodules): sys.modules.update(oldmodules) +@contextlib.contextmanager +def isolated_modules(): + """ + Save modules on entry and cleanup on exit. + """ + (saved,) = modules_setup() + try: + yield + finally: + modules_cleanup(saved) + + def mock_register_at_fork(func): # bpo-30599: Mock os.register_at_fork() when importing the random module, # since this function doesn't allow to unregister callbacks and would leak diff --git a/Lib/test/support/interpreters/__init__.py b/Lib/test/support/interpreters/__init__.py index 15a908e9663593f..d8e6654fc96efd2 100644 --- a/Lib/test/support/interpreters/__init__.py +++ b/Lib/test/support/interpreters/__init__.py @@ -6,7 +6,7 @@ # aliases: from _xxsubinterpreters import ( - InterpreterError, InterpreterNotFoundError, + InterpreterError, InterpreterNotFoundError, NotShareableError, is_shareable, ) @@ -14,7 +14,8 @@ __all__ = [ 'get_current', 'get_main', 'create', 'list_all', 'is_shareable', 'Interpreter', - 'InterpreterError', 'InterpreterNotFoundError', 'ExecFailure', + 'InterpreterError', 'InterpreterNotFoundError', 'ExecutionFailed', + 'NotShareableError', 'create_queue', 'Queue', 'QueueEmpty', 'QueueFull', ] @@ -42,7 +43,11 @@ def __getattr__(name): {formatted} """.strip() -class ExecFailure(RuntimeError): +class ExecutionFailed(RuntimeError): + """An unhandled exception happened during execution. + + This is raised from Interpreter.exec() and Interpreter.call(). + """ def __init__(self, excinfo): msg = excinfo.formatted @@ -124,6 +129,14 @@ def __hash__(self): def __del__(self): self._decref() + # for pickling: + def __getnewargs__(self): + return (self._id,) + + # for pickling: + def __getstate__(self): + return None + def _decref(self): if not self._ownsref: return @@ -157,7 +170,7 @@ def prepare_main(self, ns=None, /, **kwargs): ns = dict(ns, **kwargs) if ns is not None else kwargs _interpreters.set___main___attrs(self._id, ns) - def exec_sync(self, code, /): + def exec(self, code, /): """Run the given source code in the interpreter. This is essentially the same as calling the builtin "exec" @@ -166,10 +179,10 @@ def exec_sync(self, code, /): There is no return value. - If the code raises an unhandled exception then an ExecFailure - is raised, which summarizes the unhandled exception. The actual - exception is discarded because objects cannot be shared between - interpreters. + If the code raises an unhandled exception then an ExecutionFailed + exception is raised, which summarizes the unhandled exception. + The actual exception is discarded because objects cannot be + shared between interpreters. This blocks the current Python thread until done. During that time, the previous interpreter is allowed to run @@ -177,11 +190,35 @@ def exec_sync(self, code, /): """ excinfo = _interpreters.exec(self._id, code) if excinfo is not None: - raise ExecFailure(excinfo) + raise ExecutionFailed(excinfo) + + def call(self, callable, /): + """Call the object in the interpreter with given args/kwargs. + + Only functions that take no arguments and have no closure + are supported. + + The return value is discarded. - def run(self, code, /): + If the callable raises an exception then the error display + (including full traceback) is send back between the interpreters + and an ExecutionFailed exception is raised, much like what + happens with Interpreter.exec(). + """ + # XXX Support args and kwargs. + # XXX Support arbitrary callables. + # XXX Support returning the return value (e.g. via pickle). + excinfo = _interpreters.call(self._id, callable) + if excinfo is not None: + raise ExecutionFailed(excinfo) + + def call_in_thread(self, callable, /): + """Return a new thread that calls the object in the interpreter. + + The return value and any raised exception are discarded. + """ def task(): - self.exec_sync(code) + self.call(callable) t = threading.Thread(target=task) t.start() return t diff --git a/Lib/test/support/interpreters/channels.py b/Lib/test/support/interpreters/channels.py index 75a5a60f54f926c..f7f523b1fc5a778 100644 --- a/Lib/test/support/interpreters/channels.py +++ b/Lib/test/support/interpreters/channels.py @@ -38,7 +38,8 @@ class _ChannelEnd: _end = None - def __init__(self, cid): + def __new__(cls, cid): + self = super().__new__(cls) if self._end == 'send': cid = _channels._channel_id(cid, send=True, force=True) elif self._end == 'recv': @@ -46,6 +47,7 @@ def __init__(self, cid): else: raise NotImplementedError(self._end) self._id = cid + return self def __repr__(self): return f'{type(self).__name__}(id={int(self._id)})' @@ -61,6 +63,14 @@ def __eq__(self, other): return NotImplemented return other._id == self._id + # for pickling: + def __getnewargs__(self): + return (int(self._id),) + + # for pickling: + def __getstate__(self): + return None + @property def id(self): return self._id diff --git a/Lib/test/support/interpreters/queues.py b/Lib/test/support/interpreters/queues.py index aead0c40ca9667e..5849a1cc15e4475 100644 --- a/Lib/test/support/interpreters/queues.py +++ b/Lib/test/support/interpreters/queues.py @@ -1,5 +1,6 @@ """Cross-interpreter Queues High Level Module.""" +import pickle import queue import time import weakref @@ -17,34 +18,40 @@ ] -class QueueEmpty(_queues.QueueEmpty, queue.Empty): +class QueueEmpty(QueueError, queue.Empty): """Raised from get_nowait() when the queue is empty. It is also raised from get() if it times out. """ -class QueueFull(_queues.QueueFull, queue.Full): +class QueueFull(QueueError, queue.Full): """Raised from put_nowait() when the queue is full. It is also raised from put() if it times out. """ -def create(maxsize=0): +_SHARED_ONLY = 0 +_PICKLED = 1 + +def create(maxsize=0, *, syncobj=False): """Return a new cross-interpreter queue. The queue may be used to pass data safely between interpreters. + + "syncobj" sets the default for Queue.put() + and Queue.put_nowait(). """ - qid = _queues.create(maxsize) - return Queue(qid) + fmt = _SHARED_ONLY if syncobj else _PICKLED + qid = _queues.create(maxsize, fmt) + return Queue(qid, _fmt=fmt) def list_all(): """Return a list of all open queues.""" - return [Queue(qid) - for qid in _queues.list_all()] - + return [Queue(qid, _fmt=fmt) + for qid, fmt in _queues.list_all()] _known_queues = weakref.WeakValueDictionary() @@ -52,17 +59,20 @@ def list_all(): class Queue: """A cross-interpreter queue.""" - def __new__(cls, id, /): + def __new__(cls, id, /, *, _fmt=None): # There is only one instance for any given ID. if isinstance(id, int): id = int(id) else: raise TypeError(f'id must be an int, got {id!r}') + if _fmt is None: + _fmt, = _queues.get_queue_defaults(id) try: self = _known_queues[id] except KeyError: self = super().__new__(cls) self._id = id + self._fmt = _fmt _known_queues[id] = self _queues.bind(id) return self @@ -83,6 +93,14 @@ def __repr__(self): def __hash__(self): return hash(self._id) + # for pickling: + def __getnewargs__(self): + return (self._id,) + + # for pickling: + def __getstate__(self): + return None + @property def id(self): return self._id @@ -105,34 +123,65 @@ def qsize(self): return _queues.get_count(self._id) def put(self, obj, timeout=None, *, + syncobj=None, _delay=10 / 1000, # 10 milliseconds ): """Add the object to the queue. This blocks while the queue is full. + + If "syncobj" is None (the default) then it uses the + queue's default, set with create_queue().. + + If "syncobj" is false then all objects are supported, + at the expense of worse performance. + + If "syncobj" is true then the object must be "shareable". + Examples of "shareable" objects include the builtin singletons, + str, and memoryview. One benefit is that such objects are + passed through the queue efficiently. + + The key difference, though, is conceptual: the corresponding + object returned from Queue.get() will be strictly equivalent + to the given obj. In other words, the two objects will be + effectively indistinguishable from each other, even if the + object is mutable. The received object may actually be the + same object, or a copy (immutable values only), or a proxy. + Regardless, the received object should be treated as though + the original has been shared directly, whether or not it + actually is. That's a slightly different and stronger promise + than just (initial) equality, which is all "syncobj=False" + can promise. """ + if syncobj is None: + fmt = self._fmt + else: + fmt = _SHARED_ONLY if syncobj else _PICKLED if timeout is not None: timeout = int(timeout) if timeout < 0: raise ValueError(f'timeout value must be non-negative') end = time.time() + timeout + if fmt is _PICKLED: + obj = pickle.dumps(obj) while True: try: - _queues.put(self._id, obj) - except _queues.QueueFull as exc: + _queues.put(self._id, obj, fmt) + except QueueFull as exc: if timeout is not None and time.time() >= end: - exc.__class__ = QueueFull raise # re-raise time.sleep(_delay) else: break - def put_nowait(self, obj): - try: - return _queues.put(self._id, obj) - except _queues.QueueFull as exc: - exc.__class__ = QueueFull - raise # re-raise + def put_nowait(self, obj, *, syncobj=None): + if syncobj is None: + fmt = self._fmt + else: + fmt = _SHARED_ONLY if syncobj else _PICKLED + if fmt is _PICKLED: + obj = pickle.dumps(obj) + _queues.put(self._id, obj, fmt) def get(self, timeout=None, *, _delay=10 / 1000, # 10 milliseconds @@ -148,12 +197,17 @@ def get(self, timeout=None, *, end = time.time() + timeout while True: try: - return _queues.get(self._id) - except _queues.QueueEmpty as exc: + obj, fmt = _queues.get(self._id) + except QueueEmpty as exc: if timeout is not None and time.time() >= end: - exc.__class__ = QueueEmpty raise # re-raise time.sleep(_delay) + else: + break + if fmt == _PICKLED: + obj = pickle.loads(obj) + else: + assert fmt == _SHARED_ONLY return obj def get_nowait(self): @@ -163,10 +217,14 @@ def get_nowait(self): is the same as get(). """ try: - return _queues.get(self._id) - except _queues.QueueEmpty as exc: - exc.__class__ = QueueEmpty + obj, fmt = _queues.get(self._id) + except QueueEmpty as exc: raise # re-raise + if fmt == _PICKLED: + obj = pickle.loads(obj) + else: + assert fmt == _SHARED_ONLY + return obj -_queues._register_queue_type(Queue) +_queues._register_heap_types(Queue, QueueEmpty, QueueFull) diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py index 22787e32b5f3abe..8071c248b9b67ef 100644 --- a/Lib/test/support/os_helper.py +++ b/Lib/test/support/os_helper.py @@ -198,6 +198,23 @@ def skip_unless_symlink(test): return test if ok else unittest.skip(msg)(test) +_can_hardlink = None + +def can_hardlink(): + global _can_hardlink + if _can_hardlink is None: + # Android blocks hard links using SELinux + # (https://stackoverflow.com/q/32365690). + _can_hardlink = hasattr(os, "link") and not support.is_android + return _can_hardlink + + +def skip_unless_hardlink(test): + ok = can_hardlink() + msg = "requires hardlink support" + return test if ok else unittest.skip(msg)(test) + + _can_xattr = None @@ -595,7 +612,7 @@ def __fspath__(self): def fd_count(): """Count the number of open file descriptors. """ - if sys.platform.startswith(('linux', 'freebsd', 'emscripten')): + if sys.platform.startswith(('linux', 'android', 'freebsd', 'emscripten')): fd_path = "/proc/self/fd" elif sys.platform == "darwin": fd_path = "/dev/fd" diff --git a/Lib/test/support/script_helper.py b/Lib/test/support/script_helper.py index 759020c33aa7000..65e0bc199e7f0b5 100644 --- a/Lib/test/support/script_helper.py +++ b/Lib/test/support/script_helper.py @@ -63,8 +63,8 @@ class _PythonRunResult(collections.namedtuple("_PythonRunResult", """Helper for reporting Python subprocess run results""" def fail(self, cmd_line): """Provide helpful details about failed subcommand runs""" - # Limit to 80 lines to ASCII characters - maxlen = 80 * 100 + # Limit to 300 lines of ASCII characters + maxlen = 300 * 100 out, err = self.out, self.err if len(out) > maxlen: out = b'(... truncated stdout ...)' + out[-maxlen:] diff --git a/Lib/test/test___all__.py b/Lib/test/test___all__.py index c87cde4b3d1fab1..19dcbb207e914af 100644 --- a/Lib/test/test___all__.py +++ b/Lib/test/test___all__.py @@ -5,11 +5,6 @@ import sys import types -try: - import _multiprocessing -except ModuleNotFoundError: - _multiprocessing = None - if support.check_sanitizer(address=True, memory=True): SKIP_MODULES = frozenset(( @@ -36,17 +31,6 @@ class FailedImport(RuntimeError): class AllTest(unittest.TestCase): - def setUp(self): - # concurrent.futures uses a __getattr__ hook. Its __all__ triggers - # import of a submodule, which fails when _multiprocessing is not - # available. - if _multiprocessing is None: - sys.modules["_multiprocessing"] = types.ModuleType("_multiprocessing") - - def tearDown(self): - if _multiprocessing is None: - sys.modules.pop("_multiprocessing") - def check_all(self, modname): names = {} with warnings_helper.check_warnings( diff --git a/Lib/test/test__xxinterpchannels.py b/Lib/test/test__xxinterpchannels.py index cc2ed7849b0c0f8..c5d29bd2dd911fc 100644 --- a/Lib/test/test__xxinterpchannels.py +++ b/Lib/test/test__xxinterpchannels.py @@ -18,6 +18,11 @@ channels = import_helper.import_module('_xxinterpchannels') +# Additional tests are found in Lib/test/test_interpreters/test_channels.py. +# New tests should be added there. +# XXX The tests here should be moved there. See the note under LowLevelTests. + + ################################## # helpers diff --git a/Lib/test/test__xxsubinterpreters.py b/Lib/test/test__xxsubinterpreters.py index a76e4d0ade5b8ae..35d7355680e549f 100644 --- a/Lib/test/test__xxsubinterpreters.py +++ b/Lib/test/test__xxsubinterpreters.py @@ -7,7 +7,6 @@ import threading import unittest -import _testinternalcapi from test import support from test.support import import_helper from test.support import os_helper @@ -15,6 +14,7 @@ interpreters = import_helper.import_module('_xxsubinterpreters') +_testinternalcapi = import_helper.import_module('_testinternalcapi') from _xxsubinterpreters import InterpreterNotFoundError diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 3789ac22e3899cf..7cecf319e3638f0 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -525,17 +525,38 @@ def test_field_attr_existence(self): if name == 'Index': continue if self._is_ast_node(name, item): - x = item() + x = self._construct_ast_class(item) if isinstance(x, ast.AST): self.assertIs(type(x._fields), tuple) + def _construct_ast_class(self, cls): + kwargs = {} + for name, typ in cls.__annotations__.items(): + if typ is str: + kwargs[name] = 'capybara' + elif typ is int: + kwargs[name] = 42 + elif typ is object: + kwargs[name] = b'capybara' + elif isinstance(typ, type) and issubclass(typ, ast.AST): + kwargs[name] = self._construct_ast_class(typ) + return cls(**kwargs) + def test_arguments(self): x = ast.arguments() self.assertEqual(x._fields, ('posonlyargs', 'args', 'vararg', 'kwonlyargs', 'kw_defaults', 'kwarg', 'defaults')) - - with self.assertRaises(AttributeError): - x.args + self.assertEqual(x.__annotations__, { + 'posonlyargs': list[ast.arg], + 'args': list[ast.arg], + 'vararg': ast.arg | None, + 'kwonlyargs': list[ast.arg], + 'kw_defaults': list[ast.expr], + 'kwarg': ast.arg | None, + 'defaults': list[ast.expr], + }) + + self.assertEqual(x.args, []) self.assertIsNone(x.vararg) x = ast.arguments(*range(1, 8)) @@ -551,7 +572,7 @@ def test_field_attr_writable_deprecated(self): self.assertEqual(x._fields, 666) def test_field_attr_writable(self): - x = ast.Constant() + x = ast.Constant(1) # We can assign to _fields x._fields = 666 self.assertEqual(x._fields, 666) @@ -611,15 +632,22 @@ def test_classattrs_deprecated(self): self.assertEqual([str(w.message) for w in wlog], [ 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + "Constant.__init__ missing 1 required positional argument: 'value'. This will become " + 'an error in Python 3.15.', 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + "Constant.__init__ missing 1 required positional argument: 'value'. This will become " + 'an error in Python 3.15.', 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + "Constant.__init__ got an unexpected keyword argument 'foo'. Support for " + 'arbitrary keyword arguments is deprecated and will be removed in Python ' + '3.15.', 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', @@ -636,7 +664,8 @@ def test_classattrs_deprecated(self): ]) def test_classattrs(self): - x = ast.Constant() + with self.assertWarns(DeprecationWarning): + x = ast.Constant() self.assertEqual(x._fields, ('value', 'kind')) with self.assertRaises(AttributeError): @@ -651,7 +680,7 @@ def test_classattrs(self): with self.assertRaises(AttributeError): x.foobar - x = ast.Constant(lineno=2) + x = ast.Constant(lineno=2, value=3) self.assertEqual(x.lineno, 2) x = ast.Constant(42, lineno=0) @@ -662,8 +691,9 @@ def test_classattrs(self): self.assertRaises(TypeError, ast.Constant, 1, None, 2) self.assertRaises(TypeError, ast.Constant, 1, None, 2, lineno=0) - # Arbitrary keyword arguments are supported - self.assertEqual(ast.Constant(1, foo='bar').foo, 'bar') + # Arbitrary keyword arguments are supported (but deprecated) + with self.assertWarns(DeprecationWarning): + self.assertEqual(ast.Constant(1, foo='bar').foo, 'bar') with self.assertRaisesRegex(TypeError, "Constant got multiple values for argument 'value'"): ast.Constant(1, value=2) @@ -815,11 +845,11 @@ def test_isinstance(self): assertBytesDeprecated(self.assertNotIsInstance, Constant('42'), Bytes) assertNameConstantDeprecated(self.assertNotIsInstance, Constant(42), NameConstant) assertEllipsisDeprecated(self.assertNotIsInstance, Constant(42), Ellipsis) - assertNumDeprecated(self.assertNotIsInstance, Constant(), Num) - assertStrDeprecated(self.assertNotIsInstance, Constant(), Str) - assertBytesDeprecated(self.assertNotIsInstance, Constant(), Bytes) - assertNameConstantDeprecated(self.assertNotIsInstance, Constant(), NameConstant) - assertEllipsisDeprecated(self.assertNotIsInstance, Constant(), Ellipsis) + assertNumDeprecated(self.assertNotIsInstance, Constant(None), Num) + assertStrDeprecated(self.assertNotIsInstance, Constant(None), Str) + assertBytesDeprecated(self.assertNotIsInstance, Constant(None), Bytes) + assertNameConstantDeprecated(self.assertNotIsInstance, Constant(1), NameConstant) + assertEllipsisDeprecated(self.assertNotIsInstance, Constant(None), Ellipsis) class S(str): pass with assertStrDeprecated(): @@ -888,8 +918,9 @@ def test_module(self): self.assertEqual(x.body, body) def test_nodeclasses(self): - # Zero arguments constructor explicitly allowed - x = ast.BinOp() + # Zero arguments constructor explicitly allowed (but deprecated) + with self.assertWarns(DeprecationWarning): + x = ast.BinOp() self.assertEqual(x._fields, ('left', 'op', 'right')) # Random attribute allowed too @@ -927,8 +958,9 @@ def test_nodeclasses(self): self.assertEqual(x.right, 3) self.assertEqual(x.lineno, 0) - # Random kwargs also allowed - x = ast.BinOp(1, 2, 3, foobarbaz=42) + # Random kwargs also allowed (but deprecated) + with self.assertWarns(DeprecationWarning): + x = ast.BinOp(1, 2, 3, foobarbaz=42) self.assertEqual(x.foobarbaz, 42) def test_no_fields(self): @@ -941,8 +973,9 @@ def test_pickling(self): for protocol in range(pickle.HIGHEST_PROTOCOL + 1): for ast in (compile(i, "?", "exec", 0x400) for i in exec_tests): - ast2 = pickle.loads(pickle.dumps(ast, protocol)) - self.assertEqual(to_tuple(ast2), to_tuple(ast)) + with self.subTest(ast=ast, protocol=protocol): + ast2 = pickle.loads(pickle.dumps(ast, protocol)) + self.assertEqual(to_tuple(ast2), to_tuple(ast)) def test_invalid_sum(self): pos = dict(lineno=2, col_offset=3) @@ -1045,19 +1078,15 @@ def test_positional_only_feature_version(self): with self.assertRaises(SyntaxError): ast.parse('lambda x=1, /: ...', feature_version=(3, 7)) - def test_parenthesized_with_feature_version(self): - ast.parse('with (CtxManager() as example): ...', feature_version=(3, 10)) - # While advertised as a feature in Python 3.10, this was allowed starting 3.9 - ast.parse('with (CtxManager() as example): ...', feature_version=(3, 9)) - with self.assertRaises(SyntaxError): - ast.parse('with (CtxManager() as example): ...', feature_version=(3, 8)) - ast.parse('with CtxManager() as example: ...', feature_version=(3, 8)) - def test_assignment_expression_feature_version(self): ast.parse('(x := 0)', feature_version=(3, 8)) with self.assertRaises(SyntaxError): ast.parse('(x := 0)', feature_version=(3, 7)) + def test_conditional_context_managers_parse_with_low_feature_version(self): + # regression test for gh-115881 + ast.parse('with (x() if y else z()): ...', feature_version=(3, 8)) + def test_exception_groups_feature_version(self): code = dedent(''' try: ... @@ -1314,8 +1343,9 @@ def test_copy_location(self): 'lineno=1, col_offset=4, end_lineno=1, end_col_offset=5), lineno=1, ' 'col_offset=0, end_lineno=1, end_col_offset=5))' ) - src = ast.Call(col_offset=1, lineno=1, end_lineno=1, end_col_offset=1) - new = ast.copy_location(src, ast.Call(col_offset=None, lineno=None)) + func = ast.Name('spam', ast.Load()) + src = ast.Call(col_offset=1, lineno=1, end_lineno=1, end_col_offset=1, func=func) + new = ast.copy_location(src, ast.Call(col_offset=None, lineno=None, func=func)) self.assertIsNone(new.end_lineno) self.assertIsNone(new.end_col_offset) self.assertEqual(new.lineno, 1) @@ -1574,15 +1604,15 @@ def test_level_as_none(self): self.assertIn('sleep', ns) def test_recursion_direct(self): - e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0) + e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) e.operand = e with self.assertRaises(RecursionError): with support.infinite_recursion(): compile(ast.Expression(e), "", "eval") def test_recursion_indirect(self): - e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0) - f = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0) + e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) + f = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) e.operand = f f.operand = e with self.assertRaises(RecursionError): @@ -2870,6 +2900,23 @@ def visit_Call(self, node: ast.Call): self.assertASTTransformation(PrintToLog, code, expected) +class ASTConstructorTests(unittest.TestCase): + """Test the autogenerated constructors for AST nodes.""" + + def test_FunctionDef(self): + args = ast.arguments() + self.assertEqual(args.args, []) + self.assertEqual(args.posonlyargs, []) + with self.assertWarnsRegex(DeprecationWarning, + r"FunctionDef\.__init__ missing 1 required positional argument: 'name'"): + node = ast.FunctionDef(args=args) + self.assertFalse(hasattr(node, "name")) + self.assertEqual(node.decorator_list, []) + node = ast.FunctionDef(name='foo', args=args) + self.assertEqual(node.name, 'foo') + self.assertEqual(node.decorator_list, []) + + @support.cpython_only class ModuleStateTests(unittest.TestCase): # bpo-41194, bpo-41261, bpo-41631: The _ast module uses a global state. diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index c92c88bd5b2429c..5b9c871e1d1b5a0 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -2250,7 +2250,7 @@ def test_handle_repr(self): h = asyncio.Handle(noop, (1, 2), self.loop) filename, lineno = test_utils.get_function_source(noop) self.assertEqual(repr(h), - '' + '' % (filename, lineno)) # cancelled handle @@ -2268,14 +2268,14 @@ def test_handle_repr(self): # partial function cb = functools.partial(noop, 1, 2) h = asyncio.Handle(cb, (3,), self.loop) - regex = (r'^$' + regex = (r'^$' % (re.escape(filename), lineno)) self.assertRegex(repr(h), regex) # partial function with keyword args cb = functools.partial(noop, x=1) h = asyncio.Handle(cb, (2, 3), self.loop) - regex = (r'^$' + regex = (r'^$' % (re.escape(filename), lineno)) self.assertRegex(repr(h), regex) @@ -2316,6 +2316,24 @@ def test_handle_repr_debug(self): '' % (filename, lineno, create_filename, create_lineno)) + # partial function + cb = functools.partial(noop, 1, 2) + create_lineno = sys._getframe().f_lineno + 1 + h = asyncio.Handle(cb, (3,), self.loop) + regex = (r'^$' + % (re.escape(filename), lineno, + re.escape(create_filename), create_lineno)) + self.assertRegex(repr(h), regex) + + # partial function with keyword args + cb = functools.partial(noop, x=1) + create_lineno = sys._getframe().f_lineno + 1 + h = asyncio.Handle(cb, (2, 3), self.loop) + regex = (r'^$' + % (re.escape(filename), lineno, + re.escape(create_filename), create_lineno)) + self.assertRegex(repr(h), regex) + def test_handle_source_traceback(self): loop = asyncio.get_event_loop_policy().new_event_loop() loop.set_debug(True) diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index 210990593adfa9b..2cf48538d5d30d2 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -1130,6 +1130,31 @@ async def inner(httpd): self.assertEqual(messages, []) + def test_unclosed_server_resource_warnings(self): + async def inner(rd, wr): + fut.set_result(True) + with self.assertWarns(ResourceWarning) as cm: + del wr + gc.collect() + self.assertEqual(len(cm.warnings), 1) + self.assertTrue(str(cm.warnings[0].message).startswith("unclosed , (), None)"), ("test.test_func", "()"), - ("_thread.start_joinable_thread", "(,)"), + ("_thread.start_joinable_thread", "(, 1, None)"), ("test.test_func", "()"), ] diff --git a/Lib/test/test_base64.py b/Lib/test/test_base64.py index f6171d3ed4efd73..409c8c109e885f6 100644 --- a/Lib/test/test_base64.py +++ b/Lib/test/test_base64.py @@ -545,6 +545,40 @@ def test_b85encode(self): self.check_other_types(base64.b85encode, b"www.python.org", b'cXxL#aCvlSZ*DGca%T') + def test_z85encode(self): + eq = self.assertEqual + + tests = { + b'': b'', + b'www.python.org': b'CxXl-AcVLsz/dgCA+t', + bytes(range(255)): b"""009c61o!#m2NH?C3>iWS5d]J*6CRx17-skh9337x""" + b"""ar.{NbQB=+c[cR@eg&FcfFLssg=mfIi5%2YjuU>)kTv.7l}6Nnnj=AD""" + b"""oIFnTp/ga?r8($2sxO*itWpVyu$0IOwmYv=xLzi%y&a6dAb/]tBAI+J""" + b"""CZjQZE0{D[FpSr8GOteoH(41EJe-&}x#)cTlf[Bu8v].4}L}1:^-""" + b"""@qDP""", + b"""abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ""" + b"""0123456789!@#0^&*();:<>,. []{}""": + b"""vpA.SwObN*x>?B1zeKohADlbxB-}$ND3R+ylQTvjm[uizoh55PpF:[^""" + b"""q=D:$s6eQefFLssg=mfIi5@cEbqrBJdKV-ciY]OSe*aw7DWL""", + b'no padding..': b'zF{UpvpS[.zF7NO', + b'zero compression\x00\x00\x00\x00': b'Ds.bnay/tbAb]JhB7]Mg00000', + b'zero compression\x00\x00\x00': b'Ds.bnay/tbAb]JhB7]Mg0000', + b"""Boundary:\x00\x00\x00\x00""": b"""lt}0:wmoI7iSGcW00""", + b'Space compr: ': b'q/DePwGUG3ze:IRarR^H', + b'\xff': b'@@', + b'\xff'*2: b'%nJ', + b'\xff'*3: b'%nS9', + b'\xff'*4: b'%nSc0', + } + + for data, res in tests.items(): + eq(base64.z85encode(data), res) + + self.check_other_types(base64.z85encode, b"www.python.org", + b'CxXl-AcVLsz/dgCA+t') + def test_a85decode(self): eq = self.assertEqual @@ -626,6 +660,41 @@ def test_b85decode(self): self.check_other_types(base64.b85decode, b'cXxL#aCvlSZ*DGca%T', b"www.python.org") + def test_z85decode(self): + eq = self.assertEqual + + tests = { + b'': b'', + b'CxXl-AcVLsz/dgCA+t': b'www.python.org', + b"""009c61o!#m2NH?C3>iWS5d]J*6CRx17-skh9337x""" + b"""ar.{NbQB=+c[cR@eg&FcfFLssg=mfIi5%2YjuU>)kTv.7l}6Nnnj=AD""" + b"""oIFnTp/ga?r8($2sxO*itWpVyu$0IOwmYv=xLzi%y&a6dAb/]tBAI+J""" + b"""CZjQZE0{D[FpSr8GOteoH(41EJe-&}x#)cTlf[Bu8v].4}L}1:^-""" + b"""@qDP""": bytes(range(255)), + b"""vpA.SwObN*x>?B1zeKohADlbxB-}$ND3R+ylQTvjm[uizoh55PpF:[^""" + b"""q=D:$s6eQefFLssg=mfIi5@cEbqrBJdKV-ciY]OSe*aw7DWL""": + b"""abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ""" + b"""0123456789!@#0^&*();:<>,. []{}""", + b'zF{UpvpS[.zF7NO': b'no padding..', + b'Ds.bnay/tbAb]JhB7]Mg00000': b'zero compression\x00\x00\x00\x00', + b'Ds.bnay/tbAb]JhB7]Mg0000': b'zero compression\x00\x00\x00', + b"""lt}0:wmoI7iSGcW00""": b"""Boundary:\x00\x00\x00\x00""", + b'q/DePwGUG3ze:IRarR^H': b'Space compr: ', + b'@@': b'\xff', + b'%nJ': b'\xff'*2, + b'%nS9': b'\xff'*3, + b'%nSc0': b'\xff'*4, + } + + for data, res in tests.items(): + eq(base64.z85decode(data), res) + eq(base64.z85decode(data.decode("ascii")), res) + + self.check_other_types(base64.z85decode, b'CxXl-AcVLsz/dgCA+t', + b'www.python.org') + def test_a85_padding(self): eq = self.assertEqual @@ -707,6 +776,21 @@ def test_b85decode_errors(self): self.assertRaises(ValueError, base64.b85decode, b'|NsC') self.assertRaises(ValueError, base64.b85decode, b'|NsC1') + def test_z85decode_errors(self): + illegal = list(range(33)) + \ + list(b'"\',;_`|\\~') + \ + list(range(128, 256)) + for c in illegal: + with self.assertRaises(ValueError, msg=bytes([c])): + base64.z85decode(b'0000' + bytes([c])) + + # b'\xff\xff\xff\xff' encodes to b'%nSc0', the following will overflow: + self.assertRaises(ValueError, base64.z85decode, b'%') + self.assertRaises(ValueError, base64.z85decode, b'%n') + self.assertRaises(ValueError, base64.z85decode, b'%nS') + self.assertRaises(ValueError, base64.z85decode, b'%nSc') + self.assertRaises(ValueError, base64.z85decode, b'%nSc1') + def test_decode_nonascii_str(self): decode_funcs = (base64.b64decode, base64.standard_b64decode, @@ -714,7 +798,8 @@ def test_decode_nonascii_str(self): base64.b32decode, base64.b16decode, base64.b85decode, - base64.a85decode) + base64.a85decode, + base64.z85decode) for f in decode_funcs: self.assertRaises(ValueError, f, 'with non-ascii \xcb') diff --git a/Lib/test/test_baseexception.py b/Lib/test/test_baseexception.py index 4c3cf0b964ae565..6dc06c5e4bc9d52 100644 --- a/Lib/test/test_baseexception.py +++ b/Lib/test/test_baseexception.py @@ -129,7 +129,7 @@ class Value(str): d[HashThisKeyWillClearTheDict()] = Value() # refcount of Value() is 1 now - # Exception.__setstate__ should aquire a strong reference of key and + # Exception.__setstate__ should acquire a strong reference of key and # value in the dict. Otherwise, Value()'s refcount would go below # zero in the tp_hash call in PyObject_SetAttr(), and it would cause # crash in GC. diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index a3804a945f2e3a4..71bb1e75c6affd7 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -1599,6 +1599,13 @@ def test_extend(self): a = bytearray(b'') a.extend([Indexable(ord('a'))]) self.assertEqual(a, b'a') + a = bytearray(b'abc') + self.assertRaisesRegex(TypeError, # Override for string. + "expected iterable of integers; got: 'str'", + a.extend, 'def') + self.assertRaisesRegex(TypeError, # But not for others. + "can't extend bytearray with float", + a.extend, 1.0) def test_remove(self): b = bytearray(b'hello') diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py index 1f0b9adc3698b4a..772f0eacce28f52 100644 --- a/Lib/test/test_bz2.py +++ b/Lib/test/test_bz2.py @@ -3,19 +3,19 @@ import array import unittest +import io from io import BytesIO, DEFAULT_BUFFER_SIZE import os import pickle import glob import tempfile -import pathlib import random import shutil import subprocess import threading from test.support import import_helper from test.support import threading_helper -from test.support.os_helper import unlink +from test.support.os_helper import unlink, FakePath import _compression import sys @@ -537,12 +537,136 @@ def testMultiStreamOrdering(self): with BZ2File(self.filename) as bz2f: self.assertEqual(bz2f.read(), data1 + data2) + def testOpenFilename(self): + with BZ2File(self.filename, "wb") as f: + f.write(b'content') + self.assertIsInstance(f.fileno(), int) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), False) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + with BZ2File(self.filename, "ab") as f: + f.write(b'appendix') + self.assertIsInstance(f.fileno(), int) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), False) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + with BZ2File(self.filename, 'rb') as f: + self.assertEqual(f.read(), b'contentappendix') + self.assertIsInstance(f.fileno(), int) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + with self.assertRaises(ValueError): + f.fileno() + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + def testOpenFileWithName(self): + with open(self.filename, 'wb') as raw: + with BZ2File(raw, 'wb') as f: + f.write(b'content') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), False) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + with open(self.filename, 'ab') as raw: + with BZ2File(raw, 'ab') as f: + f.write(b'appendix') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), False) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + with open(self.filename, 'rb') as raw: + with BZ2File(raw, 'rb') as f: + self.assertEqual(f.read(), b'contentappendix') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + with self.assertRaises(ValueError): + f.fileno() + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + def testOpenFileWithoutName(self): + bio = BytesIO() + with BZ2File(bio, 'wb') as f: + f.write(b'content') + self.assertRaises(io.UnsupportedOperation, f.fileno) + self.assertRaises(ValueError, f.fileno) + + with BZ2File(bio, 'ab') as f: + f.write(b'appendix') + self.assertRaises(io.UnsupportedOperation, f.fileno) + self.assertRaises(ValueError, f.fileno) + + bio.seek(0) + with BZ2File(bio, 'rb') as f: + self.assertEqual(f.read(), b'contentappendix') + self.assertRaises(io.UnsupportedOperation, f.fileno) + with self.assertRaises(ValueError): + f.fileno() + + def testOpenFileWithIntName(self): + fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC) + with open(fd, 'wb') as raw: + with BZ2File(raw, 'wb') as f: + f.write(b'content') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertRaises(ValueError, f.fileno) + + fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT | os.O_APPEND) + with open(fd, 'ab') as raw: + with BZ2File(raw, 'ab') as f: + f.write(b'appendix') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertRaises(ValueError, f.fileno) + + fd = os.open(self.filename, os.O_RDONLY) + with open(fd, 'rb') as raw: + with BZ2File(raw, 'rb') as f: + self.assertEqual(f.read(), b'contentappendix') + self.assertEqual(f.fileno(), raw.fileno()) + with self.assertRaises(ValueError): + f.fileno() + def testOpenBytesFilename(self): str_filename = self.filename - try: - bytes_filename = str_filename.encode("ascii") - except UnicodeEncodeError: - self.skipTest("Temporary file name needs to be ASCII") + bytes_filename = os.fsencode(str_filename) with BZ2File(bytes_filename, "wb") as f: f.write(self.DATA) with BZ2File(bytes_filename, "rb") as f: @@ -552,7 +676,7 @@ def testOpenBytesFilename(self): self.assertEqual(f.read(), self.DATA) def testOpenPathLikeFilename(self): - filename = pathlib.Path(self.filename) + filename = FakePath(self.filename) with BZ2File(filename, "wb") as f: f.write(self.DATA) with BZ2File(filename, "rb") as f: diff --git a/Lib/test/test_c_locale_coercion.py b/Lib/test/test_c_locale_coercion.py index 7334a325ba22f05..e4b0b8c451fd456 100644 --- a/Lib/test/test_c_locale_coercion.py +++ b/Lib/test/test_c_locale_coercion.py @@ -26,17 +26,16 @@ TARGET_LOCALES = ["C.UTF-8", "C.utf8", "UTF-8"] # Apply some platform dependent overrides -if sys.platform.startswith("linux"): - if support.is_android: - # Android defaults to using UTF-8 for all system interfaces - EXPECTED_C_LOCALE_STREAM_ENCODING = "utf-8" - EXPECTED_C_LOCALE_FS_ENCODING = "utf-8" - else: - # Linux distros typically alias the POSIX locale directly to the C - # locale. - # TODO: Once https://bugs.python.org/issue30672 is addressed, we'll be - # able to check this case unconditionally - EXPECTED_C_LOCALE_EQUIVALENTS.append("POSIX") +if sys.platform == "android": + # Android defaults to using UTF-8 for all system interfaces + EXPECTED_C_LOCALE_STREAM_ENCODING = "utf-8" + EXPECTED_C_LOCALE_FS_ENCODING = "utf-8" +elif sys.platform.startswith("linux"): + # Linux distros typically alias the POSIX locale directly to the C + # locale. + # TODO: Once https://bugs.python.org/issue30672 is addressed, we'll be + # able to check this case unconditionally + EXPECTED_C_LOCALE_EQUIVALENTS.append("POSIX") elif sys.platform.startswith("aix"): # AIX uses iso8859-1 in the C locale, other *nix platforms use ASCII EXPECTED_C_LOCALE_STREAM_ENCODING = "iso8859-1" diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 2a6a5d287b04ee9..eaf919584b4c64c 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -5,6 +5,10 @@ import _testcapi except ImportError: _testcapi = None +try: + import _testlimitedcapi +except ImportError: + _testlimitedcapi = None import struct import collections import itertools @@ -837,12 +841,12 @@ def get_a(x): @requires_limited_api def test_vectorcall_limited_incoming(self): from _testcapi import pyobject_vectorcall - obj = _testcapi.LimitedVectorCallClass() + obj = _testlimitedcapi.LimitedVectorCallClass() self.assertEqual(pyobject_vectorcall(obj, (), ()), "vectorcall called") @requires_limited_api def test_vectorcall_limited_outgoing(self): - from _testcapi import call_vectorcall + from _testlimitedcapi import call_vectorcall args_captured = [] kwargs_captured = [] @@ -858,7 +862,7 @@ def f(*args, **kwargs): @requires_limited_api def test_vectorcall_limited_outgoing_method(self): - from _testcapi import call_vectorcall_method + from _testlimitedcapi import call_vectorcall_method args_captured = [] kwargs_captured = [] diff --git a/Lib/test/test_capi/test_bytearray.py b/Lib/test/test_capi/test_bytearray.py index 833122c4e319d89..39099f6b82240f9 100644 --- a/Lib/test/test_capi/test_bytearray.py +++ b/Lib/test/test_capi/test_bytearray.py @@ -1,7 +1,7 @@ import unittest from test.support import import_helper -_testcapi = import_helper.import_module('_testcapi') +_testlimitedcapi = import_helper.import_module('_testlimitedcapi') from _testcapi import PY_SSIZE_T_MIN, PY_SSIZE_T_MAX NULL = None @@ -19,7 +19,7 @@ def __bytes__(self): class CAPITest(unittest.TestCase): def test_check(self): # Test PyByteArray_Check() - check = _testcapi.bytearray_check + check = _testlimitedcapi.bytearray_check self.assertTrue(check(bytearray(b'abc'))) self.assertFalse(check(b'abc')) self.assertTrue(check(ByteArraySubclass(b'abc'))) @@ -32,7 +32,7 @@ def test_check(self): def test_checkexact(self): # Test PyByteArray_CheckExact() - check = _testcapi.bytearray_checkexact + check = _testlimitedcapi.bytearray_checkexact self.assertTrue(check(bytearray(b'abc'))) self.assertFalse(check(b'abc')) self.assertFalse(check(ByteArraySubclass(b'abc'))) @@ -45,7 +45,7 @@ def test_checkexact(self): def test_fromstringandsize(self): # Test PyByteArray_FromStringAndSize() - fromstringandsize = _testcapi.bytearray_fromstringandsize + fromstringandsize = _testlimitedcapi.bytearray_fromstringandsize self.assertEqual(fromstringandsize(b'abc'), bytearray(b'abc')) self.assertEqual(fromstringandsize(b'abc', 2), bytearray(b'ab')) @@ -62,7 +62,7 @@ def test_fromstringandsize(self): def test_fromobject(self): # Test PyByteArray_FromObject() - fromobject = _testcapi.bytearray_fromobject + fromobject = _testlimitedcapi.bytearray_fromobject self.assertEqual(fromobject(b'abc'), bytearray(b'abc')) self.assertEqual(fromobject(bytearray(b'abc')), bytearray(b'abc')) @@ -77,7 +77,7 @@ def test_fromobject(self): def test_size(self): # Test PyByteArray_Size() - size = _testcapi.bytearray_size + size = _testlimitedcapi.bytearray_size self.assertEqual(size(bytearray(b'abc')), 3) self.assertEqual(size(ByteArraySubclass(b'abc')), 3) @@ -88,7 +88,7 @@ def test_size(self): def test_asstring(self): """Test PyByteArray_AsString()""" - asstring = _testcapi.bytearray_asstring + asstring = _testlimitedcapi.bytearray_asstring self.assertEqual(asstring(bytearray(b'abc'), 4), b'abc\0') self.assertEqual(asstring(ByteArraySubclass(b'abc'), 4), b'abc\0') @@ -100,7 +100,7 @@ def test_asstring(self): def test_concat(self): """Test PyByteArray_Concat()""" - concat = _testcapi.bytearray_concat + concat = _testlimitedcapi.bytearray_concat ba = bytearray(b'abc') self.assertEqual(concat(ba, b'def'), bytearray(b'abcdef')) @@ -133,7 +133,7 @@ def test_concat(self): def test_resize(self): """Test PyByteArray_Resize()""" - resize = _testcapi.bytearray_resize + resize = _testlimitedcapi.bytearray_resize ba = bytearray(b'abcdef') self.assertEqual(resize(ba, 3), 0) diff --git a/Lib/test/test_capi/test_bytes.py b/Lib/test/test_capi/test_bytes.py index bb5d724ff187d47..a2ba7708f8fd260 100644 --- a/Lib/test/test_capi/test_bytes.py +++ b/Lib/test/test_capi/test_bytes.py @@ -1,7 +1,7 @@ import unittest from test.support import import_helper -_testcapi = import_helper.import_module('_testcapi') +_testlimitedcapi = import_helper.import_module('_testlimitedcapi') from _testcapi import PY_SSIZE_T_MIN, PY_SSIZE_T_MAX NULL = None @@ -19,7 +19,7 @@ def __bytes__(self): class CAPITest(unittest.TestCase): def test_check(self): # Test PyBytes_Check() - check = _testcapi.bytes_check + check = _testlimitedcapi.bytes_check self.assertTrue(check(b'abc')) self.assertFalse(check('abc')) self.assertFalse(check(bytearray(b'abc'))) @@ -33,7 +33,7 @@ def test_check(self): def test_checkexact(self): # Test PyBytes_CheckExact() - check = _testcapi.bytes_checkexact + check = _testlimitedcapi.bytes_checkexact self.assertTrue(check(b'abc')) self.assertFalse(check('abc')) self.assertFalse(check(bytearray(b'abc'))) @@ -47,7 +47,7 @@ def test_checkexact(self): def test_fromstringandsize(self): # Test PyBytes_FromStringAndSize() - fromstringandsize = _testcapi.bytes_fromstringandsize + fromstringandsize = _testlimitedcapi.bytes_fromstringandsize self.assertEqual(fromstringandsize(b'abc'), b'abc') self.assertEqual(fromstringandsize(b'abc', 2), b'ab') @@ -65,7 +65,7 @@ def test_fromstringandsize(self): def test_fromstring(self): # Test PyBytes_FromString() - fromstring = _testcapi.bytes_fromstring + fromstring = _testlimitedcapi.bytes_fromstring self.assertEqual(fromstring(b'abc\0def'), b'abc') self.assertEqual(fromstring(b''), b'') @@ -74,7 +74,7 @@ def test_fromstring(self): def test_fromobject(self): # Test PyBytes_FromObject() - fromobject = _testcapi.bytes_fromobject + fromobject = _testlimitedcapi.bytes_fromobject self.assertEqual(fromobject(b'abc'), b'abc') self.assertEqual(fromobject(bytearray(b'abc')), b'abc') @@ -88,7 +88,7 @@ def test_fromobject(self): def test_size(self): # Test PyBytes_Size() - size = _testcapi.bytes_size + size = _testlimitedcapi.bytes_size self.assertEqual(size(b'abc'), 3) self.assertEqual(size(BytesSubclass(b'abc')), 3) @@ -100,7 +100,7 @@ def test_size(self): def test_asstring(self): """Test PyBytes_AsString()""" - asstring = _testcapi.bytes_asstring + asstring = _testlimitedcapi.bytes_asstring self.assertEqual(asstring(b'abc', 4), b'abc\0') self.assertEqual(asstring(b'abc\0def', 8), b'abc\0def\0') @@ -111,8 +111,8 @@ def test_asstring(self): def test_asstringandsize(self): """Test PyBytes_AsStringAndSize()""" - asstringandsize = _testcapi.bytes_asstringandsize - asstringandsize_null = _testcapi.bytes_asstringandsize_null + asstringandsize = _testlimitedcapi.bytes_asstringandsize + asstringandsize_null = _testlimitedcapi.bytes_asstringandsize_null self.assertEqual(asstringandsize(b'abc', 4), (b'abc\0', 3)) self.assertEqual(asstringandsize(b'abc\0def', 8), (b'abc\0def\0', 7)) @@ -128,7 +128,7 @@ def test_asstringandsize(self): def test_repr(self): # Test PyBytes_Repr() - bytes_repr = _testcapi.bytes_repr + bytes_repr = _testlimitedcapi.bytes_repr self.assertEqual(bytes_repr(b'''abc''', 0), r"""b'abc'""") self.assertEqual(bytes_repr(b'''abc''', 1), r"""b'abc'""") @@ -149,7 +149,7 @@ def test_repr(self): def test_concat(self, concat=None): """Test PyBytes_Concat()""" if concat is None: - concat = _testcapi.bytes_concat + concat = _testlimitedcapi.bytes_concat self.assertEqual(concat(b'abc', b'def'), b'abcdef') self.assertEqual(concat(b'a\0b', b'c\0d'), b'a\0bc\0d') @@ -182,11 +182,11 @@ def test_concat(self, concat=None): def test_concatanddel(self): """Test PyBytes_ConcatAndDel()""" - self.test_concat(_testcapi.bytes_concatanddel) + self.test_concat(_testlimitedcapi.bytes_concatanddel) def test_decodeescape(self): """Test PyBytes_DecodeEscape()""" - decodeescape = _testcapi.bytes_decodeescape + decodeescape = _testlimitedcapi.bytes_decodeescape self.assertEqual(decodeescape(b'abc'), b'abc') self.assertEqual(decodeescape(br'\t\n\r\x0b\x0c\x00\\\'\"'), diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 67fbef4f2698146..eb0bc13911701a7 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -47,6 +47,7 @@ # Skip this test if the _testcapi module isn't available. _testcapi = import_helper.import_module('_testcapi') +import _testlimitedcapi import _testinternalcapi @@ -1099,21 +1100,76 @@ class Data(_testcapi.ObjExtraData): del d.extra self.assertIsNone(d.extra) - def test_get_type_module_name(self): + def test_get_type_name(self): + class MyType: + pass + + from _testcapi import ( + get_type_name, get_type_qualname, + get_type_fullyqualname, get_type_module_name) + from collections import OrderedDict ht = _testcapi.get_heaptype_for_name() - for cls, expected in { - int: 'builtins', - OrderedDict: 'collections', - ht: '_testcapi', - }.items(): - with self.subTest(repr(cls)): - modname = _testinternalcapi.get_type_module_name(cls) - self.assertEqual(modname, expected) + for cls, fullname, modname, qualname, name in ( + (int, + 'int', + 'builtins', + 'int', + 'int'), + (OrderedDict, + 'collections.OrderedDict', + 'collections', + 'OrderedDict', + 'OrderedDict'), + (ht, + '_testcapi.HeapTypeNameType', + '_testcapi', + 'HeapTypeNameType', + 'HeapTypeNameType'), + (MyType, + f'{__name__}.CAPITest.test_get_type_name..MyType', + __name__, + 'CAPITest.test_get_type_name..MyType', + 'MyType'), + ): + with self.subTest(cls=repr(cls)): + self.assertEqual(get_type_fullyqualname(cls), fullname) + self.assertEqual(get_type_module_name(cls), modname) + self.assertEqual(get_type_qualname(cls), qualname) + self.assertEqual(get_type_name(cls), name) + # override __module__ ht.__module__ = 'test_module' - modname = _testinternalcapi.get_type_module_name(ht) - self.assertEqual(modname, 'test_module') + self.assertEqual(get_type_fullyqualname(ht), 'test_module.HeapTypeNameType') + self.assertEqual(get_type_module_name(ht), 'test_module') + self.assertEqual(get_type_qualname(ht), 'HeapTypeNameType') + self.assertEqual(get_type_name(ht), 'HeapTypeNameType') + + # override __name__ and __qualname__ + MyType.__name__ = 'my_name' + MyType.__qualname__ = 'my_qualname' + self.assertEqual(get_type_fullyqualname(MyType), f'{__name__}.my_qualname') + self.assertEqual(get_type_module_name(MyType), __name__) + self.assertEqual(get_type_qualname(MyType), 'my_qualname') + self.assertEqual(get_type_name(MyType), 'my_name') + + # override also __module__ + MyType.__module__ = 'my_module' + self.assertEqual(get_type_fullyqualname(MyType), 'my_module.my_qualname') + self.assertEqual(get_type_module_name(MyType), 'my_module') + self.assertEqual(get_type_qualname(MyType), 'my_qualname') + self.assertEqual(get_type_name(MyType), 'my_name') + + # PyType_GetFullyQualifiedName() ignores the module if it's "builtins" + # or "__main__" of it is not a string + MyType.__module__ = 'builtins' + self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname') + MyType.__module__ = '__main__' + self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname') + MyType.__module__ = 123 + self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname') + + @requires_limited_api class TestHeapTypeRelative(unittest.TestCase): @@ -1124,7 +1180,7 @@ def test_heaptype_relative_sizes(self): # Test subclassing using "relative" basicsize, see PEP 697 def check(extra_base_size, extra_size): Base, Sub, instance, data_ptr, data_offset, data_size = ( - _testcapi.make_sized_heaptypes( + _testlimitedcapi.make_sized_heaptypes( extra_base_size, -extra_size)) # no alignment shenanigans when inheriting directly @@ -1152,11 +1208,11 @@ def check(extra_base_size, extra_size): # we don't reserve (requested + alignment) or more data self.assertLess(data_size - extra_size, - _testcapi.ALIGNOF_MAX_ALIGN_T) + _testlimitedcapi.ALIGNOF_MAX_ALIGN_T) # The offsets/sizes we calculated should be aligned. - self.assertEqual(data_offset % _testcapi.ALIGNOF_MAX_ALIGN_T, 0) - self.assertEqual(data_size % _testcapi.ALIGNOF_MAX_ALIGN_T, 0) + self.assertEqual(data_offset % _testlimitedcapi.ALIGNOF_MAX_ALIGN_T, 0) + self.assertEqual(data_size % _testlimitedcapi.ALIGNOF_MAX_ALIGN_T, 0) sizes = sorted({0, 1, 2, 3, 4, 7, 8, 123, object.__basicsize__, @@ -1182,7 +1238,7 @@ def test_heaptype_inherit_itemsize(self): object.__basicsize__+1}) for extra_size in sizes: with self.subTest(extra_size=extra_size): - Sub = _testcapi.subclass_var_heaptype( + Sub = _testlimitedcapi.subclass_var_heaptype( _testcapi.HeapCCollection, -extra_size, 0, 0) collection = Sub(1, 2, 3) collection.set_data_to_3s() @@ -1196,7 +1252,7 @@ def test_heaptype_invalid_inheritance(self): with self.assertRaises(SystemError, msg="Cannot extend variable-size class without " + "Py_TPFLAGS_ITEMS_AT_END"): - _testcapi.subclass_heaptype(int, -8, 0) + _testlimitedcapi.subclass_heaptype(int, -8, 0) def test_heaptype_relative_members(self): """Test HeapCCollection subclasses work properly""" @@ -1209,7 +1265,7 @@ def test_heaptype_relative_members(self): for offset in sizes: with self.subTest(extra_base_size=extra_base_size, extra_size=extra_size, offset=offset): if offset < extra_size: - Sub = _testcapi.make_heaptype_with_member( + Sub = _testlimitedcapi.make_heaptype_with_member( extra_base_size, -extra_size, offset, True) Base = Sub.mro()[1] instance = Sub() @@ -1228,29 +1284,29 @@ def test_heaptype_relative_members(self): instance.set_memb_relative(0) else: with self.assertRaises(SystemError): - Sub = _testcapi.make_heaptype_with_member( + Sub = _testlimitedcapi.make_heaptype_with_member( extra_base_size, -extra_size, offset, True) with self.assertRaises(SystemError): - Sub = _testcapi.make_heaptype_with_member( + Sub = _testlimitedcapi.make_heaptype_with_member( extra_base_size, extra_size, offset, True) with self.subTest(extra_base_size=extra_base_size, extra_size=extra_size): with self.assertRaises(SystemError): - Sub = _testcapi.make_heaptype_with_member( + Sub = _testlimitedcapi.make_heaptype_with_member( extra_base_size, -extra_size, -1, True) def test_heaptype_relative_members_errors(self): with self.assertRaisesRegex( SystemError, r"With Py_RELATIVE_OFFSET, basicsize must be negative"): - _testcapi.make_heaptype_with_member(0, 1234, 0, True) + _testlimitedcapi.make_heaptype_with_member(0, 1234, 0, True) with self.assertRaisesRegex( SystemError, r"Member offset out of range \(0\.\.-basicsize\)"): - _testcapi.make_heaptype_with_member(0, -8, 1234, True) + _testlimitedcapi.make_heaptype_with_member(0, -8, 1234, True) with self.assertRaisesRegex( SystemError, r"Member offset out of range \(0\.\.-basicsize\)"): - _testcapi.make_heaptype_with_member(0, -8, -1, True) + _testlimitedcapi.make_heaptype_with_member(0, -8, -1, True) - Sub = _testcapi.make_heaptype_with_member(0, -8, 0, True) + Sub = _testlimitedcapi.make_heaptype_with_member(0, -8, 0, True) instance = Sub() with self.assertRaisesRegex( SystemError, r"PyMember_GetOne used with Py_RELATIVE_OFFSET"): @@ -1796,6 +1852,7 @@ def test_py_config_isoloated_per_interpreter(self): # double checked at the time this test was written. config = _testinternalcapi.get_config() config['int_max_str_digits'] = 55555 + config['parse_argv'] = 0 _testinternalcapi.set_config(config) sub_value = _testinternalcapi.get_config()['int_max_str_digits'] assert sub_value == 55555, sub_value @@ -2264,10 +2321,19 @@ def test_gilstate_matches_current(self): _testcapi.test_current_tstate_matches() +def get_test_funcs(mod, exclude_prefix=None): + funcs = {} + for name in dir(mod): + if not name.startswith('test_'): + continue + if exclude_prefix is not None and name.startswith(exclude_prefix): + continue + funcs[name] = getattr(mod, name) + return funcs + + class Test_testcapi(unittest.TestCase): - locals().update((name, getattr(_testcapi, name)) - for name in dir(_testcapi) - if name.startswith('test_')) + locals().update(get_test_funcs(_testcapi)) # Suppress warning from PyUnicode_FromUnicode(). @warnings_helper.ignore_warnings(category=DeprecationWarning) @@ -2278,11 +2344,13 @@ def test_version_api_data(self): self.assertEqual(_testcapi.Py_Version, sys.hexversion) +class Test_testlimitedcapi(unittest.TestCase): + locals().update(get_test_funcs(_testlimitedcapi)) + + class Test_testinternalcapi(unittest.TestCase): - locals().update((name, getattr(_testinternalcapi, name)) - for name in dir(_testinternalcapi) - if name.startswith('test_') - and not name.startswith('test_lock_')) + locals().update(get_test_funcs(_testinternalcapi, + exclude_prefix='test_lock_')) @threading_helper.requires_working_threading() diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 38c6fa4b47d0c92..b0859a382de5231 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -4,10 +4,11 @@ import textwrap import unittest import gc +import os import _testinternalcapi -from test.support import script_helper +from test.support import script_helper, requires_specialization @contextlib.contextmanager @@ -30,6 +31,7 @@ def clear_executors(func): func.__code__ = func.__code__.replace() +@requires_specialization class TestOptimizerAPI(unittest.TestCase): def test_new_counter_optimizer_dealloc(self): @@ -132,6 +134,7 @@ def get_opnames(ex): return set(iter_opnames(ex)) +@requires_specialization class TestExecutorInvalidation(unittest.TestCase): def setUp(self): @@ -209,6 +212,9 @@ def f(): exe = get_first_executor(f) self.assertIsNone(exe) + +@requires_specialization +@unittest.skipIf(os.getenv("PYTHON_UOPS_OPTIMIZE") == "0", "Needs uop optimizer to run.") class TestUops(unittest.TestCase): def test_basic_loop(self): @@ -325,7 +331,8 @@ def testfunc(a): ex = get_first_executor(testfunc) self.assertIsNotNone(ex) uops = get_opnames(ex) - self.assertIn("_GUARD_IS_NOT_NONE_POP", uops) + self.assertNotIn("_GUARD_IS_NONE_POP", uops) + self.assertNotIn("_GUARD_IS_NOT_NONE_POP", uops) def test_pop_jump_if_not_none(self): def testfunc(a): @@ -341,7 +348,8 @@ def testfunc(a): ex = get_first_executor(testfunc) self.assertIsNotNone(ex) uops = get_opnames(ex) - self.assertIn("_GUARD_IS_NONE_POP", uops) + self.assertNotIn("_GUARD_IS_NONE_POP", uops) + self.assertNotIn("_GUARD_IS_NOT_NONE_POP", uops) def test_pop_jump_if_true(self): def testfunc(n): @@ -568,6 +576,9 @@ def testfunc(n): count = ops.count("_GUARD_IS_TRUE_POP") + ops.count("_GUARD_IS_FALSE_POP") self.assertLessEqual(count, 2) + +@requires_specialization +@unittest.skipIf(os.getenv("PYTHON_UOPS_OPTIMIZE") == "0", "Needs uop optimizer to run.") class TestUopsOptimization(unittest.TestCase): def _run_with_optimizer(self, testfunc, arg): @@ -786,11 +797,14 @@ def test_float_add_constant_propagation(self): def testfunc(n): a = 1.0 for _ in range(n): - a = a + 0.1 + a = a + 0.25 + a = a + 0.25 + a = a + 0.25 + a = a + 0.25 return a res, ex = self._run_with_optimizer(testfunc, 32) - self.assertAlmostEqual(res, 4.2) + self.assertAlmostEqual(res, 33.0) self.assertIsNotNone(ex) uops = get_opnames(ex) guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"] @@ -803,11 +817,14 @@ def test_float_subtract_constant_propagation(self): def testfunc(n): a = 1.0 for _ in range(n): - a = a - 0.1 + a = a - 0.25 + a = a - 0.25 + a = a - 0.25 + a = a - 0.25 return a res, ex = self._run_with_optimizer(testfunc, 32) - self.assertAlmostEqual(res, -2.2) + self.assertAlmostEqual(res, -31.0) self.assertIsNotNone(ex) uops = get_opnames(ex) guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"] @@ -820,11 +837,14 @@ def test_float_multiply_constant_propagation(self): def testfunc(n): a = 1.0 for _ in range(n): - a = a * 2.0 + a = a * 1.0 + a = a * 1.0 + a = a * 1.0 + a = a * 1.0 return a res, ex = self._run_with_optimizer(testfunc, 32) - self.assertAlmostEqual(res, 2 ** 32) + self.assertAlmostEqual(res, 1.0) self.assertIsNotNone(ex) uops = get_opnames(ex) guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"] @@ -833,6 +853,24 @@ def testfunc(n): # We'll also need to verify that propagation actually occurs. self.assertIn("_BINARY_OP_MULTIPLY_FLOAT", uops) + def test_add_unicode_propagation(self): + def testfunc(n): + a = "" + for _ in range(n): + a + a + a + a + a + a + a + a + return a + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertEqual(res, "") + self.assertIsNotNone(ex) + uops = get_opnames(ex) + guard_both_unicode_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_UNICODE"] + self.assertLessEqual(len(guard_both_unicode_count), 1) + self.assertIn("_BINARY_OP_ADD_UNICODE", uops) + def test_compare_op_type_propagation_float(self): def testfunc(n): a = 1.0 @@ -887,5 +925,36 @@ def testfunc(n): self.assertLessEqual(len(guard_both_float_count), 1) self.assertIn("_COMPARE_OP_STR", uops) + def test_type_inconsistency(self): + ns = {} + src = textwrap.dedent(""" + def testfunc(n): + for i in range(n): + x = _test_global + _test_global + """) + exec(src, ns, ns) + testfunc = ns['testfunc'] + ns['_test_global'] = 0 + _, ex = self._run_with_optimizer(testfunc, 16) + self.assertIsNone(ex) + ns['_test_global'] = 1 + _, ex = self._run_with_optimizer(testfunc, 16) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertNotIn("_GUARD_BOTH_INT", uops) + self.assertIn("_BINARY_OP_ADD_INT", uops) + # Try again, but between the runs, set the global to a float. + # This should result in no executor the second time. + ns = {} + exec(src, ns, ns) + testfunc = ns['testfunc'] + ns['_test_global'] = 0 + _, ex = self._run_with_optimizer(testfunc, 16) + self.assertIsNone(ex) + ns['_test_global'] = 3.14 + _, ex = self._run_with_optimizer(testfunc, 16) + self.assertIsNone(ex) + + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_capi/test_sys.py b/Lib/test/test_capi/test_sys.py index 08bf0370fc59b5a..54a8e026d883d49 100644 --- a/Lib/test/test_capi/test_sys.py +++ b/Lib/test/test_capi/test_sys.py @@ -5,9 +5,9 @@ from test.support import import_helper try: - import _testcapi + import _testlimitedcapi except ImportError: - _testcapi = None + _testlimitedcapi = None NULL = None @@ -20,10 +20,10 @@ class CAPITest(unittest.TestCase): maxDiff = None @support.cpython_only - @unittest.skipIf(_testcapi is None, 'need _testcapi module') + @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module') def test_sys_getobject(self): # Test PySys_GetObject() - getobject = _testcapi.sys_getobject + getobject = _testlimitedcapi.sys_getobject self.assertIs(getobject(b'stdout'), sys.stdout) with support.swap_attr(sys, '\U0001f40d', 42): @@ -38,10 +38,10 @@ def test_sys_getobject(self): # CRASHES getobject(NULL) @support.cpython_only - @unittest.skipIf(_testcapi is None, 'need _testcapi module') + @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module') def test_sys_setobject(self): # Test PySys_SetObject() - setobject = _testcapi.sys_setobject + setobject = _testlimitedcapi.sys_setobject value = ['value'] value2 = ['value2'] @@ -70,10 +70,10 @@ def test_sys_setobject(self): # CRASHES setobject(NULL, value) @support.cpython_only - @unittest.skipIf(_testcapi is None, 'need _testcapi module') + @unittest.skipIf(_testlimitedcapi is None, 'need _testlimitedcapi module') def test_sys_getxoptions(self): # Test PySys_GetXOptions() - getxoptions = _testcapi.sys_getxoptions + getxoptions = _testlimitedcapi.sys_getxoptions self.assertIs(getxoptions(), sys._xoptions) diff --git a/Lib/test/test_capi/test_unicode.py b/Lib/test/test_capi/test_unicode.py index bb6161abf4da81d..91c425e483f0fff 100644 --- a/Lib/test/test_capi/test_unicode.py +++ b/Lib/test/test_capi/test_unicode.py @@ -609,6 +609,40 @@ def check_format(expected, format, *args): check_format('xyz', b'%V', None, b'xyz') + # test %T + check_format('type: str', + b'type: %T', py_object("abc")) + check_format(f'type: st', + b'type: %.2T', py_object("abc")) + check_format(f'type: str', + b'type: %10T', py_object("abc")) + + class LocalType: + pass + obj = LocalType() + fullname = f'{__name__}.{LocalType.__qualname__}' + check_format(f'type: {fullname}', + b'type: %T', py_object(obj)) + fullname_alt = f'{__name__}:{LocalType.__qualname__}' + check_format(f'type: {fullname_alt}', + b'type: %T#', py_object(obj)) + + # test %N + check_format('type: str', + b'type: %N', py_object(str)) + check_format(f'type: st', + b'type: %.2N', py_object(str)) + check_format(f'type: str', + b'type: %10N', py_object(str)) + + check_format(f'type: {fullname}', + b'type: %N', py_object(type(obj))) + check_format(f'type: {fullname_alt}', + b'type: %N#', py_object(type(obj))) + with self.assertRaisesRegex(TypeError, "%N argument must be a type"): + check_format('type: str', + b'type: %N', py_object("abc")) + # test %ls check_format('abc', b'%ls', c_wchar_p('abc')) check_format('\u4eba\u6c11', b'%ls', c_wchar_p('\u4eba\u6c11')) diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index f5e9b11ad1cc8ac..a60f087ef2816e4 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -21,9 +21,9 @@ from clinic import DSLParser -def _make_clinic(*, filename='clinic_tests'): +def _make_clinic(*, filename='clinic_tests', limited_capi=False): clang = clinic.CLanguage(filename) - c = clinic.Clinic(clang, filename=filename, limited_capi=False) + c = clinic.Clinic(clang, filename=filename, limited_capi=limited_capi) c.block_parser = clinic.BlockParser('', clang) return c @@ -2146,6 +2146,14 @@ class Foo "" "" expected_error = err_template.format(invalid_kind) self.expect_failure(block, expected_error, lineno=3) + def test_init_cannot_define_a_return_type(self): + block = """ + class Foo "" "" + Foo.__init__ -> long + """ + expected_error = "__init__ methods cannot define a return type" + self.expect_failure(block, expected_error, lineno=1) + def test_invalid_getset(self): annotations = ["@getter", "@setter"] for annotation in annotations: @@ -2167,7 +2175,7 @@ class Foo "" "" obj: int / """ - expected_error = f"{annotation} method cannot define parameters" + expected_error = f"{annotation} methods cannot define parameters" self.expect_failure(block, expected_error) def test_setter_docstring(self): @@ -2647,7 +2655,6 @@ def test_cli_converters(self): bool() double() float() - init() int() long() Py_ssize_t() @@ -3607,6 +3614,46 @@ def test_depr_multi(self): self.assertRaises(TypeError, fn, a="a", b="b", c="c", d="d", e="e", f="f", g="g") +class LimitedCAPIOutputTests(unittest.TestCase): + + def setUp(self): + self.clinic = _make_clinic(limited_capi=True) + + @staticmethod + def wrap_clinic_input(block): + return dedent(f""" + /*[clinic input] + output everything buffer + {block} + [clinic start generated code]*/ + /*[clinic input] + dump buffer + [clinic start generated code]*/ + """) + + def test_limited_capi_float(self): + block = self.wrap_clinic_input(""" + func + f: float + / + """) + generated = self.clinic.parse(block) + self.assertNotIn("PyFloat_AS_DOUBLE", generated) + self.assertIn("float f;", generated) + self.assertIn("f = (float) PyFloat_AsDouble", generated) + + def test_limited_capi_double(self): + block = self.wrap_clinic_input(""" + func + f: double + / + """) + generated = self.clinic.parse(block) + self.assertNotIn("PyFloat_AS_DOUBLE", generated) + self.assertIn("double f;", generated) + self.assertIn("f = PyFloat_AsDouble", generated) + + try: import _testclinic_limited except ImportError: @@ -3637,6 +3684,53 @@ def test_my_int_sum(self): with self.assertRaises(TypeError): _testclinic_limited.my_int_sum(1, "str") + def test_my_double_sum(self): + for func in ( + _testclinic_limited.my_float_sum, + _testclinic_limited.my_double_sum, + ): + with self.subTest(func=func.__name__): + self.assertEqual(func(1.0, 2.5), 3.5) + with self.assertRaises(TypeError): + func() + with self.assertRaises(TypeError): + func(1) + with self.assertRaises(TypeError): + func(1., "2") + + def test_get_file_descriptor(self): + # test 'file descriptor' converter: call PyObject_AsFileDescriptor() + get_fd = _testclinic_limited.get_file_descriptor + + class MyInt(int): + pass + + class MyFile: + def __init__(self, fd): + self._fd = fd + def fileno(self): + return self._fd + + for fd in (0, 1, 2, 5, 123_456): + self.assertEqual(get_fd(fd), fd) + + myint = MyInt(fd) + self.assertEqual(get_fd(myint), fd) + + myfile = MyFile(fd) + self.assertEqual(get_fd(myfile), fd) + + with self.assertRaises(OverflowError): + get_fd(2**256) + with self.assertWarnsRegex(RuntimeWarning, + "bool is used as a file descriptor"): + get_fd(True) + with self.assertRaises(TypeError): + get_fd(1.0) + with self.assertRaises(TypeError): + get_fd("abc") + with self.assertRaises(TypeError): + get_fd(None) class PermutationTests(unittest.TestCase): @@ -3937,7 +4031,7 @@ def test_Function_and_Parameter_reprs(self): cls=None, c_basename=None, full_name='foofoo', - return_converter=clinic.init_return_converter(), + return_converter=clinic.int_return_converter(), kind=clinic.FunctionKind.METHOD_INIT, coexist=False ) diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index 1fe3b2fe53c0b63..fb832aed3152ff2 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -38,6 +38,7 @@ def verify_valid_flag(self, cmd_line): self.assertNotIn(b'Traceback', err) return out + @support.cpython_only def test_help(self): self.verify_valid_flag('-h') self.verify_valid_flag('-?') @@ -48,14 +49,17 @@ def test_help(self): self.assertNotIn(b'-X dev', out) self.assertLess(len(lines), 50) + @support.cpython_only def test_help_env(self): out = self.verify_valid_flag('--help-env') self.assertIn(b'PYTHONHOME', out) + @support.cpython_only def test_help_xoptions(self): out = self.verify_valid_flag('--help-xoptions') self.assertIn(b'-X dev', out) + @support.cpython_only def test_help_all(self): out = self.verify_valid_flag('--help-all') lines = out.splitlines() @@ -74,6 +78,7 @@ def test_optimize(self): def test_site_flag(self): self.verify_valid_flag('-S') + @support.cpython_only def test_version(self): version = ('Python %d.%d' % sys.version_info[:2]).encode("ascii") for switch in '-V', '--version', '-VV': @@ -139,6 +144,7 @@ def run_python(*args): else: self.assertEqual(err, b'') + @support.cpython_only def test_xoption_frozen_modules(self): tests = { ('=on', 'FrozenImporter'), @@ -153,6 +159,7 @@ def test_xoption_frozen_modules(self): res = assert_python_ok(*cmd) self.assertRegex(res.out.decode('utf-8'), expected) + @support.cpython_only def test_env_var_frozen_modules(self): tests = { ('on', 'FrozenImporter'), @@ -579,6 +586,7 @@ def test_del___main__(self): print("del sys.modules['__main__']", file=script) assert_python_ok(filename) + @support.cpython_only def test_unknown_options(self): rc, out, err = assert_python_failure('-E', '-z') self.assertIn(b'Unknown option: -z', err) @@ -634,15 +642,13 @@ def test_sys_flags_set(self): PYTHONDONTWRITEBYTECODE=value, PYTHONVERBOSE=value, ) - dont_write_bytecode = int(bool(value)) + expected_bool = int(bool(value)) code = ( "import sys; " "sys.stderr.write(str(sys.flags)); " f"""sys.exit(not ( - sys.flags.debug == sys.flags.optimize == - sys.flags.verbose == - {expected} - and sys.flags.dont_write_bytecode == {dont_write_bytecode} + sys.flags.optimize == sys.flags.verbose == {expected} + and sys.flags.debug == sys.flags.dont_write_bytecode == {expected_bool} ))""" ) with self.subTest(envar_value=value): @@ -693,6 +699,7 @@ def run_xdev(self, *args, check_exitcode=True, xdev=True): self.assertEqual(proc.returncode, 0, proc) return proc.stdout.rstrip() + @support.cpython_only def test_xdev(self): # sys.flags.dev_mode code = "import sys; print(sys.flags.dev_mode)" @@ -871,6 +878,39 @@ def test_pythondevmode_env(self): self.assertEqual(proc.stdout.rstrip(), 'True') self.assertEqual(proc.returncode, 0, proc) + @unittest.skipUnless(support.Py_GIL_DISABLED, + "PYTHON_GIL and -X gil only supported in Py_GIL_DISABLED builds") + def test_python_gil(self): + cases = [ + # (env, opt, expected, msg) + (None, None, 'None', "no options set"), + ('0', None, '0', "PYTHON_GIL=0"), + ('1', None, '1', "PYTHON_GIL=1"), + ('1', '0', '0', "-X gil=0 overrides PYTHON_GIL=1"), + (None, '0', '0', "-X gil=0"), + (None, '1', '1', "-X gil=1"), + ] + + code = "import sys; print(sys.flags.gil)" + environ = dict(os.environ) + + for env, opt, expected, msg in cases: + with self.subTest(msg, env=env, opt=opt): + environ.pop('PYTHON_GIL', None) + if env is not None: + environ['PYTHON_GIL'] = env + extra_args = [] + if opt is not None: + extra_args = ['-X', f'gil={opt}'] + + proc = subprocess.run([sys.executable, *extra_args, '-c', code], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, env=environ) + self.assertEqual(proc.returncode, 0, proc) + self.assertEqual(proc.stdout.rstrip(), expected) + self.assertEqual(proc.stderr, '') + @unittest.skipUnless(sys.platform == 'win32', 'bpo-32457 only applies on Windows') def test_argv0_normalization(self): @@ -883,6 +923,7 @@ def test_argv0_normalization(self): self.assertEqual(proc.returncode, 0, proc) self.assertEqual(proc.stdout.strip(), b'0') + @support.cpython_only def test_parsing_error(self): args = [sys.executable, '-I', '--unknown-option'] proc = subprocess.run(args, diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py index ff511a625a0194d..fe3776d6dd93378 100644 --- a/Lib/test/test_codecs.py +++ b/Lib/test/test_codecs.py @@ -482,11 +482,11 @@ def test_only_one_bom(self): def test_badbom(self): s = io.BytesIO(4*b"\xff") f = codecs.getreader(self.encoding)(s) - self.assertRaises(UnicodeError, f.read) + self.assertRaises(UnicodeDecodeError, f.read) s = io.BytesIO(8*b"\xff") f = codecs.getreader(self.encoding)(s) - self.assertRaises(UnicodeError, f.read) + self.assertRaises(UnicodeDecodeError, f.read) def test_partial(self): self.check_partial( @@ -666,11 +666,11 @@ def test_only_one_bom(self): def test_badbom(self): s = io.BytesIO(b"\xff\xff") f = codecs.getreader(self.encoding)(s) - self.assertRaises(UnicodeError, f.read) + self.assertRaises(UnicodeDecodeError, f.read) s = io.BytesIO(b"\xff\xff\xff\xff") f = codecs.getreader(self.encoding)(s) - self.assertRaises(UnicodeError, f.read) + self.assertRaises(UnicodeDecodeError, f.read) def test_partial(self): self.check_partial( @@ -1356,13 +1356,29 @@ def test_decode(self): def test_decode_invalid(self): testcases = [ - (b"xn--w&", "strict", UnicodeError()), + (b"xn--w&", "strict", UnicodeDecodeError("punycode", b"", 5, 6, "")), + (b"&egbpdaj6bu4bxfgehfvwxn", "strict", UnicodeDecodeError("punycode", b"", 0, 1, "")), + (b"egbpdaj6bu&4bx&fgehfvwxn", "strict", UnicodeDecodeError("punycode", b"", 10, 11, "")), + (b"egbpdaj6bu4bxfgehfvwxn&", "strict", UnicodeDecodeError("punycode", b"", 22, 23, "")), + (b"\xFFProprostnemluvesky-uyb24dma41a", "strict", UnicodeDecodeError("ascii", b"", 0, 1, "")), + (b"Pro\xFFprostnemluvesky-uyb24dma41a", "strict", UnicodeDecodeError("ascii", b"", 3, 4, "")), + (b"Proprost&nemluvesky-uyb24&dma41a", "strict", UnicodeDecodeError("punycode", b"", 25, 26, "")), + (b"Proprostnemluvesky&-&uyb24dma41a", "strict", UnicodeDecodeError("punycode", b"", 20, 21, "")), + (b"Proprostnemluvesky-&uyb24dma41a", "strict", UnicodeDecodeError("punycode", b"", 19, 20, "")), + (b"Proprostnemluvesky-uyb24d&ma41a", "strict", UnicodeDecodeError("punycode", b"", 25, 26, "")), + (b"Proprostnemluvesky-uyb24dma41a&", "strict", UnicodeDecodeError("punycode", b"", 30, 31, "")), (b"xn--w&", "ignore", "xn-"), ] for puny, errors, expected in testcases: with self.subTest(puny=puny, errors=errors): if isinstance(expected, Exception): - self.assertRaises(UnicodeError, puny.decode, "punycode", errors) + with self.assertRaises(UnicodeDecodeError) as cm: + puny.decode("punycode", errors) + exc = cm.exception + self.assertEqual(exc.encoding, expected.encoding) + self.assertEqual(exc.object, puny) + self.assertEqual(exc.start, expected.start) + self.assertEqual(exc.end, expected.end) else: self.assertEqual(puny.decode("punycode", errors), expected) @@ -1532,7 +1548,7 @@ def test_nameprep(self): orig = str(orig, "utf-8", "surrogatepass") if prepped is None: # Input contains prohibited characters - self.assertRaises(UnicodeError, nameprep, orig) + self.assertRaises(UnicodeEncodeError, nameprep, orig) else: prepped = str(prepped, "utf-8", "surrogatepass") try: @@ -1542,11 +1558,46 @@ def test_nameprep(self): class IDNACodecTest(unittest.TestCase): + + invalid_decode_testcases = [ + (b"\xFFpython.org", UnicodeDecodeError("idna", b"\xFFpython.org", 0, 1, "")), + (b"pyt\xFFhon.org", UnicodeDecodeError("idna", b"pyt\xFFhon.org", 3, 4, "")), + (b"python\xFF.org", UnicodeDecodeError("idna", b"python\xFF.org", 6, 7, "")), + (b"python.\xFForg", UnicodeDecodeError("idna", b"python.\xFForg", 7, 8, "")), + (b"python.o\xFFrg", UnicodeDecodeError("idna", b"python.o\xFFrg", 8, 9, "")), + (b"python.org\xFF", UnicodeDecodeError("idna", b"python.org\xFF", 10, 11, "")), + (b"xn--pythn-&mua.org", UnicodeDecodeError("idna", b"xn--pythn-&mua.org", 10, 11, "")), + (b"xn--pythn-m&ua.org", UnicodeDecodeError("idna", b"xn--pythn-m&ua.org", 11, 12, "")), + (b"xn--pythn-mua&.org", UnicodeDecodeError("idna", b"xn--pythn-mua&.org", 13, 14, "")), + ] + invalid_encode_testcases = [ + (f"foo.{'\xff'*60}", UnicodeEncodeError("idna", f"foo.{'\xff'*60}", 4, 64, "")), + ("あさ.\u034f", UnicodeEncodeError("idna", "あさ.\u034f", 3, 4, "")), + ] + def test_builtin_decode(self): self.assertEqual(str(b"python.org", "idna"), "python.org") self.assertEqual(str(b"python.org.", "idna"), "python.org.") self.assertEqual(str(b"xn--pythn-mua.org", "idna"), "pyth\xf6n.org") self.assertEqual(str(b"xn--pythn-mua.org.", "idna"), "pyth\xf6n.org.") + self.assertEqual(str(b"XN--pythn-mua.org.", "idna"), "pyth\xf6n.org.") + self.assertEqual(str(b"xN--pythn-mua.org.", "idna"), "pyth\xf6n.org.") + self.assertEqual(str(b"Xn--pythn-mua.org.", "idna"), "pyth\xf6n.org.") + self.assertEqual(str(b"bugs.xn--pythn-mua.org.", "idna"), + "bugs.pyth\xf6n.org.") + self.assertEqual(str(b"bugs.XN--pythn-mua.org.", "idna"), + "bugs.pyth\xf6n.org.") + + def test_builtin_decode_invalid(self): + for case, expected in self.invalid_decode_testcases: + with self.subTest(case=case, expected=expected): + with self.assertRaises(UnicodeDecodeError) as cm: + case.decode("idna") + exc = cm.exception + self.assertEqual(exc.encoding, expected.encoding) + self.assertEqual(exc.object, expected.object) + self.assertEqual(exc.start, expected.start, msg=f'reason: {exc.reason}') + self.assertEqual(exc.end, expected.end) def test_builtin_encode(self): self.assertEqual("python.org".encode("idna"), b"python.org") @@ -1554,10 +1605,21 @@ def test_builtin_encode(self): self.assertEqual("pyth\xf6n.org".encode("idna"), b"xn--pythn-mua.org") self.assertEqual("pyth\xf6n.org.".encode("idna"), b"xn--pythn-mua.org.") + def test_builtin_encode_invalid(self): + for case, expected in self.invalid_encode_testcases: + with self.subTest(case=case, expected=expected): + with self.assertRaises(UnicodeEncodeError) as cm: + case.encode("idna") + exc = cm.exception + self.assertEqual(exc.encoding, expected.encoding) + self.assertEqual(exc.object, expected.object) + self.assertEqual(exc.start, expected.start) + self.assertEqual(exc.end, expected.end) + def test_builtin_decode_length_limit(self): - with self.assertRaisesRegex(UnicodeError, "way too long"): + with self.assertRaisesRegex(UnicodeDecodeError, "way too long"): (b"xn--016c"+b"a"*1100).decode("idna") - with self.assertRaisesRegex(UnicodeError, "too long"): + with self.assertRaisesRegex(UnicodeDecodeError, "too long"): (b"xn--016c"+b"a"*70).decode("idna") def test_stream(self): @@ -1595,6 +1657,39 @@ def test_incremental_decode(self): self.assertEqual(decoder.decode(b"rg."), "org.") self.assertEqual(decoder.decode(b"", True), "") + def test_incremental_decode_invalid(self): + iterdecode_testcases = [ + (b"\xFFpython.org", UnicodeDecodeError("idna", b"\xFF", 0, 1, "")), + (b"pyt\xFFhon.org", UnicodeDecodeError("idna", b"pyt\xFF", 3, 4, "")), + (b"python\xFF.org", UnicodeDecodeError("idna", b"python\xFF", 6, 7, "")), + (b"python.\xFForg", UnicodeDecodeError("idna", b"\xFF", 0, 1, "")), + (b"python.o\xFFrg", UnicodeDecodeError("idna", b"o\xFF", 1, 2, "")), + (b"python.org\xFF", UnicodeDecodeError("idna", b"org\xFF", 3, 4, "")), + (b"xn--pythn-&mua.org", UnicodeDecodeError("idna", b"xn--pythn-&mua.", 10, 11, "")), + (b"xn--pythn-m&ua.org", UnicodeDecodeError("idna", b"xn--pythn-m&ua.", 11, 12, "")), + (b"xn--pythn-mua&.org", UnicodeDecodeError("idna", b"xn--pythn-mua&.", 13, 14, "")), + ] + for case, expected in iterdecode_testcases: + with self.subTest(case=case, expected=expected): + with self.assertRaises(UnicodeDecodeError) as cm: + list(codecs.iterdecode((bytes([c]) for c in case), "idna")) + exc = cm.exception + self.assertEqual(exc.encoding, expected.encoding) + self.assertEqual(exc.object, expected.object) + self.assertEqual(exc.start, expected.start) + self.assertEqual(exc.end, expected.end) + + decoder = codecs.getincrementaldecoder("idna")() + for case, expected in self.invalid_decode_testcases: + with self.subTest(case=case, expected=expected): + with self.assertRaises(UnicodeDecodeError) as cm: + decoder.decode(case) + exc = cm.exception + self.assertEqual(exc.encoding, expected.encoding) + self.assertEqual(exc.object, expected.object) + self.assertEqual(exc.start, expected.start) + self.assertEqual(exc.end, expected.end) + def test_incremental_encode(self): self.assertEqual( b"".join(codecs.iterencode("python.org", "idna")), @@ -1623,6 +1718,23 @@ def test_incremental_encode(self): self.assertEqual(encoder.encode("ample.org."), b"xn--xample-9ta.org.") self.assertEqual(encoder.encode("", True), b"") + def test_incremental_encode_invalid(self): + iterencode_testcases = [ + (f"foo.{'\xff'*60}", UnicodeEncodeError("idna", f"{'\xff'*60}", 0, 60, "")), + ("あさ.\u034f", UnicodeEncodeError("idna", "\u034f", 0, 1, "")), + ] + for case, expected in iterencode_testcases: + with self.subTest(case=case, expected=expected): + with self.assertRaises(UnicodeEncodeError) as cm: + list(codecs.iterencode(case, "idna")) + exc = cm.exception + self.assertEqual(exc.encoding, expected.encoding) + self.assertEqual(exc.object, expected.object) + self.assertEqual(exc.start, expected.start) + self.assertEqual(exc.end, expected.end) + + # codecs.getincrementalencoder.encode() does not throw an error + def test_errors(self): """Only supports "strict" error handler""" "python.org".encode("idna", "strict") diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 0126780982059a3..d3e69bfedccd07d 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -527,11 +527,11 @@ def test_compile_ast(self): self.assertRaises(TypeError, compile, co1, '', 'eval') # raise exception when node type is no start node - self.assertRaises(TypeError, compile, _ast.If(), '', 'exec') + self.assertRaises(TypeError, compile, _ast.If(test=_ast.Name(id='x', ctx=_ast.Load())), '', 'exec') # raise exception when node has invalid children ast = _ast.Module() - ast.body = [_ast.BoolOp()] + ast.body = [_ast.BoolOp(op=_ast.Or())] self.assertRaises(TypeError, compile, ast, '', 'exec') def test_compile_invalid_typealias(self): diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py index 0ec6013dc11e3e6..14c2af19e3eb28b 100644 --- a/Lib/test/test_compileall.py +++ b/Lib/test/test_compileall.py @@ -977,7 +977,7 @@ class CommandLineTestsNoSourceEpoch(CommandLineTestsBase, -@unittest.skipUnless(hasattr(os, 'link'), 'requires os.link') +@os_helper.skip_unless_hardlink class HardlinkDedupTestsBase: # Test hardlink_dupes parameter of compileall.compile_dir() diff --git a/Lib/test/test_concurrent_futures/__init__.py b/Lib/test/test_concurrent_futures/__init__.py index 430fa93aa456a2d..b38bd38d338f0ee 100644 --- a/Lib/test/test_concurrent_futures/__init__.py +++ b/Lib/test/test_concurrent_futures/__init__.py @@ -1,10 +1,12 @@ import os.path import unittest from test import support -from test.support import import_helper +from test.support import threading_helper + + +# Adjust if we ever have a platform with processes but not threads. +threading_helper.requires_working_threading(module=True) -# Skip tests if _multiprocessing wasn't built. -import_helper.import_module('_multiprocessing') if support.check_sanitizer(address=True, memory=True): # gh-90791: Skip the test because it is too slow when Python is built diff --git a/Lib/test/test_concurrent_futures/test_init.py b/Lib/test/test_concurrent_futures/test_init.py index d79a6367701fb4b..113a4d1c54be03b 100644 --- a/Lib/test/test_concurrent_futures/test_init.py +++ b/Lib/test/test_concurrent_futures/test_init.py @@ -5,6 +5,8 @@ import unittest import sys from concurrent.futures._base import BrokenExecutor +from concurrent.futures.process import _check_system_limits + from logging.handlers import QueueHandler from test import support @@ -117,6 +119,11 @@ class FailingInitializerResourcesTest(unittest.TestCase): """ def _test(self, test_class): + try: + _check_system_limits() + except NotImplementedError: + self.skipTest("ProcessPoolExecutor unavailable on this system") + runner = unittest.TextTestRunner() runner.run(test_class('test_initializer')) diff --git a/Lib/test/test_concurrent_futures/test_process_pool.py b/Lib/test/test_concurrent_futures/test_process_pool.py index 7fc59a05f3deac0..70444bb147fadca 100644 --- a/Lib/test/test_concurrent_futures/test_process_pool.py +++ b/Lib/test/test_concurrent_futures/test_process_pool.py @@ -201,13 +201,13 @@ def test_python_finalization_error(self): # QueueFeederThread. orig_start_new_thread = threading._start_joinable_thread nthread = 0 - def mock_start_new_thread(func, *args): + def mock_start_new_thread(func, *args, **kwargs): nonlocal nthread if nthread >= 1: raise RuntimeError("can't create new thread at " "interpreter shutdown") nthread += 1 - return orig_start_new_thread(func, *args) + return orig_start_new_thread(func, *args, **kwargs) with support.swap_attr(threading, '_start_joinable_thread', mock_start_new_thread): diff --git a/Lib/test/test_concurrent_futures/test_shutdown.py b/Lib/test/test_concurrent_futures/test_shutdown.py index 45dab7a75fdd504..7a4065afd46fc84 100644 --- a/Lib/test/test_concurrent_futures/test_shutdown.py +++ b/Lib/test/test_concurrent_futures/test_shutdown.py @@ -247,7 +247,9 @@ def test_cancel_futures_wait_false(self): # Errors in atexit hooks don't change the process exit code, check # stderr manually. self.assertFalse(err) - self.assertEqual(out.strip(), b"apple") + # gh-116682: stdout may be empty if shutdown happens before task + # starts executing. + self.assertIn(out.strip(), [b"apple", b""]) class ProcessPoolShutdownTest(ExecutorShutdownTest): diff --git a/Lib/test/test_concurrent_futures/util.py b/Lib/test/test_concurrent_futures/util.py index dc48bec796b87fe..3b8ec3e205d5aa7 100644 --- a/Lib/test/test_concurrent_futures/util.py +++ b/Lib/test/test_concurrent_futures/util.py @@ -85,6 +85,8 @@ def get_context(self): self.skipTest("ProcessPoolExecutor unavailable on this system") if sys.platform == "win32": self.skipTest("require unix system") + if support.check_sanitizer(thread=True): + self.skipTest("TSAN doesn't support threads after fork") return super().get_context() @@ -111,6 +113,8 @@ def get_context(self): self.skipTest("ProcessPoolExecutor unavailable on this system") if sys.platform == "win32": self.skipTest("require unix system") + if support.check_sanitizer(thread=True): + self.skipTest("TSAN doesn't support threads after fork") return super().get_context() @@ -136,6 +140,12 @@ def strip_mixin(name): def setup_module(): - unittest.addModuleCleanup(multiprocessing.util._cleanup_tests) + try: + _check_system_limits() + except NotImplementedError: + pass + else: + unittest.addModuleCleanup(multiprocessing.util._cleanup_tests) + thread_info = threading_helper.threading_setup() unittest.addModuleCleanup(threading_helper.threading_cleanup, *thread_info) diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 2d7dfbde7082ee3..5d58e34740adafe 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -1555,6 +1555,30 @@ def test_source_as_bytes(self): "'[badbad'" ) + def test_keys_without_value_with_extra_whitespace(self): + lines = [ + '[SECT]\n', + 'KEY1\n', + ' KEY2 = VAL2\n', # note the Space before the key! + ] + parser = configparser.ConfigParser( + comment_prefixes="", + allow_no_value=True, + strict=False, + delimiters=('=',), + interpolation=None, + ) + with self.assertRaises(configparser.MultilineContinuationError) as dse: + parser.read_file(lines) + self.assertEqual( + str(dse.exception), + "Key without value continued with an indented line.\n" + "file: '', line: 3\n" + "' KEY2 = VAL2\\n'" + ) + + + class CoverageOneHundredTestCase(unittest.TestCase): """Covers edge cases in the codebase.""" diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py index 5217f2a71ec0f96..d74ab7e016f78c0 100644 --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -265,9 +265,11 @@ def test_write_lineterminator(self): writer = csv.writer(sio, lineterminator=lineterminator) writer.writerow(['a', 'b']) writer.writerow([1, 2]) + writer.writerow(['\r', '\n']) self.assertEqual(sio.getvalue(), f'a,b{lineterminator}' - f'1,2{lineterminator}') + f'1,2{lineterminator}' + f'"\r","\n"{lineterminator}') def test_write_iterable(self): self._write_test(iter(['a', 1, 'p,q']), 'a,1,"p,q"') @@ -507,22 +509,44 @@ def test_read_linenum(self): self.assertEqual(r.line_num, 3) def test_roundtrip_quoteed_newlines(self): - with TemporaryFile("w+", encoding="utf-8", newline='') as fileobj: - writer = csv.writer(fileobj) - rows = [['a\nb','b'],['c','x\r\nd']] - writer.writerows(rows) - fileobj.seek(0) - for i, row in enumerate(csv.reader(fileobj)): - self.assertEqual(row, rows[i]) + rows = [ + ['\na', 'b\nc', 'd\n'], + ['\re', 'f\rg', 'h\r'], + ['\r\ni', 'j\r\nk', 'l\r\n'], + ['\n\rm', 'n\n\ro', 'p\n\r'], + ['\r\rq', 'r\r\rs', 't\r\r'], + ['\n\nu', 'v\n\nw', 'x\n\n'], + ] + for lineterminator in '\r\n', '\n', '\r': + with self.subTest(lineterminator=lineterminator): + with TemporaryFile("w+", encoding="utf-8", newline='') as fileobj: + writer = csv.writer(fileobj, lineterminator=lineterminator) + writer.writerows(rows) + fileobj.seek(0) + for i, row in enumerate(csv.reader(fileobj)): + self.assertEqual(row, rows[i]) def test_roundtrip_escaped_unquoted_newlines(self): - with TemporaryFile("w+", encoding="utf-8", newline='') as fileobj: - writer = csv.writer(fileobj,quoting=csv.QUOTE_NONE,escapechar="\\") - rows = [['a\nb','b'],['c','x\r\nd']] - writer.writerows(rows) - fileobj.seek(0) - for i, row in enumerate(csv.reader(fileobj,quoting=csv.QUOTE_NONE,escapechar="\\")): - self.assertEqual(row,rows[i]) + rows = [ + ['\na', 'b\nc', 'd\n'], + ['\re', 'f\rg', 'h\r'], + ['\r\ni', 'j\r\nk', 'l\r\n'], + ['\n\rm', 'n\n\ro', 'p\n\r'], + ['\r\rq', 'r\r\rs', 't\r\r'], + ['\n\nu', 'v\n\nw', 'x\n\n'], + ] + for lineterminator in '\r\n', '\n', '\r': + with self.subTest(lineterminator=lineterminator): + with TemporaryFile("w+", encoding="utf-8", newline='') as fileobj: + writer = csv.writer(fileobj, lineterminator=lineterminator, + quoting=csv.QUOTE_NONE, escapechar="\\") + writer.writerows(rows) + fileobj.seek(0) + for i, row in enumerate(csv.reader(fileobj, + quoting=csv.QUOTE_NONE, + escapechar="\\")): + self.assertEqual(row, rows[i]) + class TestDialectRegistry(unittest.TestCase): def test_registry_badargs(self): diff --git a/Lib/test/test_ctypes/test_loading.py b/Lib/test/test_ctypes/test_loading.py index 59d7f51935f3cd8..b218e9e7720c79a 100644 --- a/Lib/test/test_ctypes/test_loading.py +++ b/Lib/test/test_ctypes/test_loading.py @@ -59,11 +59,15 @@ def test_load_version(self): self.assertRaises(OSError, cdll.LoadLibrary, self.unknowndll) def test_find(self): + found = False for name in ("c", "m"): lib = find_library(name) if lib: + found = True cdll.LoadLibrary(lib) CDLL(lib) + if not found: + self.skipTest("Could not find c and m libraries") @unittest.skipUnless(os.name == "nt", 'test specific to Windows') diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py index 39d4ace8d4a1d8c..d9af05c306eb303 100644 --- a/Lib/test/test_email/test_email.py +++ b/Lib/test/test_email/test_email.py @@ -337,6 +337,21 @@ def test_nonascii_as_string_without_cte(self): msg = email.message_from_bytes(source) self.assertEqual(msg.as_string(), expected) + def test_nonascii_as_string_with_ascii_charset(self): + m = textwrap.dedent("""\ + MIME-Version: 1.0 + Content-type: text/plain; charset="us-ascii" + Content-Transfer-Encoding: 8bit + + Test if non-ascii messages with no Content-Transfer-Encoding set + can be as_string'd: + Föö bär + """) + source = m.encode('iso-8859-1') + expected = source.decode('ascii', 'replace') + msg = email.message_from_bytes(source) + self.assertEqual(msg.as_string(), expected) + def test_nonascii_as_string_without_content_type_and_cte(self): m = textwrap.dedent("""\ MIME-Version: 1.0 diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 6c60854bbd76cc0..ab1d579ed12755e 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -443,31 +443,31 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): CONFIG_COMPAT = { '_config_init': API_COMPAT, - 'isolated': 0, - 'use_environment': 1, - 'dev_mode': 0, + 'isolated': False, + 'use_environment': True, + 'dev_mode': False, - 'install_signal_handlers': 1, - 'use_hash_seed': 0, + 'install_signal_handlers': True, + 'use_hash_seed': False, 'hash_seed': 0, 'int_max_str_digits': sys.int_info.default_max_str_digits, 'cpu_count': -1, - 'faulthandler': 0, + 'faulthandler': False, 'tracemalloc': 0, - 'perf_profiling': 0, - 'import_time': 0, - 'code_debug_ranges': 1, - 'show_ref_count': 0, - 'dump_refs': 0, + 'perf_profiling': False, + 'import_time': False, + 'code_debug_ranges': True, + 'show_ref_count': False, + 'dump_refs': False, 'dump_refs_file': None, - 'malloc_stats': 0, + 'malloc_stats': False, 'filesystem_encoding': GET_DEFAULT_CONFIG, 'filesystem_errors': GET_DEFAULT_CONFIG, 'pycache_prefix': None, 'program_name': GET_DEFAULT_CONFIG, - 'parse_argv': 0, + 'parse_argv': False, 'argv': [""], 'orig_argv': [], @@ -484,45 +484,47 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'exec_prefix': GET_DEFAULT_CONFIG, 'base_exec_prefix': GET_DEFAULT_CONFIG, 'module_search_paths': GET_DEFAULT_CONFIG, - 'module_search_paths_set': 1, + 'module_search_paths_set': True, 'platlibdir': sys.platlibdir, 'stdlib_dir': GET_DEFAULT_CONFIG, - 'site_import': 1, + 'site_import': True, 'bytes_warning': 0, - 'warn_default_encoding': 0, - 'inspect': 0, - 'interactive': 0, + 'warn_default_encoding': False, + 'inspect': False, + 'interactive': False, 'optimization_level': 0, - 'parser_debug': 0, - 'write_bytecode': 1, + 'parser_debug': False, + 'write_bytecode': True, 'verbose': 0, - 'quiet': 0, - 'user_site_directory': 1, - 'configure_c_stdio': 0, - 'buffered_stdio': 1, + 'quiet': False, + 'user_site_directory': True, + 'configure_c_stdio': False, + 'buffered_stdio': True, 'stdio_encoding': GET_DEFAULT_CONFIG, 'stdio_errors': GET_DEFAULT_CONFIG, - 'skip_source_first_line': 0, + 'skip_source_first_line': False, 'run_command': None, 'run_module': None, 'run_filename': None, 'sys_path_0': None, - '_install_importlib': 1, + '_install_importlib': True, 'check_hash_pycs_mode': 'default', - 'pathconfig_warnings': 1, - '_init_main': 1, + 'pathconfig_warnings': True, + '_init_main': True, 'use_frozen_modules': not support.Py_DEBUG, - 'safe_path': 0, + 'safe_path': False, '_is_python_build': IGNORE_CONFIG, } if Py_STATS: CONFIG_COMPAT['_pystats'] = 0 if support.Py_DEBUG: CONFIG_COMPAT['run_presite'] = None + if support.Py_GIL_DISABLED: + CONFIG_COMPAT['enable_gil'] = -1 if MS_WINDOWS: CONFIG_COMPAT.update({ 'legacy_windows_stdio': 0, @@ -530,22 +532,22 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): CONFIG_PYTHON = dict(CONFIG_COMPAT, _config_init=API_PYTHON, - configure_c_stdio=1, - parse_argv=2, + configure_c_stdio=True, + parse_argv=True, ) CONFIG_ISOLATED = dict(CONFIG_COMPAT, _config_init=API_ISOLATED, - isolated=1, - use_environment=0, - user_site_directory=0, - safe_path=1, - dev_mode=0, - install_signal_handlers=0, - use_hash_seed=0, - faulthandler=0, + isolated=True, + use_environment=False, + user_site_directory=False, + safe_path=True, + dev_mode=False, + install_signal_handlers=False, + use_hash_seed=False, + faulthandler=False, tracemalloc=0, - perf_profiling=0, - pathconfig_warnings=0, + perf_profiling=False, + pathconfig_warnings=False, ) if MS_WINDOWS: CONFIG_ISOLATED['legacy_windows_stdio'] = 0 @@ -851,15 +853,15 @@ def test_init_from_config(self): 'utf8_mode': 1, } config = { - 'install_signal_handlers': 0, - 'use_hash_seed': 1, + 'install_signal_handlers': False, + 'use_hash_seed': True, 'hash_seed': 123, 'tracemalloc': 2, - 'perf_profiling': 0, - 'import_time': 1, - 'code_debug_ranges': 0, - 'show_ref_count': 1, - 'malloc_stats': 1, + 'perf_profiling': False, + 'import_time': True, + 'code_debug_ranges': False, + 'show_ref_count': True, + 'malloc_stats': True, 'stdio_encoding': 'iso8859-1', 'stdio_errors': 'replace', @@ -872,7 +874,7 @@ def test_init_from_config(self): '-X', 'cmdline_xoption', '-c', 'pass', 'arg2'], - 'parse_argv': 2, + 'parse_argv': True, 'xoptions': [ 'config_xoption1=3', 'config_xoption2=', @@ -886,26 +888,26 @@ def test_init_from_config(self): ], 'run_command': 'pass\n', - 'site_import': 0, + 'site_import': False, 'bytes_warning': 1, - 'inspect': 1, - 'interactive': 1, + 'inspect': True, + 'interactive': True, 'optimization_level': 2, - 'write_bytecode': 0, + 'write_bytecode': False, 'verbose': 1, - 'quiet': 1, - 'configure_c_stdio': 1, - 'buffered_stdio': 0, - 'user_site_directory': 0, - 'faulthandler': 1, + 'quiet': True, + 'configure_c_stdio': True, + 'buffered_stdio': False, + 'user_site_directory': False, + 'faulthandler': True, 'platlibdir': 'my_platlibdir', 'module_search_paths': self.IGNORE_CONFIG, - 'safe_path': 1, + 'safe_path': True, 'int_max_str_digits': 31337, 'cpu_count': 4321, 'check_hash_pycs_mode': 'always', - 'pathconfig_warnings': 0, + 'pathconfig_warnings': False, } if Py_STATS: config['_pystats'] = 1 @@ -917,28 +919,28 @@ def test_init_compat_env(self): 'allocator': ALLOCATOR_FOR_CONFIG, } config = { - 'use_hash_seed': 1, + 'use_hash_seed': True, 'hash_seed': 42, 'tracemalloc': 2, - 'perf_profiling': 0, - 'import_time': 1, - 'code_debug_ranges': 0, - 'malloc_stats': 1, - 'inspect': 1, + 'perf_profiling': False, + 'import_time': True, + 'code_debug_ranges': False, + 'malloc_stats': True, + 'inspect': True, 'optimization_level': 2, 'pythonpath_env': '/my/path', 'pycache_prefix': 'env_pycache_prefix', - 'write_bytecode': 0, + 'write_bytecode': False, 'verbose': 1, - 'buffered_stdio': 0, + 'buffered_stdio': False, 'stdio_encoding': 'iso8859-1', 'stdio_errors': 'replace', - 'user_site_directory': 0, - 'faulthandler': 1, + 'user_site_directory': False, + 'faulthandler': True, 'warnoptions': ['EnvVar'], 'platlibdir': 'env_platlibdir', 'module_search_paths': self.IGNORE_CONFIG, - 'safe_path': 1, + 'safe_path': True, 'int_max_str_digits': 4567, } if Py_STATS: @@ -952,32 +954,32 @@ def test_init_python_env(self): 'utf8_mode': 1, } config = { - 'use_hash_seed': 1, + 'use_hash_seed': True, 'hash_seed': 42, 'tracemalloc': 2, - 'perf_profiling': 0, - 'import_time': 1, - 'code_debug_ranges': 0, - 'malloc_stats': 1, - 'inspect': 1, + 'perf_profiling': False, + 'import_time': True, + 'code_debug_ranges': False, + 'malloc_stats': True, + 'inspect': True, 'optimization_level': 2, 'pythonpath_env': '/my/path', 'pycache_prefix': 'env_pycache_prefix', - 'write_bytecode': 0, + 'write_bytecode': False, 'verbose': 1, - 'buffered_stdio': 0, + 'buffered_stdio': False, 'stdio_encoding': 'iso8859-1', 'stdio_errors': 'replace', - 'user_site_directory': 0, - 'faulthandler': 1, + 'user_site_directory': False, + 'faulthandler': True, 'warnoptions': ['EnvVar'], 'platlibdir': 'env_platlibdir', 'module_search_paths': self.IGNORE_CONFIG, - 'safe_path': 1, + 'safe_path': True, 'int_max_str_digits': 4567, } if Py_STATS: - config['_pystats'] = 1 + config['_pystats'] = True self.check_all_configs("test_init_python_env", config, preconfig, api=API_PYTHON) @@ -1002,8 +1004,8 @@ def test_init_dev_mode(self): 'allocator': PYMEM_ALLOCATOR_DEBUG, } config = { - 'faulthandler': 1, - 'dev_mode': 1, + 'faulthandler': True, + 'dev_mode': True, 'warnoptions': ['default'], } self.check_all_configs("test_init_dev_mode", config, preconfig, @@ -1019,11 +1021,11 @@ def test_preinit_parse_argv(self): 'argv': ['script.py'], 'orig_argv': ['python3', '-X', 'dev', '-P', 'script.py'], 'run_filename': os.path.abspath('script.py'), - 'dev_mode': 1, - 'faulthandler': 1, + 'dev_mode': True, + 'faulthandler': True, 'warnoptions': ['default'], 'xoptions': ['dev'], - 'safe_path': 1, + 'safe_path': True, } self.check_all_configs("test_preinit_parse_argv", config, preconfig, api=API_PYTHON) @@ -1048,30 +1050,30 @@ def test_preinit_dont_parse_argv(self): def test_init_isolated_flag(self): config = { - 'isolated': 1, - 'safe_path': 1, - 'use_environment': 0, - 'user_site_directory': 0, + 'isolated': True, + 'safe_path': True, + 'use_environment': False, + 'user_site_directory': False, } self.check_all_configs("test_init_isolated_flag", config, api=API_PYTHON) def test_preinit_isolated1(self): # _PyPreConfig.isolated=1, _PyCoreConfig.isolated not set config = { - 'isolated': 1, - 'safe_path': 1, - 'use_environment': 0, - 'user_site_directory': 0, + 'isolated': True, + 'safe_path': True, + 'use_environment': False, + 'user_site_directory': False, } self.check_all_configs("test_preinit_isolated1", config, api=API_COMPAT) def test_preinit_isolated2(self): # _PyPreConfig.isolated=0, _PyCoreConfig.isolated=1 config = { - 'isolated': 1, - 'safe_path': 1, - 'use_environment': 0, - 'user_site_directory': 0, + 'isolated': True, + 'safe_path': True, + 'use_environment': False, + 'user_site_directory': False, } self.check_all_configs("test_preinit_isolated2", config, api=API_COMPAT) @@ -1139,7 +1141,7 @@ def test_init_run_main(self): 'orig_argv': ['python3', '-c', code, 'arg2'], 'program_name': './python3', 'run_command': code + '\n', - 'parse_argv': 2, + 'parse_argv': True, 'sys_path_0': '', } self.check_all_configs("test_init_run_main", config, api=API_PYTHON) @@ -1154,7 +1156,7 @@ def test_init_main(self): 'arg2'], 'program_name': './python3', 'run_command': code + '\n', - 'parse_argv': 2, + 'parse_argv': True, '_init_main': 0, 'sys_path_0': '', } @@ -1164,12 +1166,12 @@ def test_init_main(self): def test_init_parse_argv(self): config = { - 'parse_argv': 2, + 'parse_argv': True, 'argv': ['-c', 'arg1', '-v', 'arg3'], 'orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'], 'program_name': './argv0', 'run_command': 'pass\n', - 'use_environment': 0, + 'use_environment': False, } self.check_all_configs("test_init_parse_argv", config, api=API_PYTHON) @@ -1178,7 +1180,7 @@ def test_init_dont_parse_argv(self): 'parse_argv': 0, } config = { - 'parse_argv': 0, + 'parse_argv': False, 'argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'], 'orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'], 'program_name': './argv0', @@ -1653,20 +1655,20 @@ def test_get_argc_argv(self): def test_init_use_frozen_modules(self): tests = { - ('=on', 1), - ('=off', 0), - ('=', 1), - ('', 1), + ('=on', True), + ('=off', False), + ('=', True), + ('', True), } for raw, expected in tests: optval = f'frozen_modules{raw}' config = { - 'parse_argv': 2, + 'parse_argv': True, 'argv': ['-c'], 'orig_argv': ['./argv0', '-X', optval, '-c', 'pass'], 'program_name': './argv0', 'run_command': 'pass\n', - 'use_environment': 1, + 'use_environment': True, 'xoptions': [optval], 'use_frozen_modules': expected, } @@ -1792,9 +1794,9 @@ def test_frozenmain(self): sys.argv ['./argv0', '-E', 'arg1', 'arg2'] config program_name: ./argv0 config executable: {executable} - config use_environment: 1 - config configure_c_stdio: 1 - config buffered_stdio: 0 + config use_environment: True + config configure_c_stdio: True + config buffered_stdio: False """).lstrip() self.assertEqual(out, expected) diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index cf3e042de1a4b4f..6418d243db65ce2 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -447,7 +447,7 @@ def spam(cls): def test_bad_new_super(self): with self.assertRaisesRegex( TypeError, - 'has no members defined', + 'do not use .super...__new__;', ): class BadSuper(self.enum_type): def __new__(cls, value): @@ -3409,6 +3409,36 @@ def __new__(cls, int_value, *value_aliases): self.assertIs(Types(2), Types.NetList) self.assertIs(Types('nl'), Types.NetList) + def test_second_tuple_item_is_falsey(self): + class Cardinal(Enum): + RIGHT = (1, 0) + UP = (0, 1) + LEFT = (-1, 0) + DOWN = (0, -1) + self.assertIs(Cardinal(1, 0), Cardinal.RIGHT) + self.assertIs(Cardinal(-1, 0), Cardinal.LEFT) + + def test_no_members(self): + with self.assertRaisesRegex( + TypeError, + 'has no members', + ): + Enum(7) + with self.assertRaisesRegex( + TypeError, + 'has no members', + ): + Flag(7) + + def test_empty_names(self): + for nothing in '', [], {}: + for e_type in None, int: + empty_enum = Enum('empty_enum', nothing, type=e_type) + self.assertEqual(len(empty_enum), 0) + self.assertRaisesRegex(TypeError, 'has no members', empty_enum, 0) + self.assertRaisesRegex(TypeError, '.int. object is not iterable', Enum, 'bad_enum', names=0) + self.assertRaisesRegex(TypeError, '.int. object is not iterable', Enum, 'bad_enum', 0, type=int) + class TestOrder(unittest.TestCase): "test usage of the `_order_` attribute" @@ -4033,6 +4063,8 @@ def test_global_repr_conform1(self): @reraise_if_not_enum(NoName) def test_global_enum_str(self): + self.assertEqual(repr(NoName.ONE), 'test_enum.ONE') + self.assertEqual(repr(NoName(0)), 'test_enum.NoName(0)') self.assertEqual(str(NoName.ONE & NoName.TWO), 'NoName(0)') self.assertEqual(str(NoName(0)), 'NoName(0)') diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index c7e76414ff07154..c5eff8ad8ccca17 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -301,6 +301,7 @@ def baz(): { 6 0="""''', 5, 13) + check('b"fooжжж"'.encode(), 1, 1, 1, 10) # Errors thrown by symtable.c check('x = [(yield i) for i in range(3)]', 1, 7) diff --git a/Lib/test/test_external_inspection.py b/Lib/test/test_external_inspection.py new file mode 100644 index 000000000000000..86c07de507e39cb --- /dev/null +++ b/Lib/test/test_external_inspection.py @@ -0,0 +1,84 @@ +import unittest +import os +import textwrap +import importlib +import sys +from test.support import os_helper, SHORT_TIMEOUT +from test.support.script_helper import make_script + +import subprocess + +PROCESS_VM_READV_SUPPORTED = False + +try: + from _testexternalinspection import PROCESS_VM_READV_SUPPORTED + from _testexternalinspection import get_stack_trace +except ImportError: + unittest.skip("Test only runs when _testexternalinspection is available") + +def _make_test_script(script_dir, script_basename, source): + to_return = make_script(script_dir, script_basename, source) + importlib.invalidate_caches() + return to_return + +class TestGetStackTrace(unittest.TestCase): + + @unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux", "Test only runs on Linux and MacOS") + @unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, "Test only runs on Linux with process_vm_readv support") + def test_remote_stack_trace(self): + # Spawn a process with some realistic Python code + script = textwrap.dedent("""\ + import time, sys, os + def bar(): + for x in range(100): + if x == 50: + baz() + def baz(): + foo() + + def foo(): + fifo = sys.argv[1] + with open(sys.argv[1], "w") as fifo: + fifo.write("ready") + time.sleep(1000) + + bar() + """) + stack_trace = None + with os_helper.temp_dir() as work_dir: + script_dir = os.path.join(work_dir, "script_pkg") + os.mkdir(script_dir) + fifo = f"{work_dir}/the_fifo" + os.mkfifo(fifo) + script_name = _make_test_script(script_dir, 'script', script) + try: + p = subprocess.Popen([sys.executable, script_name, str(fifo)]) + with open(fifo, "r") as fifo_file: + response = fifo_file.read() + self.assertEqual(response, "ready") + stack_trace = get_stack_trace(p.pid) + except PermissionError: + self.skipTest("Insufficient permissions to read the stack trace") + finally: + os.remove(fifo) + p.kill() + p.terminate() + p.wait(timeout=SHORT_TIMEOUT) + + + expected_stack_trace = [ + 'foo', + 'baz', + 'bar', + '' + ] + self.assertEqual(stack_trace, expected_stack_trace) + + @unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux", "Test only runs on Linux and MacOS") + @unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, "Test only runs on Linux with process_vm_readv support") + def test_self_trace(self): + stack_trace = get_stack_trace(os.getpid()) + self.assertEqual(stack_trace[0], "test_self_trace") + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_fcntl.py b/Lib/test/test_fcntl.py index 6d734d052454d34..5fae0de7423c878 100644 --- a/Lib/test/test_fcntl.py +++ b/Lib/test/test_fcntl.py @@ -129,7 +129,8 @@ def test_fcntl_bad_file_overflow(self): fcntl.fcntl(BadFile(INT_MIN - 1), fcntl.F_SETFL, os.O_NONBLOCK) @unittest.skipIf( - platform.machine().startswith('arm') and platform.system() == 'Linux', + platform.machine().startswith(("arm", "aarch")) + and platform.system() in ("Linux", "Android"), "ARM Linux returns EINVAL for F_NOTIFY DN_MULTISHOT") def test_fcntl_64_bit(self): # Issue #1309352: fcntl shouldn't fail when the third arg fits in a diff --git a/Lib/test/test_filecmp.py b/Lib/test/test_filecmp.py index 9b5ac12bccc58f9..b5df71678264a81 100644 --- a/Lib/test/test_filecmp.py +++ b/Lib/test/test_filecmp.py @@ -8,11 +8,24 @@ from test.support import os_helper +def _create_file_shallow_equal(template_path, new_path): + """create a file with the same size and mtime but different content.""" + shutil.copy2(template_path, new_path) + with open(new_path, 'r+b') as f: + next_char = bytearray(f.read(1)) + next_char[0] = (next_char[0] + 1) % 256 + f.seek(0) + f.write(next_char) + shutil.copystat(template_path, new_path) + assert os.stat(new_path).st_size == os.stat(template_path).st_size + assert os.stat(new_path).st_mtime == os.stat(template_path).st_mtime + class FileCompareTestCase(unittest.TestCase): def setUp(self): self.name = os_helper.TESTFN self.name_same = os_helper.TESTFN + '-same' self.name_diff = os_helper.TESTFN + '-diff' + self.name_same_shallow = os_helper.TESTFN + '-same-shallow' data = 'Contents of file go here.\n' for name in [self.name, self.name_same, self.name_diff]: with open(name, 'w', encoding="utf-8") as output: @@ -20,12 +33,19 @@ def setUp(self): with open(self.name_diff, 'a+', encoding="utf-8") as output: output.write('An extra line.\n') + + for name in [self.name_same, self.name_diff]: + shutil.copystat(self.name, name) + + _create_file_shallow_equal(self.name, self.name_same_shallow) + self.dir = tempfile.gettempdir() def tearDown(self): os.unlink(self.name) os.unlink(self.name_same) os.unlink(self.name_diff) + os.unlink(self.name_same_shallow) def test_matching(self): self.assertTrue(filecmp.cmp(self.name, self.name), @@ -36,12 +56,17 @@ def test_matching(self): "Comparing file to identical file fails") self.assertTrue(filecmp.cmp(self.name, self.name_same, shallow=False), "Comparing file to identical file fails") + self.assertTrue(filecmp.cmp(self.name, self.name_same_shallow), + "Shallow identical files should be considered equal") def test_different(self): self.assertFalse(filecmp.cmp(self.name, self.name_diff), "Mismatched files compare as equal") self.assertFalse(filecmp.cmp(self.name, self.dir), "File and directory compare as equal") + self.assertFalse(filecmp.cmp(self.name, self.name_same_shallow, + shallow=False), + "Mismatched file to shallow identical file compares as equal") def test_cache_clear(self): first_compare = filecmp.cmp(self.name, self.name_same, shallow=False) @@ -56,6 +81,8 @@ def setUp(self): self.dir = os.path.join(tmpdir, 'dir') self.dir_same = os.path.join(tmpdir, 'dir-same') self.dir_diff = os.path.join(tmpdir, 'dir-diff') + self.dir_diff_file = os.path.join(tmpdir, 'dir-diff-file') + self.dir_same_shallow = os.path.join(tmpdir, 'dir-same-shallow') # Another dir is created under dir_same, but it has a name from the # ignored list so it should not affect testing results. @@ -63,7 +90,17 @@ def setUp(self): self.caseinsensitive = os.path.normcase('A') == os.path.normcase('a') data = 'Contents of file go here.\n' - for dir in (self.dir, self.dir_same, self.dir_diff, self.dir_ignored): + + shutil.rmtree(self.dir, True) + os.mkdir(self.dir) + subdir_path = os.path.join(self.dir, 'subdir') + os.mkdir(subdir_path) + dir_file_path = os.path.join(self.dir, "file") + with open(dir_file_path, 'w', encoding="utf-8") as output: + output.write(data) + + for dir in (self.dir_same, self.dir_same_shallow, + self.dir_diff, self.dir_diff_file): shutil.rmtree(dir, True) os.mkdir(dir) subdir_path = os.path.join(dir, 'subdir') @@ -72,14 +109,25 @@ def setUp(self): fn = 'FiLe' # Verify case-insensitive comparison else: fn = 'file' - with open(os.path.join(dir, fn), 'w', encoding="utf-8") as output: - output.write(data) + + file_path = os.path.join(dir, fn) + + if dir is self.dir_same_shallow: + _create_file_shallow_equal(dir_file_path, file_path) + else: + shutil.copy2(dir_file_path, file_path) with open(os.path.join(self.dir_diff, 'file2'), 'w', encoding="utf-8") as output: output.write('An extra file.\n') + # Add different file2 with respect to dir_diff + with open(os.path.join(self.dir_diff_file, 'file2'), 'w', encoding="utf-8") as output: + output.write('Different contents.\n') + + def tearDown(self): - for dir in (self.dir, self.dir_same, self.dir_diff): + for dir in (self.dir, self.dir_same, self.dir_diff, + self.dir_same_shallow, self.dir_diff_file): shutil.rmtree(dir) def test_default_ignores(self): @@ -102,11 +150,7 @@ def test_cmpfiles(self): shallow=False), "Comparing directory to same fails") - # Add different file2 - with open(os.path.join(self.dir, 'file2'), 'w', encoding="utf-8") as output: - output.write('Different contents.\n') - - self.assertFalse(filecmp.cmpfiles(self.dir, self.dir_same, + self.assertFalse(filecmp.cmpfiles(self.dir, self.dir_diff_file, ['file', 'file2']) == (['file'], ['file2'], []), "Comparing mismatched directories fails") @@ -116,11 +160,22 @@ def _assert_lists(self, actual, expected): """Assert that two lists are equal, up to ordering.""" self.assertEqual(sorted(actual), sorted(expected)) + def test_dircmp_identical_directories(self): + self._assert_dircmp_identical_directories() + self._assert_dircmp_identical_directories(shallow=False) - def test_dircmp(self): + def test_dircmp_different_file(self): + self._assert_dircmp_different_file() + self._assert_dircmp_different_file(shallow=False) + + def test_dircmp_different_directories(self): + self._assert_dircmp_different_directories() + self._assert_dircmp_different_directories(shallow=False) + + def _assert_dircmp_identical_directories(self, **options): # Check attributes for comparison of two identical directories left_dir, right_dir = self.dir, self.dir_same - d = filecmp.dircmp(left_dir, right_dir) + d = filecmp.dircmp(left_dir, right_dir, **options) self.assertEqual(d.left, left_dir) self.assertEqual(d.right, right_dir) if self.caseinsensitive: @@ -142,9 +197,10 @@ def test_dircmp(self): ] self._assert_report(d.report, expected_report) + def _assert_dircmp_different_directories(self, **options): # Check attributes for comparison of two different directories (right) left_dir, right_dir = self.dir, self.dir_diff - d = filecmp.dircmp(left_dir, right_dir) + d = filecmp.dircmp(left_dir, right_dir, **options) self.assertEqual(d.left, left_dir) self.assertEqual(d.right, right_dir) self._assert_lists(d.left_list, ['file', 'subdir']) @@ -164,12 +220,8 @@ def test_dircmp(self): self._assert_report(d.report, expected_report) # Check attributes for comparison of two different directories (left) - left_dir, right_dir = self.dir, self.dir_diff - shutil.move( - os.path.join(self.dir_diff, 'file2'), - os.path.join(self.dir, 'file2') - ) - d = filecmp.dircmp(left_dir, right_dir) + left_dir, right_dir = self.dir_diff, self.dir + d = filecmp.dircmp(left_dir, right_dir, **options) self.assertEqual(d.left, left_dir) self.assertEqual(d.right, right_dir) self._assert_lists(d.left_list, ['file', 'file2', 'subdir']) @@ -180,27 +232,51 @@ def test_dircmp(self): self.assertEqual(d.same_files, ['file']) self.assertEqual(d.diff_files, []) expected_report = [ - "diff {} {}".format(self.dir, self.dir_diff), - "Only in {} : ['file2']".format(self.dir), + "diff {} {}".format(self.dir_diff, self.dir), + "Only in {} : ['file2']".format(self.dir_diff), "Identical files : ['file']", "Common subdirectories : ['subdir']", ] self._assert_report(d.report, expected_report) - # Add different file2 - with open(os.path.join(self.dir_diff, 'file2'), 'w', encoding="utf-8") as output: - output.write('Different contents.\n') - d = filecmp.dircmp(self.dir, self.dir_diff) + + def _assert_dircmp_different_file(self, **options): + # A different file2 + d = filecmp.dircmp(self.dir_diff, self.dir_diff_file, **options) self.assertEqual(d.same_files, ['file']) self.assertEqual(d.diff_files, ['file2']) expected_report = [ - "diff {} {}".format(self.dir, self.dir_diff), + "diff {} {}".format(self.dir_diff, self.dir_diff_file), "Identical files : ['file']", "Differing files : ['file2']", "Common subdirectories : ['subdir']", ] self._assert_report(d.report, expected_report) + def test_dircmp_no_shallow_different_file(self): + # A non shallow different file2 + d = filecmp.dircmp(self.dir, self.dir_same_shallow, shallow=False) + self.assertEqual(d.same_files, []) + self.assertEqual(d.diff_files, ['file']) + expected_report = [ + "diff {} {}".format(self.dir, self.dir_same_shallow), + "Differing files : ['file']", + "Common subdirectories : ['subdir']", + ] + self._assert_report(d.report, expected_report) + + def test_dircmp_shallow_same_file(self): + # A non shallow different file2 + d = filecmp.dircmp(self.dir, self.dir_same_shallow) + self.assertEqual(d.same_files, ['file']) + self.assertEqual(d.diff_files, []) + expected_report = [ + "diff {} {}".format(self.dir, self.dir_same_shallow), + "Identical files : ['file']", + "Common subdirectories : ['subdir']", + ] + self._assert_report(d.report, expected_report) + def test_dircmp_subdirs_type(self): """Check that dircmp.subdirs respects subclassing.""" class MyDirCmp(filecmp.dircmp): diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py index f8812c281c2deba..8e744a1223e86f8 100644 --- a/Lib/test/test_frame.py +++ b/Lib/test/test_frame.py @@ -293,71 +293,6 @@ def gen(): """) assert_python_ok("-c", code) - @support.cpython_only - @unittest.skipIf(Py_GIL_DISABLED, "test requires precise GC scheduling") - def test_sneaky_frame_object(self): - - def trace(frame, event, arg): - """ - Don't actually do anything, just force a frame object to be created. - """ - - def callback(phase, info): - """ - Yo dawg, I heard you like frames, so I'm allocating a frame while - you're allocating a frame, so you can have a frame while you have a - frame! - """ - nonlocal sneaky_frame_object - sneaky_frame_object = sys._getframe().f_back.f_back - # We're done here: - gc.callbacks.remove(callback) - - def f(): - while True: - yield - - old_threshold = gc.get_threshold() - old_callbacks = gc.callbacks[:] - old_enabled = gc.isenabled() - old_trace = sys.gettrace() - try: - # Stop the GC for a second while we set things up: - gc.disable() - # Create a paused generator: - g = f() - next(g) - # Move all objects to the oldest generation, and tell the GC to run - # on the *very next* allocation: - gc.collect() - gc.set_threshold(1, 0, 0) - sys._clear_internal_caches() - # Okay, so here's the nightmare scenario: - # - We're tracing the resumption of a generator, which creates a new - # frame object. - # - The allocation of this frame object triggers a collection - # *before* the frame object is actually created. - # - During the collection, we request the exact same frame object. - # This test does it with a GC callback, but in real code it would - # likely be a trace function, weakref callback, or finalizer. - # - The collection finishes, and the original frame object is - # created. We now have two frame objects fighting over ownership - # of the same interpreter frame! - sys.settrace(trace) - gc.callbacks.append(callback) - sneaky_frame_object = None - gc.enable() - next(g) - # g.gi_frame should be the frame object from the callback (the - # one that was *requested* second, but *created* first): - self.assertIs(g.gi_frame, sneaky_frame_object) - finally: - gc.set_threshold(*old_threshold) - gc.callbacks[:] = old_callbacks - sys.settrace(old_trace) - if old_enabled: - gc.enable() - @support.cpython_only @threading_helper.requires_working_threading() def test_sneaky_frame_object_teardown(self): diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 2c814d5e8888403..1a6d8afe6ed6fe6 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -31,10 +31,6 @@ decimal = import_helper.import_fresh_module('decimal', fresh=['_decimal']) -_partial_types = [py_functools.partial] -if c_functools: - _partial_types.append(c_functools.partial) - @contextlib.contextmanager def replaced_module(name, replacement): @@ -207,10 +203,7 @@ def test_repr(self): kwargs = {'a': object(), 'b': object()} kwargs_reprs = ['a={a!r}, b={b!r}'.format_map(kwargs), 'b={b!r}, a={a!r}'.format_map(kwargs)] - if self.partial in _partial_types: - name = 'functools.partial' - else: - name = self.partial.__name__ + name = f"{self.partial.__module__}.{self.partial.__qualname__}" f = self.partial(capture) self.assertEqual(f'{name}({capture!r})', repr(f)) @@ -229,10 +222,7 @@ def test_repr(self): for kwargs_repr in kwargs_reprs]) def test_recursive_repr(self): - if self.partial in _partial_types: - name = 'functools.partial' - else: - name = self.partial.__name__ + name = f"{self.partial.__module__}.{self.partial.__qualname__}" f = self.partial(capture) f.__setstate__((f, (), {}, {})) @@ -2706,7 +2696,10 @@ def static_func(arg: int) -> str: A().static_func ): with self.subTest(meth=meth): - self.assertEqual(meth.__doc__, 'My function docstring') + self.assertEqual(meth.__doc__, + ('My function docstring' + if support.HAVE_DOCSTRINGS + else None)) self.assertEqual(meth.__annotations__['arg'], int) self.assertEqual(A.func.__name__, 'func') @@ -2795,7 +2788,10 @@ def decorated_classmethod(cls, arg: int) -> str: WithSingleDispatch().decorated_classmethod ): with self.subTest(meth=meth): - self.assertEqual(meth.__doc__, 'My function docstring') + self.assertEqual(meth.__doc__, + ('My function docstring' + if support.HAVE_DOCSTRINGS + else None)) self.assertEqual(meth.__annotations__['arg'], int) self.assertEqual( @@ -3138,7 +3134,10 @@ def test_access_from_class(self): self.assertIsInstance(CachedCostItem.cost, py_functools.cached_property) def test_doc(self): - self.assertEqual(CachedCostItem.cost.__doc__, "The cost of the item.") + self.assertEqual(CachedCostItem.cost.__doc__, + ("The cost of the item." + if support.HAVE_DOCSTRINGS + else None)) def test_module(self): self.assertEqual(CachedCostItem.cost.__module__, CachedCostItem.__module__) diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index dd09643788d62fb..f1a7afac0bcd195 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -5,7 +5,7 @@ from test.support.import_helper import import_module from test.support.os_helper import temp_dir, TESTFN, unlink from test.support.script_helper import assert_python_ok, make_script -from test.support import threading_helper +from test.support import threading_helper, gc_threshold import gc import sys @@ -1330,6 +1330,7 @@ def callback(ignored): # with an empty __dict__. self.assertEqual(x, None) + @gc_threshold(1000, 0, 0) def test_bug1055820d(self): # Corresponds to temp2d.py in the bug report. This is very much like # test_bug1055820c, but uses a __del__ method instead of a weakref @@ -1397,6 +1398,32 @@ def __del__(self): # empty __dict__. self.assertEqual(x, None) + @gc_threshold(1000, 0, 0) + def test_indirect_calls_with_gc_disabled(self): + junk = [] + i = 0 + detector = GC_Detector() + while not detector.gc_happened: + i += 1 + if i > 10000: + self.fail("gc didn't happen after 10000 iterations") + junk.append([]) # this will eventually trigger gc + + try: + gc.disable() + junk = [] + i = 0 + detector = GC_Detector() + while not detector.gc_happened: + i += 1 + if i > 10000: + break + junk.append([]) # this may eventually trigger gc (if it is enabled) + + self.assertEqual(i, 10001) + finally: + gc.enable() + class PythonFinalizationTests(unittest.TestCase): def test_ast_fini(self): diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 0d2ccd558d436d5..7b9dd36f85454f9 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -33,7 +33,7 @@ def skip_if_different_mount_drives(): import parser from stack import Stack import tier1_generator - import tier2_abstract_generator + import optimizer_generator def handle_stderr(): @@ -350,12 +350,15 @@ def test_cache_effect(self): output = """ TARGET(OP) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 4; INSTRUCTION_STATS(OP); PyObject *value; value = stack_pointer[-1]; uint16_t counter = read_u16(&this_instr[1].cache); + (void)counter; uint32_t extra = read_u32(&this_instr[2].cache); + (void)extra; stack_pointer += -1; DISPATCH(); } @@ -399,6 +402,7 @@ def test_macro_instruction(self): INSTRUCTION_STATS(OP); PREDICTED(OP); _Py_CODEUNIT *this_instr = next_instr - 6; + (void)this_instr; PyObject *right; PyObject *left; PyObject *arg2; @@ -408,6 +412,7 @@ def test_macro_instruction(self): left = stack_pointer[-2]; { uint16_t counter = read_u16(&this_instr[1].cache); + (void)counter; op1(left, right); } /* Skip 2 cache entries */ @@ -415,6 +420,7 @@ def test_macro_instruction(self): arg2 = stack_pointer[-3]; { uint32_t extra = read_u32(&this_instr[4].cache); + (void)extra; res = op2(arg2, left, right); } stack_pointer[-3] = res; @@ -424,6 +430,7 @@ def test_macro_instruction(self): TARGET(OP1) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 2; INSTRUCTION_STATS(OP1); PyObject *right; @@ -431,6 +438,7 @@ def test_macro_instruction(self): right = stack_pointer[-1]; left = stack_pointer[-2]; uint16_t counter = read_u16(&this_instr[1].cache); + (void)counter; op1(left, right); DISPATCH(); } @@ -841,7 +849,7 @@ def run_cases_test(self, input: str, input2: str, expected: str): temp_input.flush() with handle_stderr(): - tier2_abstract_generator.generate_tier2_abstract_from_files( + optimizer_generator.generate_tier2_abstract_from_files( [self.temp_input_filename, self.temp_input2_filename], self.temp_output_filename ) @@ -890,8 +898,8 @@ def test_overridden_abstract_args(self): """ output = """ case OP: { - _Py_UOpsSymType *arg1; - _Py_UOpsSymType *out; + _Py_UopsSymbol *arg1; + _Py_UopsSymbol *out; arg1 = stack_pointer[-1]; eggs(); stack_pointer[-1] = out; @@ -899,8 +907,8 @@ def test_overridden_abstract_args(self): } case OP2: { - _Py_UOpsSymType *out; - out = sym_new_unknown(ctx); + _Py_UopsSymbol *out; + out = sym_new_not_null(ctx); if (out == NULL) goto out_of_space; stack_pointer[-1] = out; break; @@ -924,16 +932,16 @@ def test_no_overridden_case(self): """ output = """ case OP: { - _Py_UOpsSymType *out; - out = sym_new_unknown(ctx); + _Py_UopsSymbol *out; + out = sym_new_not_null(ctx); if (out == NULL) goto out_of_space; stack_pointer[-1] = out; break; } case OP2: { - _Py_UOpsSymType *arg1; - _Py_UOpsSymType *out; + _Py_UopsSymbol *arg1; + _Py_UopsSymbol *out; arg1 = stack_pointer[-1]; stack_pointer[-1] = out; break; diff --git a/Lib/test/test_glob.py b/Lib/test/test_glob.py index 8b2ea8f89f5dafb..2de997501039ade 100644 --- a/Lib/test/test_glob.py +++ b/Lib/test/test_glob.py @@ -344,6 +344,18 @@ def test_glob_non_directory(self): eq(self.rglob('nonexistent', '*'), []) eq(self.rglob('nonexistent', '**'), []) + @unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()') + @unittest.skipIf(sys.platform == "vxworks", + "fifo requires special path on VxWorks") + def test_glob_named_pipe(self): + path = os.path.join(self.tempdir, 'mypipe') + os.mkfifo(path) + self.assertEqual(self.rglob('mypipe'), [path]) + self.assertEqual(self.rglob('mypipe*'), [path]) + self.assertEqual(self.rglob('mypipe', ''), []) + self.assertEqual(self.rglob('mypipe', 'sub'), []) + self.assertEqual(self.rglob('mypipe', '*'), []) + def test_glob_many_open_files(self): depth = 30 base = os.path.join(self.tempdir, 'deep') @@ -440,9 +452,9 @@ def fn(pat): self.assertEqual(fn('?'), r'(?s:[^/])\Z') self.assertEqual(fn('**'), r'(?s:.*)\Z') self.assertEqual(fn('**/**'), r'(?s:.*)\Z') - self.assertRaises(ValueError, fn, '***') - self.assertRaises(ValueError, fn, 'a**') - self.assertRaises(ValueError, fn, '**b') + self.assertEqual(fn('***'), r'(?s:[^/]*)\Z') + self.assertEqual(fn('a**'), r'(?s:a[^/]*)\Z') + self.assertEqual(fn('**b'), r'(?s:[^/]*b)\Z') self.assertEqual(fn('/**/*/*.*/**'), r'(?s:/(?:.+/)?[^/]+/[^/]*\.[^/]*/.*)\Z') def test_translate_seps(self): diff --git a/Lib/test/test_gzip.py b/Lib/test/test_gzip.py index 128f933787a3f67..d220c7d06e50c97 100644 --- a/Lib/test/test_gzip.py +++ b/Lib/test/test_gzip.py @@ -5,7 +5,6 @@ import functools import io import os -import pathlib import struct import sys import unittest @@ -79,16 +78,18 @@ def test_write(self): f.close() def test_write_read_with_pathlike_file(self): - filename = pathlib.Path(self.filename) + filename = os_helper.FakePath(self.filename) with gzip.GzipFile(filename, 'w') as f: f.write(data1 * 50) self.assertIsInstance(f.name, str) + self.assertEqual(f.name, self.filename) with gzip.GzipFile(filename, 'a') as f: f.write(data1) with gzip.GzipFile(filename) as f: d = f.read() self.assertEqual(d, data1 * 51) self.assertIsInstance(f.name, str) + self.assertEqual(f.name, self.filename) # The following test_write_xy methods test that write accepts # the corresponding bytes-like object type as input @@ -472,13 +473,118 @@ def test_textio_readlines(self): with io.TextIOWrapper(f, encoding="ascii") as t: self.assertEqual(t.readlines(), lines) + def test_fileobj_with_name(self): + with open(self.filename, "xb") as raw: + with gzip.GzipFile(fileobj=raw, mode="x") as f: + f.write(b'one') + self.assertEqual(f.name, raw.name) + self.assertEqual(f.fileno(), raw.fileno()) + self.assertEqual(f.mode, gzip.WRITE) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertEqual(f.name, raw.name) + self.assertRaises(AttributeError, f.fileno) + self.assertEqual(f.mode, gzip.WRITE) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), True) + + with open(self.filename, "wb") as raw: + with gzip.GzipFile(fileobj=raw, mode="w") as f: + f.write(b'two') + self.assertEqual(f.name, raw.name) + self.assertEqual(f.fileno(), raw.fileno()) + self.assertEqual(f.mode, gzip.WRITE) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertEqual(f.name, raw.name) + self.assertRaises(AttributeError, f.fileno) + self.assertEqual(f.mode, gzip.WRITE) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), True) + + with open(self.filename, "ab") as raw: + with gzip.GzipFile(fileobj=raw, mode="a") as f: + f.write(b'three') + self.assertEqual(f.name, raw.name) + self.assertEqual(f.fileno(), raw.fileno()) + self.assertEqual(f.mode, gzip.WRITE) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertEqual(f.name, raw.name) + self.assertRaises(AttributeError, f.fileno) + self.assertEqual(f.mode, gzip.WRITE) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), True) + + with open(self.filename, "rb") as raw: + with gzip.GzipFile(fileobj=raw, mode="r") as f: + self.assertEqual(f.read(), b'twothree') + self.assertEqual(f.name, raw.name) + self.assertEqual(f.fileno(), raw.fileno()) + self.assertEqual(f.mode, gzip.READ) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertEqual(f.name, raw.name) + self.assertRaises(AttributeError, f.fileno) + self.assertEqual(f.mode, gzip.READ) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + def test_fileobj_from_fdopen(self): # Issue #13781: Opening a GzipFile for writing fails when using a # fileobj created with os.fdopen(). - fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT) - with os.fdopen(fd, "wb") as f: - with gzip.GzipFile(fileobj=f, mode="w") as g: - pass + fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT | os.O_EXCL) + with os.fdopen(fd, "xb") as raw: + with gzip.GzipFile(fileobj=raw, mode="x") as f: + f.write(b'one') + self.assertEqual(f.name, '') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertIs(f.closed, True) + self.assertEqual(f.name, '') + self.assertRaises(AttributeError, f.fileno) + + fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC) + with os.fdopen(fd, "wb") as raw: + with gzip.GzipFile(fileobj=raw, mode="w") as f: + f.write(b'two') + self.assertEqual(f.name, '') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertEqual(f.name, '') + self.assertRaises(AttributeError, f.fileno) + + fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT | os.O_APPEND) + with os.fdopen(fd, "ab") as raw: + with gzip.GzipFile(fileobj=raw, mode="a") as f: + f.write(b'three') + self.assertEqual(f.name, '') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertEqual(f.name, '') + self.assertRaises(AttributeError, f.fileno) + + fd = os.open(self.filename, os.O_RDONLY) + with os.fdopen(fd, "rb") as raw: + with gzip.GzipFile(fileobj=raw, mode="r") as f: + self.assertEqual(f.read(), b'twothree') + self.assertEqual(f.name, '') + self.assertEqual(f.fileno(), raw.fileno()) + self.assertEqual(f.name, '') + self.assertRaises(AttributeError, f.fileno) def test_fileobj_mode(self): gzip.GzipFile(self.filename, "wb").close() @@ -508,17 +614,69 @@ def test_fileobj_mode(self): def test_bytes_filename(self): str_filename = self.filename - try: - bytes_filename = str_filename.encode("ascii") - except UnicodeEncodeError: - self.skipTest("Temporary file name needs to be ASCII") + bytes_filename = os.fsencode(str_filename) with gzip.GzipFile(bytes_filename, "wb") as f: f.write(data1 * 50) + self.assertEqual(f.name, bytes_filename) with gzip.GzipFile(bytes_filename, "rb") as f: self.assertEqual(f.read(), data1 * 50) + self.assertEqual(f.name, bytes_filename) # Sanity check that we are actually operating on the right file. with gzip.GzipFile(str_filename, "rb") as f: self.assertEqual(f.read(), data1 * 50) + self.assertEqual(f.name, str_filename) + + def test_fileobj_without_name(self): + bio = io.BytesIO() + with gzip.GzipFile(fileobj=bio, mode='wb') as f: + f.write(data1 * 50) + self.assertEqual(f.name, '') + self.assertRaises(io.UnsupportedOperation, f.fileno) + self.assertEqual(f.mode, gzip.WRITE) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertEqual(f.name, '') + self.assertRaises(AttributeError, f.fileno) + self.assertEqual(f.mode, gzip.WRITE) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), True) + + bio.seek(0) + with gzip.GzipFile(fileobj=bio, mode='rb') as f: + self.assertEqual(f.read(), data1 * 50) + self.assertEqual(f.name, '') + self.assertRaises(io.UnsupportedOperation, f.fileno) + self.assertEqual(f.mode, gzip.READ) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertEqual(f.name, '') + self.assertRaises(AttributeError, f.fileno) + self.assertEqual(f.mode, gzip.READ) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + + def test_fileobj_and_filename(self): + filename2 = self.filename + 'new' + with (open(self.filename, 'wb') as fileobj, + gzip.GzipFile(fileobj=fileobj, filename=filename2, mode='wb') as f): + f.write(data1 * 50) + self.assertEqual(f.name, filename2) + with (open(self.filename, 'rb') as fileobj, + gzip.GzipFile(fileobj=fileobj, filename=filename2, mode='rb') as f): + self.assertEqual(f.read(), data1 * 50) + self.assertEqual(f.name, filename2) + # Sanity check that we are actually operating on the right file. + with gzip.GzipFile(self.filename, 'rb') as f: + self.assertEqual(f.read(), data1 * 50) + self.assertEqual(f.name, self.filename) def test_decompress_limited(self): """Decompressed data buffering should be limited""" @@ -707,13 +865,16 @@ def test_binary_modes(self): self.assertEqual(file_data, uncompressed) def test_pathlike_file(self): - filename = pathlib.Path(self.filename) + filename = os_helper.FakePath(self.filename) with gzip.open(filename, "wb") as f: f.write(data1 * 50) + self.assertEqual(f.name, self.filename) with gzip.open(filename, "ab") as f: f.write(data1) + self.assertEqual(f.name, self.filename) with gzip.open(filename) as f: self.assertEqual(f.read(), data1 * 51) + self.assertEqual(f.name, self.filename) def test_implicit_binary_modes(self): # Test implicit binary modes (no "b" or "t" in mode string). diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py index a39a2c45ebc2e28..1502fba9f3e8b8b 100644 --- a/Lib/test/test_hmac.py +++ b/Lib/test/test_hmac.py @@ -479,6 +479,14 @@ def test_exercise_all_methods(self): self.fail("Exception raised during normal usage of HMAC class.") +class UpdateTestCase(unittest.TestCase): + @hashlib_helper.requires_hashdigest('sha256') + def test_with_str_update(self): + with self.assertRaises(TypeError): + h = hmac.new(b"key", digestmod='sha256') + h.update("invalid update") + + class CopyTestCase(unittest.TestCase): @hashlib_helper.requires_hashdigest('sha256') diff --git a/Lib/test/test_importlib/extension/test_case_sensitivity.py b/Lib/test/test_importlib/extension/test_case_sensitivity.py index 0bb74fff5fcf968..40311627a144e82 100644 --- a/Lib/test/test_importlib/extension/test_case_sensitivity.py +++ b/Lib/test/test_importlib/extension/test_case_sensitivity.py @@ -8,7 +8,8 @@ machinery = util.import_importlib('importlib.machinery') -@unittest.skipIf(util.EXTENSIONS.filename is None, f'{util.EXTENSIONS.name} not available') +@unittest.skipIf(util.EXTENSIONS is None or util.EXTENSIONS.filename is None, + 'dynamic loading not supported or test module not available') @util.case_insensitive_tests class ExtensionModuleCaseSensitivityTest(util.CASEOKTestBase): diff --git a/Lib/test/test_importlib/extension/test_finder.py b/Lib/test/test_importlib/extension/test_finder.py index 1d5b6e7a5de94b7..3de120958fd27db 100644 --- a/Lib/test/test_importlib/extension/test_finder.py +++ b/Lib/test/test_importlib/extension/test_finder.py @@ -11,7 +11,7 @@ class FinderTests(abc.FinderTests): """Test the finder for extension modules.""" def setUp(self): - if not self.machinery.EXTENSION_SUFFIXES: + if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS: raise unittest.SkipTest("Requires dynamic loading support.") if util.EXTENSIONS.name in sys.builtin_module_names: raise unittest.SkipTest( diff --git a/Lib/test/test_importlib/extension/test_loader.py b/Lib/test/test_importlib/extension/test_loader.py index 84a0680e4ec6532..f4879e75847d8d8 100644 --- a/Lib/test/test_importlib/extension/test_loader.py +++ b/Lib/test/test_importlib/extension/test_loader.py @@ -17,7 +17,7 @@ class LoaderTests: """Test ExtensionFileLoader.""" def setUp(self): - if not self.machinery.EXTENSION_SUFFIXES: + if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS: raise unittest.SkipTest("Requires dynamic loading support.") if util.EXTENSIONS.name in sys.builtin_module_names: raise unittest.SkipTest( @@ -99,7 +99,7 @@ class SinglePhaseExtensionModuleTests(abc.LoaderTests): # Test loading extension modules without multi-phase initialization. def setUp(self): - if not self.machinery.EXTENSION_SUFFIXES: + if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS: raise unittest.SkipTest("Requires dynamic loading support.") self.name = '_testsinglephase' if self.name in sys.builtin_module_names: @@ -180,7 +180,7 @@ class MultiPhaseExtensionModuleTests(abc.LoaderTests): # Test loading extension modules with multi-phase initialization (PEP 489). def setUp(self): - if not self.machinery.EXTENSION_SUFFIXES: + if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS: raise unittest.SkipTest("Requires dynamic loading support.") self.name = '_testmultiphase' if self.name in sys.builtin_module_names: diff --git a/Lib/test/test_importlib/extension/test_path_hook.py b/Lib/test/test_importlib/extension/test_path_hook.py index ec9644dc520534c..314a635c77e0820 100644 --- a/Lib/test/test_importlib/extension/test_path_hook.py +++ b/Lib/test/test_importlib/extension/test_path_hook.py @@ -5,6 +5,8 @@ import unittest +@unittest.skipIf(util.EXTENSIONS is None or util.EXTENSIONS.filename is None, + 'dynamic loading not supported or test module not available') class PathHookTests: """Test the path hook for extension modules.""" diff --git a/Lib/test/test_importlib/resources/test_files.py b/Lib/test/test_importlib/resources/test_files.py index 1450cfb310926aa..26c8b04e44c3b9b 100644 --- a/Lib/test/test_importlib/resources/test_files.py +++ b/Lib/test/test_importlib/resources/test_files.py @@ -70,7 +70,7 @@ def setUp(self): self.addCleanup(self.fixtures.close) self.site_dir = self.fixtures.enter_context(os_helper.temp_dir()) self.fixtures.enter_context(import_helper.DirsOnSysPath(self.site_dir)) - self.fixtures.enter_context(import_helper.CleanImport()) + self.fixtures.enter_context(import_helper.isolated_modules()) class ModulesFilesTests(SiteDir, unittest.TestCase): diff --git a/Lib/test/test_importlib/test_lazy.py b/Lib/test/test_importlib/test_lazy.py index cc993f333e355af..38ab21907b58d9d 100644 --- a/Lib/test/test_importlib/test_lazy.py +++ b/Lib/test/test_importlib/test_lazy.py @@ -2,9 +2,12 @@ from importlib import abc from importlib import util import sys +import time +import threading import types import unittest +from test.support import threading_helper from test.test_importlib import util as test_util @@ -40,6 +43,7 @@ class TestingImporter(abc.MetaPathFinder, abc.Loader): module_name = 'lazy_loader_test' mutated_name = 'changed' loaded = None + load_count = 0 source_code = 'attr = 42; __name__ = {!r}'.format(mutated_name) def find_spec(self, name, path, target=None): @@ -48,8 +52,10 @@ def find_spec(self, name, path, target=None): return util.spec_from_loader(name, util.LazyLoader(self)) def exec_module(self, module): + time.sleep(0.01) # Simulate a slow load. exec(self.source_code, module.__dict__) self.loaded = module + self.load_count += 1 class LazyLoaderTests(unittest.TestCase): @@ -59,8 +65,9 @@ def test_init(self): # Classes that don't define exec_module() trigger TypeError. util.LazyLoader(object) - def new_module(self, source_code=None): - loader = TestingImporter() + def new_module(self, source_code=None, loader=None): + if loader is None: + loader = TestingImporter() if source_code is not None: loader.source_code = source_code spec = util.spec_from_loader(TestingImporter.module_name, @@ -140,6 +147,37 @@ def test_module_already_in_sys(self): # Force the load; just care that no exception is raised. module.__name__ + @threading_helper.requires_working_threading() + def test_module_load_race(self): + with test_util.uncache(TestingImporter.module_name): + loader = TestingImporter() + module = self.new_module(loader=loader) + self.assertEqual(loader.load_count, 0) + + class RaisingThread(threading.Thread): + exc = None + def run(self): + try: + super().run() + except Exception as exc: + self.exc = exc + + def access_module(): + return module.attr + + threads = [] + for _ in range(2): + threads.append(thread := RaisingThread(target=access_module)) + thread.start() + + # Races could cause errors + for thread in threads: + thread.join() + self.assertIsNone(thread.exc) + + # Or multiple load attempts + self.assertEqual(loader.load_count, 1) + if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_importlib/test_spec.py b/Lib/test/test_importlib/test_spec.py index 80aa3609c6f96eb..02318926f35eeee 100644 --- a/Lib/test/test_importlib/test_spec.py +++ b/Lib/test/test_importlib/test_spec.py @@ -502,7 +502,8 @@ def test_spec_from_loader_is_package_true_with_fileloader(self): self.assertEqual(spec.loader, self.fileloader) self.assertEqual(spec.origin, self.path) self.assertIs(spec.loader_state, None) - self.assertEqual(spec.submodule_search_locations, [os.getcwd()]) + location = cwd if (cwd := os.getcwd()) != '/' else '' + self.assertEqual(spec.submodule_search_locations, [location]) self.assertEqual(spec.cached, self.cached) self.assertTrue(spec.has_location) @@ -601,7 +602,8 @@ def test_spec_from_file_location_smsl_empty(self): self.assertEqual(spec.loader, self.fileloader) self.assertEqual(spec.origin, self.path) self.assertIs(spec.loader_state, None) - self.assertEqual(spec.submodule_search_locations, [os.getcwd()]) + location = cwd if (cwd := os.getcwd()) != '/' else '' + self.assertEqual(spec.submodule_search_locations, [location]) self.assertEqual(spec.cached, self.cached) self.assertTrue(spec.has_location) @@ -626,7 +628,8 @@ def test_spec_from_file_location_smsl_default(self): self.assertEqual(spec.loader, self.pkgloader) self.assertEqual(spec.origin, self.path) self.assertIs(spec.loader_state, None) - self.assertEqual(spec.submodule_search_locations, [os.getcwd()]) + location = cwd if (cwd := os.getcwd()) != '/' else '' + self.assertEqual(spec.submodule_search_locations, [location]) self.assertEqual(spec.cached, self.cached) self.assertTrue(spec.has_location) diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py index fe5e7b31d9c32b7..a09286806e51526 100644 --- a/Lib/test/test_importlib/test_util.py +++ b/Lib/test/test_importlib/test_util.py @@ -577,7 +577,7 @@ def test_cache_from_source_respects_pycache_prefix_relative(self): with util.temporary_pycache_prefix(pycache_prefix): self.assertEqual( self.util.cache_from_source(path, optimization=''), - expect) + os.path.normpath(expect)) @unittest.skipIf(sys.implementation.cache_tag is None, 'requires sys.implementation.cache_tag to not be None') diff --git a/Lib/test/test_importlib/util.py b/Lib/test/test_importlib/util.py index c25be096e528748..a900cc1dddf425b 100644 --- a/Lib/test/test_importlib/util.py +++ b/Lib/test/test_importlib/util.py @@ -6,6 +6,7 @@ import marshal import os import os.path +from test import support from test.support import import_helper from test.support import os_helper import unittest @@ -22,25 +23,34 @@ if 'importlib' not in sys.builtin_module_names: BUILTINS.bad_name = 'importlib' -EXTENSIONS = types.SimpleNamespace() -EXTENSIONS.path = None -EXTENSIONS.ext = None -EXTENSIONS.filename = None -EXTENSIONS.file_path = None -EXTENSIONS.name = '_testsinglephase' - -def _extension_details(): - global EXTENSIONS - for path in sys.path: - for ext in machinery.EXTENSION_SUFFIXES: - filename = EXTENSIONS.name + ext - file_path = os.path.join(path, filename) - if os.path.exists(file_path): - EXTENSIONS.path = path - EXTENSIONS.ext = ext - EXTENSIONS.filename = filename - EXTENSIONS.file_path = file_path - return +if support.is_wasi: + # dlopen() is a shim for WASI as of WASI SDK which fails by default. + # We don't provide an implementation, so tests will fail. + # But we also don't want to turn off dynamic loading for those that provide + # a working implementation. + def _extension_details(): + global EXTENSIONS + EXTENSIONS = None +else: + EXTENSIONS = types.SimpleNamespace() + EXTENSIONS.path = None + EXTENSIONS.ext = None + EXTENSIONS.filename = None + EXTENSIONS.file_path = None + EXTENSIONS.name = '_testsinglephase' + + def _extension_details(): + global EXTENSIONS + for path in sys.path: + for ext in machinery.EXTENSION_SUFFIXES: + filename = EXTENSIONS.name + ext + file_path = os.path.join(path, filename) + if os.path.exists(file_path): + EXTENSIONS.path = path + EXTENSIONS.ext = ext + EXTENSIONS.filename = filename + EXTENSIONS.file_path = file_path + return _extension_details() diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index c5a6de5993fad49..c3a9dc998e38d04 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -40,11 +40,11 @@ from test.support import has_subprocess_support, SuppressCrashReport from test import support -from . import inspect_fodder as mod -from . import inspect_fodder2 as mod2 -from . import inspect_stock_annotations -from . import inspect_stringized_annotations -from . import inspect_stringized_annotations_2 +from test.test_inspect import inspect_fodder as mod +from test.test_inspect import inspect_fodder2 as mod2 +from test.test_inspect import inspect_stock_annotations +from test.test_inspect import inspect_stringized_annotations +from test.test_inspect import inspect_stringized_annotations_2 # Functions tested in this suite: @@ -991,7 +991,11 @@ def test_class_decorator(self): self.assertSourceEqual(mod2.cls196, 194, 201) self.assertSourceEqual(mod2.cls196.cls200, 198, 201) + @support.requires_docstrings def test_class_inside_conditional(self): + # We skip this test when docstrings are not present, + # because docstrings are one of the main factors of + # finding the correct class in the source code. self.assertSourceEqual(mod2.cls238.cls239, 239, 240) def test_multiple_children_classes(self): @@ -2928,9 +2932,12 @@ def p(name): return signature.parameters[name].default # This doesn't work now. # (We don't have a valid signature for "type" in 3.4) + class ThisWorksNow: + __call__ = type + # TODO: Support type. + self.assertEqual(ThisWorksNow()(1), int) + self.assertEqual(ThisWorksNow()('A', (), {}).__name__, 'A') with self.assertRaisesRegex(ValueError, "no signature found"): - class ThisWorksNow: - __call__ = type test_callable(ThisWorksNow()) # Regression test for issue #20786 @@ -3137,6 +3144,10 @@ def m1d(*args, **kwargs): int)) def test_signature_on_classmethod(self): + self.assertEqual(self.signature(classmethod), + ((('function', ..., ..., "positional_only"),), + ...)) + class Test: @classmethod def foo(cls, arg1, *, arg2=1): @@ -3155,6 +3166,10 @@ def foo(cls, arg1, *, arg2=1): ...)) def test_signature_on_staticmethod(self): + self.assertEqual(self.signature(staticmethod), + ((('function', ..., ..., "positional_only"),), + ...)) + class Test: @staticmethod def foo(cls, *, arg): @@ -3513,6 +3528,98 @@ def __init__(self, b): ((('a', ..., ..., "positional_or_keyword"),), ...)) + with self.subTest('classmethod'): + class CM(type): + @classmethod + def __call__(cls, a): + return a + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('staticmethod'): + class CM(type): + @staticmethod + def __call__(a): + return a + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('MethodType'): + class A: + def call(self, a): + return a + class CM(type): + __call__ = A().call + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partial'): + class CM(type): + __call__ = functools.partial(lambda x, a: (x, a), 2) + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(1), (2, 1)) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partialmethod'): + class CM(type): + __call__ = functools.partialmethod(lambda self, x, a: (x, a), 2) + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(1), (2, 1)) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('BuiltinMethodType'): + class CM(type): + __call__ = ':'.join + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(['a', 'bc']), 'a:bc') + # BUG: Returns '' + with self.assertRaises(AssertionError): + self.assertEqual(self.signature(C), self.signature(''.join)) + + with self.subTest('MethodWrapperType'): + class CM(type): + __call__ = (2).__pow__ + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(C(3), 8) + self.assertEqual(C(3, 7), 1) + # BUG: Returns '' + with self.assertRaises(AssertionError): + self.assertEqual(self.signature(C), self.signature((0).__pow__)) + class CM(type): def __new__(mcls, name, bases, dct, *, foo=1): return super().__new__(mcls, name, bases, dct) @@ -3574,6 +3681,169 @@ def __init__(self, b): ('bar', 2, ..., "keyword_only")), ...)) + def test_signature_on_class_with_init(self): + class C: + def __init__(self, b): + pass + + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('classmethod'): + class C: + @classmethod + def __init__(cls, b): + pass + + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('staticmethod'): + class C: + @staticmethod + def __init__(b): + pass + + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('MethodType'): + class A: + def call(self, a): + pass + class C: + __init__ = A().call + + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partial'): + class C: + __init__ = functools.partial(lambda x, a: None, 2) + + C(1) # does not raise + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partialmethod'): + class C: + def _init(self, x, a): + self.a = (x, a) + __init__ = functools.partialmethod(_init, 2) + + self.assertEqual(C(1).a, (2, 1)) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + def test_signature_on_class_with_new(self): + with self.subTest('FunctionType'): + class C: + def __new__(cls, a): + return a + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('classmethod'): + class C: + @classmethod + def __new__(cls, cls2, a): + return a + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('staticmethod'): + class C: + @staticmethod + def __new__(cls, a): + return a + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('MethodType'): + class A: + def call(self, cls, a): + return a + class C: + __new__ = A().call + + self.assertEqual(C(1), 1) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partial'): + class C: + __new__ = functools.partial(lambda x, cls, a: (x, a), 2) + + self.assertEqual(C(1), (2, 1)) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partialmethod'): + class C: + __new__ = functools.partialmethod(lambda cls, x, a: (x, a), 2) + + self.assertEqual(C(1), (2, 1)) + self.assertEqual(self.signature(C), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('BuiltinMethodType'): + class C: + __new__ = str.__subclasscheck__ + + self.assertEqual(C(), False) + # TODO: Support BuiltinMethodType + # self.assertEqual(self.signature(C), ((), ...)) + self.assertRaises(ValueError, self.signature, C) + + with self.subTest('MethodWrapperType'): + class C: + __new__ = type.__or__.__get__(int, type) + + self.assertEqual(C(), C | int) + # TODO: Support MethodWrapperType + # self.assertEqual(self.signature(C), ((), ...)) + self.assertRaises(ValueError, self.signature, C) + + # TODO: Test ClassMethodDescriptorType + + with self.subTest('MethodDescriptorType'): + class C: + __new__ = type.__dict__['__subclasscheck__'] + + self.assertEqual(C(C), True) + self.assertEqual(self.signature(C), self.signature(C.__subclasscheck__)) + + with self.subTest('WrapperDescriptorType'): + class C: + __new__ = type.__or__ + + self.assertEqual(C(int), C | int) + # TODO: Support WrapperDescriptorType + # self.assertEqual(self.signature(C), self.signature(C.__or__)) + self.assertRaises(ValueError, self.signature, C) + def test_signature_on_subclass(self): class A: def __new__(cls, a=1, *args, **kwargs): @@ -3627,8 +3897,11 @@ class D(C): pass # Test meta-classes without user-defined __init__ or __new__ class C(type): pass class D(C): pass + self.assertEqual(C('A', (), {}).__name__, 'A') + # TODO: Support type. with self.assertRaisesRegex(ValueError, "callable.*is not supported"): self.assertEqual(inspect.signature(C), None) + self.assertEqual(D('A', (), {}).__name__, 'A') with self.assertRaisesRegex(ValueError, "callable.*is not supported"): self.assertEqual(inspect.signature(D), None) @@ -3678,16 +3951,117 @@ class Bar(Spam, Foo): ((('a', ..., ..., "positional_or_keyword"),), ...)) - class Wrapped: - pass - Wrapped.__wrapped__ = lambda a: None - self.assertEqual(self.signature(Wrapped), + with self.subTest('classmethod'): + class C: + @classmethod + def __call__(cls, a): + pass + + self.assertEqual(self.signature(C()), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('staticmethod'): + class C: + @staticmethod + def __call__(a): + pass + + self.assertEqual(self.signature(C()), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('MethodType'): + class A: + def call(self, a): + return a + class C: + __call__ = A().call + + self.assertEqual(C()(1), 1) + self.assertEqual(self.signature(C()), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partial'): + class C: + __call__ = functools.partial(lambda x, a: (x, a), 2) + + self.assertEqual(C()(1), (2, 1)) + self.assertEqual(self.signature(C()), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('partialmethod'): + class C: + __call__ = functools.partialmethod(lambda self, x, a: (x, a), 2) + + self.assertEqual(C()(1), (2, 1)) + self.assertEqual(self.signature(C()), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + with self.subTest('BuiltinMethodType'): + class C: + __call__ = ':'.join + + self.assertEqual(C()(['a', 'bc']), 'a:bc') + self.assertEqual(self.signature(C()), self.signature(''.join)) + + with self.subTest('MethodWrapperType'): + class C: + __call__ = (2).__pow__ + + self.assertEqual(C()(3), 8) + self.assertEqual(self.signature(C()), self.signature((0).__pow__)) + + with self.subTest('ClassMethodDescriptorType'): + class C(dict): + __call__ = dict.__dict__['fromkeys'] + + res = C()([1, 2], 3) + self.assertEqual(res, {1: 3, 2: 3}) + self.assertEqual(type(res), C) + self.assertEqual(self.signature(C()), self.signature(dict.fromkeys)) + + with self.subTest('MethodDescriptorType'): + class C(str): + __call__ = str.join + + self.assertEqual(C(':')(['a', 'bc']), 'a:bc') + self.assertEqual(self.signature(C()), self.signature(''.join)) + + with self.subTest('WrapperDescriptorType'): + class C(int): + __call__ = int.__pow__ + + self.assertEqual(C(2)(3), 8) + self.assertEqual(self.signature(C()), self.signature((0).__pow__)) + + with self.subTest('MemberDescriptorType'): + class C: + __slots__ = '__call__' + c = C() + c.__call__ = lambda a: a + self.assertEqual(c(1), 1) + self.assertEqual(self.signature(c), + ((('a', ..., ..., "positional_or_keyword"),), + ...)) + + def test_signature_on_wrapper(self): + class Wrapper: + def __call__(self, b): + pass + wrapper = Wrapper() + wrapper.__wrapped__ = lambda a: None + self.assertEqual(self.signature(wrapper), ((('a', ..., ..., "positional_or_keyword"),), ...)) # wrapper loop: - Wrapped.__wrapped__ = Wrapped + wrapper = Wrapper() + wrapper.__wrapped__ = wrapper with self.assertRaisesRegex(ValueError, 'wrapper loop'): - self.signature(Wrapped) + self.signature(wrapper) def test_signature_on_lambdas(self): self.assertEqual(self.signature((lambda a=10: a)), @@ -4914,6 +5288,7 @@ def func(*args, **kwargs): with self.assertRaises(ValueError): inspect.signature(func) + @support.requires_docstrings def test_base_class_have_text_signature(self): # see issue 43118 from test.typinganndata.ann_module7 import BufferedReader @@ -4999,6 +5374,14 @@ def test_recursion_limit(self): with self.assertRaisesRegex(ValueError, 'wrapper loop'): inspect.unwrap(obj) + def test_wrapped_descriptor(self): + self.assertIs(inspect.unwrap(NTimesUnwrappable), NTimesUnwrappable) + self.assertIs(inspect.unwrap(staticmethod), staticmethod) + self.assertIs(inspect.unwrap(classmethod), classmethod) + self.assertIs(inspect.unwrap(staticmethod(classmethod)), classmethod) + self.assertIs(inspect.unwrap(classmethod(staticmethod)), staticmethod) + + class TestMain(unittest.TestCase): def test_only_source(self): module = importlib.import_module('unittest') diff --git a/Lib/test/test_int.py b/Lib/test/test_int.py index 0bf55facad9fedb..47fc50a0e203497 100644 --- a/Lib/test/test_int.py +++ b/Lib/test/test_int.py @@ -664,84 +664,78 @@ def test_denial_of_service_prevented_int_to_str(self): """Regression test: ensure we fail before performing O(N**2) work.""" maxdigits = sys.get_int_max_str_digits() assert maxdigits < 50_000, maxdigits # A test prerequisite. - get_time = time.process_time - if get_time() <= 0: # some platforms like WASM lack process_time() - get_time = time.monotonic huge_int = int(f'0x{"c"*65_000}', base=16) # 78268 decimal digits. digits = 78_268 - with support.adjust_int_max_str_digits(digits): - start = get_time() + with ( + support.adjust_int_max_str_digits(digits), + support.CPUStopwatch() as sw_convert): huge_decimal = str(huge_int) - seconds_to_convert = get_time() - start self.assertEqual(len(huge_decimal), digits) # Ensuring that we chose a slow enough conversion to measure. # It takes 0.1 seconds on a Zen based cloud VM in an opt build. # Some OSes have a low res 1/64s timer, skip if hard to measure. - if seconds_to_convert < 1/64: + if sw_convert.seconds < sw_convert.clock_info.resolution * 2: raise unittest.SkipTest('"slow" conversion took only ' - f'{seconds_to_convert} seconds.') + f'{sw_convert.seconds} seconds.') # We test with the limit almost at the size needed to check performance. # The performant limit check is slightly fuzzy, give it a some room. with support.adjust_int_max_str_digits(int(.995 * digits)): - with self.assertRaises(ValueError) as err: - start = get_time() + with ( + self.assertRaises(ValueError) as err, + support.CPUStopwatch() as sw_fail_huge): str(huge_int) - seconds_to_fail_huge = get_time() - start self.assertIn('conversion', str(err.exception)) - self.assertLessEqual(seconds_to_fail_huge, seconds_to_convert/2) + self.assertLessEqual(sw_fail_huge.seconds, sw_convert.seconds/2) # Now we test that a conversion that would take 30x as long also fails # in a similarly fast fashion. extra_huge_int = int(f'0x{"c"*500_000}', base=16) # 602060 digits. - with self.assertRaises(ValueError) as err: - start = get_time() + with ( + self.assertRaises(ValueError) as err, + support.CPUStopwatch() as sw_fail_extra_huge): # If not limited, 8 seconds said Zen based cloud VM. str(extra_huge_int) - seconds_to_fail_extra_huge = get_time() - start self.assertIn('conversion', str(err.exception)) - self.assertLess(seconds_to_fail_extra_huge, seconds_to_convert/2) + self.assertLess(sw_fail_extra_huge.seconds, sw_convert.seconds/2) def test_denial_of_service_prevented_str_to_int(self): """Regression test: ensure we fail before performing O(N**2) work.""" maxdigits = sys.get_int_max_str_digits() assert maxdigits < 100_000, maxdigits # A test prerequisite. - get_time = time.process_time - if get_time() <= 0: # some platforms like WASM lack process_time() - get_time = time.monotonic digits = 133700 huge = '8'*digits - with support.adjust_int_max_str_digits(digits): - start = get_time() + with ( + support.adjust_int_max_str_digits(digits), + support.CPUStopwatch() as sw_convert): int(huge) - seconds_to_convert = get_time() - start # Ensuring that we chose a slow enough conversion to measure. # It takes 0.1 seconds on a Zen based cloud VM in an opt build. # Some OSes have a low res 1/64s timer, skip if hard to measure. - if seconds_to_convert < 1/64: + if sw_convert.seconds < sw_convert.clock_info.resolution * 2: raise unittest.SkipTest('"slow" conversion took only ' - f'{seconds_to_convert} seconds.') + f'{sw_convert.seconds} seconds.') with support.adjust_int_max_str_digits(digits - 1): - with self.assertRaises(ValueError) as err: - start = get_time() + with ( + self.assertRaises(ValueError) as err, + support.CPUStopwatch() as sw_fail_huge): int(huge) - seconds_to_fail_huge = get_time() - start self.assertIn('conversion', str(err.exception)) - self.assertLessEqual(seconds_to_fail_huge, seconds_to_convert/2) + self.assertLessEqual(sw_fail_huge.seconds, sw_convert.seconds/2) # Now we test that a conversion that would take 30x as long also fails # in a similarly fast fashion. extra_huge = '7'*1_200_000 - with self.assertRaises(ValueError) as err: - start = get_time() + with ( + self.assertRaises(ValueError) as err, + support.CPUStopwatch() as sw_fail_extra_huge): # If not limited, 8 seconds in the Zen based cloud VM. int(extra_huge) - seconds_to_fail_extra_huge = get_time() - start self.assertIn('conversion', str(err.exception)) - self.assertLessEqual(seconds_to_fail_extra_huge, seconds_to_convert/2) + self.assertLessEqual(sw_fail_extra_huge.seconds, sw_convert.seconds/2) def test_power_of_two_bases_unlimited(self): """The limit does not apply to power of 2 bases.""" diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index aefd326977095f9..3cde9bd0014d9a9 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -1,4 +1,5 @@ import os +import pickle import threading from textwrap import dedent import unittest @@ -261,6 +262,12 @@ def test_equality(self): self.assertEqual(interp1, interp1) self.assertNotEqual(interp1, interp2) + def test_pickle(self): + interp = interpreters.create() + data = pickle.dumps(interp) + unpickled = pickle.loads(data) + self.assertEqual(unpickled, interp) + class TestInterpreterIsRunning(TestBase): @@ -280,7 +287,7 @@ def test_subinterpreter(self): def test_finished(self): r, w = self.pipe() interp = interpreters.create() - interp.exec_sync(f"""if True: + interp.exec(f"""if True: import os os.write({w}, b'x') """) @@ -312,7 +319,7 @@ def test_with_only_background_threads(self): FINISHED = b'F' interp = interpreters.create() - interp.exec_sync(f"""if True: + interp.exec(f"""if True: import os import threading @@ -326,7 +333,7 @@ def task(): self.assertFalse(interp.is_running()) os.write(w_thread, DONE) - interp.exec_sync('t.join()') + interp.exec('t.join()') self.assertEqual(os.read(r_interp, 1), FINISHED) @@ -393,7 +400,7 @@ def test_from_sibling(self): interp2 = interpreters.create() self.assertEqual(set(interpreters.list_all()), {main, interp1, interp2}) - interp1.exec_sync(dedent(f""" + interp1.exec(dedent(f""" from test.support import interpreters interp2 = interpreters.Interpreter({interp2.id}) interp2.close() @@ -427,7 +434,7 @@ def test_subthreads_still_running(self): FINISHED = b'F' interp = interpreters.create() - interp.exec_sync(f"""if True: + interp.exec(f"""if True: import os import threading import time @@ -503,27 +510,27 @@ def test_not_shareable(self): interp.prepare_main(spam={'spam': 'eggs', 'foo': 'bar'}) # Make sure neither was actually bound. - with self.assertRaises(interpreters.ExecFailure): - interp.exec_sync('print(foo)') - with self.assertRaises(interpreters.ExecFailure): - interp.exec_sync('print(spam)') + with self.assertRaises(interpreters.ExecutionFailed): + interp.exec('print(foo)') + with self.assertRaises(interpreters.ExecutionFailed): + interp.exec('print(spam)') -class TestInterpreterExecSync(TestBase): +class TestInterpreterExec(TestBase): def test_success(self): interp = interpreters.create() script, file = _captured_script('print("it worked!", end="")') with file: - interp.exec_sync(script) + interp.exec(script) out = file.read() self.assertEqual(out, 'it worked!') def test_failure(self): interp = interpreters.create() - with self.assertRaises(interpreters.ExecFailure): - interp.exec_sync('raise Exception') + with self.assertRaises(interpreters.ExecutionFailed): + interp.exec('raise Exception') def test_display_preserved_exception(self): tempdir = self.temp_dir() @@ -542,21 +549,21 @@ def script(): spam.eggs() interp = interpreters.create() - interp.exec_sync(script) + interp.exec(script) """) stdout, stderr = self.assert_python_failure(scriptfile) self.maxDiff = None - interpmod_line, = (l for l in stderr.splitlines() if ' exec_sync' in l) - # File "{interpreters.__file__}", line 179, in exec_sync + interpmod_line, = (l for l in stderr.splitlines() if ' exec' in l) + # File "{interpreters.__file__}", line 179, in exec self.assertEqual(stderr, dedent(f"""\ Traceback (most recent call last): File "{scriptfile}", line 9, in - interp.exec_sync(script) - ~~~~~~~~~~~~~~~~^^^^^^^^ + interp.exec(script) + ~~~~~~~~~~~^^^^^^^^ {interpmod_line.strip()} - raise ExecFailure(excinfo) - test.support.interpreters.ExecFailure: RuntimeError: uh-oh! + raise ExecutionFailed(excinfo) + test.support.interpreters.ExecutionFailed: RuntimeError: uh-oh! Uncaught in the interpreter: @@ -578,7 +585,7 @@ def test_in_thread(self): script, file = _captured_script('print("it worked!", end="")') with file: def f(): - interp.exec_sync(script) + interp.exec(script) t = threading.Thread(target=f) t.start() @@ -604,7 +611,7 @@ def test_fork(self): with open('{file.name}', 'w', encoding='utf-8') as out: out.write('{expected}') """) - interp.exec_sync(script) + interp.exec(script) file.seek(0) content = file.read() @@ -615,17 +622,17 @@ def test_already_running(self): interp = interpreters.create() with _running(interp): with self.assertRaises(RuntimeError): - interp.exec_sync('print("spam")') + interp.exec('print("spam")') def test_bad_script(self): interp = interpreters.create() with self.assertRaises(TypeError): - interp.exec_sync(10) + interp.exec(10) def test_bytes_for_script(self): interp = interpreters.create() with self.assertRaises(TypeError): - interp.exec_sync(b'print("spam")') + interp.exec(b'print("spam")') def test_with_background_threads_still_running(self): r_interp, w_interp = self.pipe() @@ -636,7 +643,7 @@ def test_with_background_threads_still_running(self): FINISHED = b'F' interp = interpreters.create() - interp.exec_sync(f"""if True: + interp.exec(f"""if True: import os import threading @@ -648,46 +655,229 @@ def task(): t.start() os.write({w_interp}, {RAN!r}) """) - interp.exec_sync(f"""if True: + interp.exec(f"""if True: os.write({w_interp}, {RAN!r}) """) os.write(w_thread, DONE) - interp.exec_sync('t.join()') + interp.exec('t.join()') self.assertEqual(os.read(r_interp, 1), RAN) self.assertEqual(os.read(r_interp, 1), RAN) self.assertEqual(os.read(r_interp, 1), FINISHED) # test_xxsubinterpreters covers the remaining - # Interpreter.exec_sync() behavior. + # Interpreter.exec() behavior. -class TestInterpreterRun(TestBase): - - def test_success(self): - interp = interpreters.create() - script, file = _captured_script('print("it worked!", end="")') - with file: - t = interp.run(script) +def call_func_noop(): + pass + + +def call_func_return_shareable(): + return (1, None) + + +def call_func_return_not_shareable(): + return [1, 2, 3] + + +def call_func_failure(): + raise Exception('spam!') + + +def call_func_ident(value): + return value + + +def get_call_func_closure(value): + def call_func_closure(): + return value + return call_func_closure + + +class Spam: + + @staticmethod + def noop(): + pass + + @classmethod + def from_values(cls, *values): + return cls(values) + + def __init__(self, value): + self.value = value + + def __call__(self, *args, **kwargs): + return (self.value, args, kwargs) + + def __eq__(self, other): + if not isinstance(other, Spam): + return NotImplemented + return self.value == other.value + + def run(self, *args, **kwargs): + return (self.value, args, kwargs) + + +def call_func_complex(op, /, value=None, *args, exc=None, **kwargs): + if exc is not None: + raise exc + if op == '': + raise ValueError('missing op') + elif op == 'ident': + if args or kwargs: + raise Exception((args, kwargs)) + return value + elif op == 'full-ident': + return (value, args, kwargs) + elif op == 'globals': + if value is not None or args or kwargs: + raise Exception((value, args, kwargs)) + return __name__ + elif op == 'interpid': + if value is not None or args or kwargs: + raise Exception((value, args, kwargs)) + return interpreters.get_current().id + elif op == 'closure': + if args or kwargs: + raise Exception((args, kwargs)) + return get_call_func_closure(value) + elif op == 'custom': + if args or kwargs: + raise Exception((args, kwargs)) + return Spam(value) + elif op == 'custom-inner': + if args or kwargs: + raise Exception((args, kwargs)) + class Eggs(Spam): + pass + return Eggs(value) + elif not isinstance(op, str): + raise TypeError(op) + else: + raise NotImplementedError(op) + + +class TestInterpreterCall(TestBase): + + # signature + # - blank + # - args + # - kwargs + # - args, kwargs + # return + # - nothing (None) + # - simple + # - closure + # - custom + # ops: + # - do nothing + # - fail + # - echo + # - do complex, relative to interpreter + # scope + # - global func + # - local closure + # - returned closure + # - callable type instance + # - type + # - classmethod + # - staticmethod + # - instance method + # exception + # - builtin + # - custom + # - preserves info (e.g. SyntaxError) + # - matching error display + + def test_call(self): + interp = interpreters.create() + + for i, (callable, args, kwargs) in enumerate([ + (call_func_noop, (), {}), + (call_func_return_shareable, (), {}), + (call_func_return_not_shareable, (), {}), + (Spam.noop, (), {}), + ]): + with self.subTest(f'success case #{i+1}'): + res = interp.call(callable) + self.assertIs(res, None) + + for i, (callable, args, kwargs) in enumerate([ + (call_func_ident, ('spamspamspam',), {}), + (get_call_func_closure, (42,), {}), + (get_call_func_closure(42), (), {}), + (Spam.from_values, (), {}), + (Spam.from_values, (1, 2, 3), {}), + (Spam, ('???'), {}), + (Spam(101), (), {}), + (Spam(10101).run, (), {}), + (call_func_complex, ('ident', 'spam'), {}), + (call_func_complex, ('full-ident', 'spam'), {}), + (call_func_complex, ('full-ident', 'spam', 'ham'), {'eggs': '!!!'}), + (call_func_complex, ('globals',), {}), + (call_func_complex, ('interpid',), {}), + (call_func_complex, ('closure',), {'value': '~~~'}), + (call_func_complex, ('custom', 'spam!'), {}), + (call_func_complex, ('custom-inner', 'eggs!'), {}), + (call_func_complex, ('???',), {'exc': ValueError('spam')}), + ]): + with self.subTest(f'invalid case #{i+1}'): + with self.assertRaises(Exception): + if args or kwargs: + raise Exception((args, kwargs)) + interp.call(callable) + + with self.assertRaises(interpreters.ExecutionFailed): + interp.call(call_func_failure) + + def test_call_in_thread(self): + interp = interpreters.create() + + for i, (callable, args, kwargs) in enumerate([ + (call_func_noop, (), {}), + (call_func_return_shareable, (), {}), + (call_func_return_not_shareable, (), {}), + (Spam.noop, (), {}), + ]): + with self.subTest(f'success case #{i+1}'): + with self.captured_thread_exception() as ctx: + t = interp.call_in_thread(callable) + t.join() + self.assertIsNone(ctx.caught) + + for i, (callable, args, kwargs) in enumerate([ + (call_func_ident, ('spamspamspam',), {}), + (get_call_func_closure, (42,), {}), + (get_call_func_closure(42), (), {}), + (Spam.from_values, (), {}), + (Spam.from_values, (1, 2, 3), {}), + (Spam, ('???'), {}), + (Spam(101), (), {}), + (Spam(10101).run, (), {}), + (call_func_complex, ('ident', 'spam'), {}), + (call_func_complex, ('full-ident', 'spam'), {}), + (call_func_complex, ('full-ident', 'spam', 'ham'), {'eggs': '!!!'}), + (call_func_complex, ('globals',), {}), + (call_func_complex, ('interpid',), {}), + (call_func_complex, ('closure',), {'value': '~~~'}), + (call_func_complex, ('custom', 'spam!'), {}), + (call_func_complex, ('custom-inner', 'eggs!'), {}), + (call_func_complex, ('???',), {'exc': ValueError('spam')}), + ]): + with self.subTest(f'invalid case #{i+1}'): + if args or kwargs: + continue + with self.captured_thread_exception() as ctx: + t = interp.call_in_thread(callable) + t.join() + self.assertIsNotNone(ctx.caught) + + with self.captured_thread_exception() as ctx: + t = interp.call_in_thread(call_func_failure) t.join() - out = file.read() - - self.assertEqual(out, 'it worked!') - - def test_failure(self): - caught = False - def excepthook(args): - nonlocal caught - caught = True - threading.excepthook = excepthook - try: - interp = interpreters.create() - t = interp.run('raise Exception') - t.join() - - self.assertTrue(caught) - except BaseException: - threading.excepthook = threading.__excepthook__ + self.assertIsNotNone(ctx.caught) class TestIsShareable(TestBase): diff --git a/Lib/test/test_interpreters/test_channels.py b/Lib/test/test_interpreters/test_channels.py index 3c3e18832d41683..7e0b82884c33d39 100644 --- a/Lib/test/test_interpreters/test_channels.py +++ b/Lib/test/test_interpreters/test_channels.py @@ -1,3 +1,5 @@ +import importlib +import pickle import threading from textwrap import dedent import unittest @@ -11,6 +13,24 @@ from .utils import _run_output, TestBase +class LowLevelTests(TestBase): + + # The behaviors in the low-level module is important in as much + # as it is exercised by the high-level module. Therefore the + # most # important testing happens in the high-level tests. + # These low-level tests cover corner cases that are not + # encountered by the high-level module, thus they + # mostly shouldn't matter as much. + + # Additional tests are found in Lib/test/test__xxinterpchannels.py. + # XXX Those should be either moved to LowLevelTests or eliminated + # in favor of high-level tests in this file. + + def test_highlevel_reloaded(self): + # See gh-115490 (https://github.com/python/cpython/issues/115490). + importlib.reload(channels) + + class TestChannels(TestBase): def test_create(self): @@ -81,6 +101,12 @@ def test_equality(self): self.assertEqual(ch1, ch1) self.assertNotEqual(ch1, ch2) + def test_pickle(self): + ch, _ = channels.create() + data = pickle.dumps(ch) + unpickled = pickle.loads(data) + self.assertEqual(unpickled, ch) + class TestSendChannelAttrs(TestBase): @@ -106,6 +132,12 @@ def test_equality(self): self.assertEqual(ch1, ch1) self.assertNotEqual(ch1, ch2) + def test_pickle(self): + _, ch = channels.create() + data = pickle.dumps(ch) + unpickled = pickle.loads(data) + self.assertEqual(unpickled, ch) + class TestSendRecv(TestBase): @@ -120,7 +152,7 @@ def test_send_recv_main(self): def test_send_recv_same_interpreter(self): interp = interpreters.create() - interp.exec_sync(dedent(""" + interp.exec(dedent(""" from test.support.interpreters import channels r, s = channels.create() orig = b'spam' @@ -193,7 +225,7 @@ def test_send_recv_nowait_main_with_default(self): def test_send_recv_nowait_same_interpreter(self): interp = interpreters.create() - interp.exec_sync(dedent(""" + interp.exec(dedent(""" from test.support.interpreters import channels r, s = channels.create() orig = b'spam' diff --git a/Lib/test/test_interpreters/test_lifecycle.py b/Lib/test/test_interpreters/test_lifecycle.py index c2917d839904f91..becf003e2e5f207 100644 --- a/Lib/test/test_interpreters/test_lifecycle.py +++ b/Lib/test/test_interpreters/test_lifecycle.py @@ -124,7 +124,7 @@ def test_sys_path_0(self): orig = sys.path[0] interp = interpreters.create() - interp.exec_sync(f"""if True: + interp.exec(f"""if True: import json import sys print(json.dumps({{ @@ -164,6 +164,7 @@ def test_sys_path_0(self): class FinalizationTests(TestBase): + @support.requires_subprocess() def test_gh_109793(self): # Make sure finalization finishes and the correct error code # is reported, even when subinterpreters get cleaned up at the end. diff --git a/Lib/test/test_interpreters/test_queues.py b/Lib/test/test_interpreters/test_queues.py index 2a8ca99c1f6e3f3..d16d294b82d0449 100644 --- a/Lib/test/test_interpreters/test_queues.py +++ b/Lib/test/test_interpreters/test_queues.py @@ -1,25 +1,97 @@ +import importlib +import pickle import threading from textwrap import dedent import unittest import time -from test.support import import_helper +from test.support import import_helper, Py_DEBUG # Raise SkipTest if subinterpreters not supported. _queues = import_helper.import_module('_xxinterpqueues') from test.support import interpreters from test.support.interpreters import queues -from .utils import _run_output, TestBase +from .utils import _run_output, TestBase as _TestBase -class TestBase(TestBase): +def get_num_queues(): + return len(_queues.list_all()) + + +class TestBase(_TestBase): def tearDown(self): - for qid in _queues.list_all(): + for qid, _ in _queues.list_all(): try: _queues.destroy(qid) except Exception: pass +class LowLevelTests(TestBase): + + # The behaviors in the low-level module is important in as much + # as it is exercised by the high-level module. Therefore the + # most # important testing happens in the high-level tests. + # These low-level tests cover corner cases that are not + # encountered by the high-level module, thus they + # mostly shouldn't matter as much. + + def test_highlevel_reloaded(self): + # See gh-115490 (https://github.com/python/cpython/issues/115490). + importlib.reload(queues) + + def test_create_destroy(self): + qid = _queues.create(2, 0) + _queues.destroy(qid) + self.assertEqual(get_num_queues(), 0) + with self.assertRaises(queues.QueueNotFoundError): + _queues.get(qid) + with self.assertRaises(queues.QueueNotFoundError): + _queues.destroy(qid) + + def test_not_destroyed(self): + # It should have cleaned up any remaining queues. + stdout, stderr = self.assert_python_ok( + '-c', + dedent(f""" + import {_queues.__name__} as _queues + _queues.create(2, 0) + """), + ) + self.assertEqual(stdout, '') + if Py_DEBUG: + self.assertNotEqual(stderr, '') + else: + self.assertEqual(stderr, '') + + def test_bind_release(self): + with self.subTest('typical'): + qid = _queues.create(2, 0) + _queues.bind(qid) + _queues.release(qid) + self.assertEqual(get_num_queues(), 0) + + with self.subTest('bind too much'): + qid = _queues.create(2, 0) + _queues.bind(qid) + _queues.bind(qid) + _queues.release(qid) + _queues.destroy(qid) + self.assertEqual(get_num_queues(), 0) + + with self.subTest('nested'): + qid = _queues.create(2, 0) + _queues.bind(qid) + _queues.bind(qid) + _queues.release(qid) + _queues.release(qid) + self.assertEqual(get_num_queues(), 0) + + with self.subTest('release without binding'): + qid = _queues.create(2, 0) + with self.assertRaises(queues.QueueError): + _queues.release(qid) + + class QueueTests(TestBase): def test_create(self): @@ -51,20 +123,20 @@ def test_shareable(self): queue1 = queues.create() interp = interpreters.create() - interp.exec_sync(dedent(f""" + interp.exec(dedent(f""" from test.support.interpreters import queues queue1 = queues.Queue({queue1.id}) """)); with self.subTest('same interpreter'): queue2 = queues.create() - queue1.put(queue2) + queue1.put(queue2, syncobj=True) queue3 = queue1.get() self.assertIs(queue3, queue2) with self.subTest('from current interpreter'): queue4 = queues.create() - queue1.put(queue4) + queue1.put(queue4, syncobj=True) out = _run_output(interp, dedent(""" queue4 = queue1.get() print(queue4.id) @@ -75,7 +147,7 @@ def test_shareable(self): with self.subTest('from subinterpreter'): out = _run_output(interp, dedent(""" queue5 = queues.create() - queue1.put(queue5) + queue1.put(queue5, syncobj=True) print(queue5.id) """)) qid = int(out) @@ -112,13 +184,19 @@ def test_equality(self): self.assertEqual(queue1, queue1) self.assertNotEqual(queue1, queue2) + def test_pickle(self): + queue = queues.create() + data = pickle.dumps(queue) + unpickled = pickle.loads(data) + self.assertEqual(unpickled, queue) + class TestQueueOps(TestBase): def test_empty(self): queue = queues.create() before = queue.empty() - queue.put(None) + queue.put(None, syncobj=True) during = queue.empty() queue.get() after = queue.empty() @@ -133,7 +211,7 @@ def test_full(self): queue = queues.create(3) for _ in range(3): actual.append(queue.full()) - queue.put(None) + queue.put(None, syncobj=True) actual.append(queue.full()) for _ in range(3): queue.get() @@ -147,16 +225,16 @@ def test_qsize(self): queue = queues.create() for _ in range(3): actual.append(queue.qsize()) - queue.put(None) + queue.put(None, syncobj=True) actual.append(queue.qsize()) queue.get() actual.append(queue.qsize()) - queue.put(None) + queue.put(None, syncobj=True) actual.append(queue.qsize()) for _ in range(3): queue.get() actual.append(queue.qsize()) - queue.put(None) + queue.put(None, syncobj=True) actual.append(queue.qsize()) queue.get() actual.append(queue.qsize()) @@ -165,30 +243,91 @@ def test_qsize(self): def test_put_get_main(self): expected = list(range(20)) - queue = queues.create() - for i in range(20): - queue.put(i) - actual = [queue.get() for _ in range(20)] + for syncobj in (True, False): + kwds = dict(syncobj=syncobj) + with self.subTest(f'syncobj={syncobj}'): + queue = queues.create() + for i in range(20): + queue.put(i, **kwds) + actual = [queue.get() for _ in range(20)] - self.assertEqual(actual, expected) + self.assertEqual(actual, expected) def test_put_timeout(self): - queue = queues.create(2) - queue.put(None) - queue.put(None) - with self.assertRaises(queues.QueueFull): - queue.put(None, timeout=0.1) - queue.get() - queue.put(None) + for syncobj in (True, False): + kwds = dict(syncobj=syncobj) + with self.subTest(f'syncobj={syncobj}'): + queue = queues.create(2) + queue.put(None, **kwds) + queue.put(None, **kwds) + with self.assertRaises(queues.QueueFull): + queue.put(None, timeout=0.1, **kwds) + queue.get() + queue.put(None, **kwds) def test_put_nowait(self): - queue = queues.create(2) - queue.put_nowait(None) - queue.put_nowait(None) - with self.assertRaises(queues.QueueFull): - queue.put_nowait(None) - queue.get() - queue.put_nowait(None) + for syncobj in (True, False): + kwds = dict(syncobj=syncobj) + with self.subTest(f'syncobj={syncobj}'): + queue = queues.create(2) + queue.put_nowait(None, **kwds) + queue.put_nowait(None, **kwds) + with self.assertRaises(queues.QueueFull): + queue.put_nowait(None, **kwds) + queue.get() + queue.put_nowait(None, **kwds) + + def test_put_syncobj(self): + for obj in [ + None, + True, + 10, + 'spam', + b'spam', + (0, 'a'), + ]: + with self.subTest(repr(obj)): + queue = queues.create() + + queue.put(obj, syncobj=True) + obj2 = queue.get() + self.assertEqual(obj2, obj) + + queue.put(obj, syncobj=True) + obj2 = queue.get_nowait() + self.assertEqual(obj2, obj) + + for obj in [ + [1, 2, 3], + {'a': 13, 'b': 17}, + ]: + with self.subTest(repr(obj)): + queue = queues.create() + with self.assertRaises(interpreters.NotShareableError): + queue.put(obj, syncobj=True) + + def test_put_not_syncobj(self): + for obj in [ + None, + True, + 10, + 'spam', + b'spam', + (0, 'a'), + # not shareable + [1, 2, 3], + {'a': 13, 'b': 17}, + ]: + with self.subTest(repr(obj)): + queue = queues.create() + + queue.put(obj, syncobj=False) + obj2 = queue.get() + self.assertEqual(obj2, obj) + + queue.put(obj, syncobj=False) + obj2 = queue.get_nowait() + self.assertEqual(obj2, obj) def test_get_timeout(self): queue = queues.create() @@ -200,17 +339,54 @@ def test_get_nowait(self): with self.assertRaises(queues.QueueEmpty): queue.get_nowait() + def test_put_get_default_syncobj(self): + expected = list(range(20)) + queue = queues.create(syncobj=True) + for methname in ('get', 'get_nowait'): + with self.subTest(f'{methname}()'): + get = getattr(queue, methname) + for i in range(20): + queue.put(i) + actual = [get() for _ in range(20)] + self.assertEqual(actual, expected) + + obj = [1, 2, 3] # lists are not shareable + with self.assertRaises(interpreters.NotShareableError): + queue.put(obj) + + def test_put_get_default_not_syncobj(self): + expected = list(range(20)) + queue = queues.create(syncobj=False) + for methname in ('get', 'get_nowait'): + with self.subTest(f'{methname}()'): + get = getattr(queue, methname) + + for i in range(20): + queue.put(i) + actual = [get() for _ in range(20)] + self.assertEqual(actual, expected) + + obj = [1, 2, 3] # lists are not shareable + queue.put(obj) + obj2 = get() + self.assertEqual(obj, obj2) + self.assertIsNot(obj, obj2) + def test_put_get_same_interpreter(self): interp = interpreters.create() - interp.exec_sync(dedent(""" + interp.exec(dedent(""" from test.support.interpreters import queues queue = queues.create() - orig = b'spam' - queue.put(orig) - obj = queue.get() - assert obj == orig, 'expected: obj == orig' - assert obj is not orig, 'expected: obj is not orig' """)) + for methname in ('get', 'get_nowait'): + with self.subTest(f'{methname}()'): + interp.exec(dedent(f""" + orig = b'spam' + queue.put(orig, syncobj=True) + obj = queue.{methname}() + assert obj == orig, 'expected: obj == orig' + assert obj is not orig, 'expected: obj is not orig' + """)) def test_put_get_different_interpreters(self): interp = interpreters.create() @@ -218,34 +394,37 @@ def test_put_get_different_interpreters(self): queue2 = queues.create() self.assertEqual(len(queues.list_all()), 2) - obj1 = b'spam' - queue1.put(obj1) - - out = _run_output( - interp, - dedent(f""" - from test.support.interpreters import queues - queue1 = queues.Queue({queue1.id}) - queue2 = queues.Queue({queue2.id}) - assert queue1.qsize() == 1, 'expected: queue1.qsize() == 1' - obj = queue1.get() - assert queue1.qsize() == 0, 'expected: queue1.qsize() == 0' - assert obj == b'spam', 'expected: obj == obj1' - # When going to another interpreter we get a copy. - assert id(obj) != {id(obj1)}, 'expected: obj is not obj1' - obj2 = b'eggs' - print(id(obj2)) - assert queue2.qsize() == 0, 'expected: queue2.qsize() == 0' - queue2.put(obj2) - assert queue2.qsize() == 1, 'expected: queue2.qsize() == 1' - """)) - self.assertEqual(len(queues.list_all()), 2) - self.assertEqual(queue1.qsize(), 0) - self.assertEqual(queue2.qsize(), 1) - - obj2 = queue2.get() - self.assertEqual(obj2, b'eggs') - self.assertNotEqual(id(obj2), int(out)) + for methname in ('get', 'get_nowait'): + with self.subTest(f'{methname}()'): + obj1 = b'spam' + queue1.put(obj1, syncobj=True) + + out = _run_output( + interp, + dedent(f""" + from test.support.interpreters import queues + queue1 = queues.Queue({queue1.id}) + queue2 = queues.Queue({queue2.id}) + assert queue1.qsize() == 1, 'expected: queue1.qsize() == 1' + obj = queue1.{methname}() + assert queue1.qsize() == 0, 'expected: queue1.qsize() == 0' + assert obj == b'spam', 'expected: obj == obj1' + # When going to another interpreter we get a copy. + assert id(obj) != {id(obj1)}, 'expected: obj is not obj1' + obj2 = b'eggs' + print(id(obj2)) + assert queue2.qsize() == 0, 'expected: queue2.qsize() == 0' + queue2.put(obj2, syncobj=True) + assert queue2.qsize() == 1, 'expected: queue2.qsize() == 1' + """)) + self.assertEqual(len(queues.list_all()), 2) + self.assertEqual(queue1.qsize(), 0) + self.assertEqual(queue2.qsize(), 1) + + get = getattr(queue2, methname) + obj2 = get() + self.assertEqual(obj2, b'eggs') + self.assertNotEqual(id(obj2), int(out)) def test_put_cleared_with_subinterpreter(self): interp = interpreters.create() @@ -258,8 +437,8 @@ def test_put_cleared_with_subinterpreter(self): queue = queues.Queue({queue.id}) obj1 = b'spam' obj2 = b'eggs' - queue.put(obj1) - queue.put(obj2) + queue.put(obj1, syncobj=True) + queue.put(obj2, syncobj=True) """)) self.assertEqual(queue.qsize(), 2) @@ -281,12 +460,12 @@ def f(): break except queues.QueueEmpty: continue - queue2.put(obj) + queue2.put(obj, syncobj=True) t = threading.Thread(target=f) t.start() orig = b'spam' - queue1.put(orig) + queue1.put(orig, syncobj=True) obj = queue2.get() t.join() diff --git a/Lib/test/test_interpreters/utils.py b/Lib/test/test_interpreters/utils.py index 3a37ed09dd89434..973d05d4f96dcb8 100644 --- a/Lib/test/test_interpreters/utils.py +++ b/Lib/test/test_interpreters/utils.py @@ -4,8 +4,9 @@ import subprocess import sys import tempfile -import threading from textwrap import dedent +import threading +import types import unittest from test import support @@ -41,7 +42,7 @@ def _run_output(interp, request, init=None): with rpipe: if init: interp.prepare_main(init) - interp.exec_sync(script) + interp.exec(script) return rpipe.read() @@ -49,7 +50,7 @@ def _run_output(interp, request, init=None): def _running(interp): r, w = os.pipe() def run(): - interp.exec_sync(dedent(f""" + interp.exec(dedent(f""" # wait for "signal" with open({r}) as rpipe: rpipe.read() @@ -84,6 +85,18 @@ def temp_dir(self): self.addCleanup(lambda: os_helper.rmtree(tempdir)) return tempdir + @contextlib.contextmanager + def captured_thread_exception(self): + ctx = types.SimpleNamespace(caught=None) + def excepthook(args): + ctx.caught = args + orig_excepthook = threading.excepthook + threading.excepthook = excepthook + try: + yield ctx + finally: + threading.excepthook = orig_excepthook + def make_script(self, filename, dirname=None, text=None): if text: text = dedent(text) diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index 9af0730ea980049..95e67911db6a7f2 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -1,7 +1,7 @@ import doctest import unittest from test import support -from test.support import threading_helper +from test.support import threading_helper, script_helper from itertools import * import weakref from decimal import Decimal @@ -1699,6 +1699,14 @@ def test_tee(self): self.pickletest(proto, a, compare=ans) self.pickletest(proto, b, compare=ans) + def test_tee_dealloc_segfault(self): + # gh-115874: segfaults when accessing module state in tp_dealloc. + script = ( + "import typing, copyreg, itertools; " + "copyreg.buggy_tee = itertools.tee(())" + ) + script_helper.assert_python_ok("-c", script) + # Issue 13454: Crash when deleting backward iterator from tee() def test_tee_del_backward(self): forward, backward = tee(repeat(None, 20000000)) diff --git a/Lib/test/test_largefile.py b/Lib/test/test_largefile.py index 3b0930fe69e30e8..849b6cb3e50a193 100644 --- a/Lib/test/test_largefile.py +++ b/Lib/test/test_largefile.py @@ -2,7 +2,6 @@ """ import os -import stat import sys import unittest import socket @@ -29,7 +28,7 @@ def setUp(self): mode = 'w+b' with self.open(TESTFN, mode) as f: - current_size = os.fstat(f.fileno())[stat.ST_SIZE] + current_size = os.fstat(f.fileno()).st_size if current_size == size+1: return @@ -40,13 +39,13 @@ def setUp(self): f.seek(size) f.write(b'a') f.flush() - self.assertEqual(os.fstat(f.fileno())[stat.ST_SIZE], size+1) + self.assertEqual(os.fstat(f.fileno()).st_size, size+1) @classmethod def tearDownClass(cls): with cls.open(TESTFN, 'wb'): pass - if not os.stat(TESTFN)[stat.ST_SIZE] == 0: + if not os.stat(TESTFN).st_size == 0: raise cls.failureException('File was not truncated by opening ' 'with mode "wb"') unlink(TESTFN2) @@ -67,7 +66,7 @@ def test_large_read(self, _size): self.assertEqual(f.tell(), size + 1) def test_osstat(self): - self.assertEqual(os.stat(TESTFN)[stat.ST_SIZE], size+1) + self.assertEqual(os.stat(TESTFN).st_size, size+1) def test_seek_read(self): with self.open(TESTFN, 'rb') as f: diff --git a/Lib/test/test_list.py b/Lib/test/test_list.py index 2969c6e2f98a23f..0601b33e79ebb69 100644 --- a/Lib/test/test_list.py +++ b/Lib/test/test_list.py @@ -96,6 +96,11 @@ def imul(a, b): a *= b self.assertRaises((MemoryError, OverflowError), mul, lst, n) self.assertRaises((MemoryError, OverflowError), imul, lst, n) + def test_empty_slice(self): + x = [] + x[:] = x + self.assertEqual(x, []) + def test_list_resize_overflow(self): # gh-97616: test new_allocated * sizeof(PyObject*) overflow # check in list_resize() diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index d87142698efa8b3..7b5bc6b6a741800 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -80,6 +80,9 @@ skip_if_asan_fork = unittest.skipIf( support.HAVE_ASAN_FORK_BUG, "libasan has a pthread_create() dead lock related to thread+fork") +skip_if_tsan_fork = unittest.skipIf( + support.check_sanitizer(thread=True), + "TSAN doesn't support threads after fork") class BaseTest(unittest.TestCase): @@ -603,7 +606,7 @@ def test_name(self): def test_builtin_handlers(self): # We can't actually *use* too many handlers in the tests, # but we can try instantiating them with various options - if sys.platform in ('linux', 'darwin'): + if sys.platform in ('linux', 'android', 'darwin'): for existing in (True, False): fn = make_temp_file() if not existing: @@ -667,7 +670,7 @@ def test_path_objects(self): (logging.handlers.RotatingFileHandler, (pfn, 'a')), (logging.handlers.TimedRotatingFileHandler, (pfn, 'h')), ) - if sys.platform in ('linux', 'darwin'): + if sys.platform in ('linux', 'android', 'darwin'): cases += ((logging.handlers.WatchedFileHandler, (pfn, 'w')),) for cls, args in cases: h = cls(*args, encoding="utf-8") @@ -731,6 +734,7 @@ def remove_loop(fname, tries): @support.requires_fork() @threading_helper.requires_working_threading() @skip_if_asan_fork + @skip_if_tsan_fork def test_post_fork_child_no_deadlock(self): """Ensure child logging locks are not held; bpo-6721 & bpo-36533.""" class _OurHandler(logging.Handler): @@ -6080,6 +6084,52 @@ def test_rollover(self): print(tf.read()) self.assertTrue(found, msg=msg) + def test_rollover_at_midnight(self, weekly=False): + os_helper.unlink(self.fn) + now = datetime.datetime.now() + atTime = now.time() + if not 0.1 < atTime.microsecond/1e6 < 0.9: + # The test requires all records to be emitted within + # the range of the same whole second. + time.sleep((0.1 - atTime.microsecond/1e6) % 1.0) + now = datetime.datetime.now() + atTime = now.time() + atTime = atTime.replace(microsecond=0) + fmt = logging.Formatter('%(asctime)s %(message)s') + when = f'W{now.weekday()}' if weekly else 'MIDNIGHT' + for i in range(3): + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when=when, atTime=atTime) + fh.setFormatter(fmt) + r2 = logging.makeLogRecord({'msg': f'testing1 {i}'}) + fh.emit(r2) + fh.close() + self.assertLogFile(self.fn) + with open(self.fn, encoding="utf-8") as f: + for i, line in enumerate(f): + self.assertIn(f'testing1 {i}', line) + + os.utime(self.fn, (now.timestamp() - 1,)*2) + for i in range(2): + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when=when, atTime=atTime) + fh.setFormatter(fmt) + r2 = logging.makeLogRecord({'msg': f'testing2 {i}'}) + fh.emit(r2) + fh.close() + rolloverDate = now - datetime.timedelta(days=7 if weekly else 1) + otherfn = f'{self.fn}.{rolloverDate:%Y-%m-%d}' + self.assertLogFile(otherfn) + with open(self.fn, encoding="utf-8") as f: + for i, line in enumerate(f): + self.assertIn(f'testing2 {i}', line) + with open(otherfn, encoding="utf-8") as f: + for i, line in enumerate(f): + self.assertIn(f'testing1 {i}', line) + + def test_rollover_at_weekday(self): + self.test_rollover_at_midnight(weekly=True) + def test_invalid(self): assertRaises = self.assertRaises assertRaises(ValueError, logging.handlers.TimedRotatingFileHandler, @@ -6089,22 +6139,47 @@ def test_invalid(self): assertRaises(ValueError, logging.handlers.TimedRotatingFileHandler, self.fn, 'W7', encoding="utf-8", delay=True) + # TODO: Test for utc=False. def test_compute_rollover_daily_attime(self): currentTime = 0 + rh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when='MIDNIGHT', + utc=True, atTime=None) + try: + actual = rh.computeRollover(currentTime) + self.assertEqual(actual, currentTime + 24 * 60 * 60) + + actual = rh.computeRollover(currentTime + 24 * 60 * 60 - 1) + self.assertEqual(actual, currentTime + 24 * 60 * 60) + + actual = rh.computeRollover(currentTime + 24 * 60 * 60) + self.assertEqual(actual, currentTime + 48 * 60 * 60) + + actual = rh.computeRollover(currentTime + 25 * 60 * 60) + self.assertEqual(actual, currentTime + 48 * 60 * 60) + finally: + rh.close() + atTime = datetime.time(12, 0, 0) rh = logging.handlers.TimedRotatingFileHandler( - self.fn, encoding="utf-8", when='MIDNIGHT', interval=1, backupCount=0, + self.fn, encoding="utf-8", when='MIDNIGHT', utc=True, atTime=atTime) try: actual = rh.computeRollover(currentTime) self.assertEqual(actual, currentTime + 12 * 60 * 60) + actual = rh.computeRollover(currentTime + 12 * 60 * 60 - 1) + self.assertEqual(actual, currentTime + 12 * 60 * 60) + + actual = rh.computeRollover(currentTime + 12 * 60 * 60) + self.assertEqual(actual, currentTime + 36 * 60 * 60) + actual = rh.computeRollover(currentTime + 13 * 60 * 60) self.assertEqual(actual, currentTime + 36 * 60 * 60) finally: rh.close() - #@unittest.skipIf(True, 'Temporarily skipped while failures investigated.') + # TODO: Test for utc=False. def test_compute_rollover_weekly_attime(self): currentTime = int(time.time()) today = currentTime - currentTime % 86400 @@ -6129,14 +6204,28 @@ def test_compute_rollover_weekly_attime(self): expected += 12 * 60 * 60 # Add in adjustment for today expected += today + actual = rh.computeRollover(today) if actual != expected: print('failed in timezone: %d' % time.timezone) print('local vars: %s' % locals()) self.assertEqual(actual, expected) + + actual = rh.computeRollover(today + 12 * 60 * 60 - 1) + if actual != expected: + print('failed in timezone: %d' % time.timezone) + print('local vars: %s' % locals()) + self.assertEqual(actual, expected) + if day == wday: # goes into following week expected += 7 * 24 * 60 * 60 + actual = rh.computeRollover(today + 12 * 60 * 60) + if actual != expected: + print('failed in timezone: %d' % time.timezone) + print('local vars: %s' % locals()) + self.assertEqual(actual, expected) + actual = rh.computeRollover(today + 13 * 60 * 60) if actual != expected: print('failed in timezone: %d' % time.timezone) @@ -6154,7 +6243,7 @@ def test_compute_files_to_delete(self): for i in range(10): times.append(dt.strftime('%Y-%m-%d_%H-%M-%S')) dt += datetime.timedelta(seconds=5) - prefixes = ('a.b', 'a.b.c', 'd.e', 'd.e.f') + prefixes = ('a.b', 'a.b.c', 'd.e', 'd.e.f', 'g') files = [] rotators = [] for prefix in prefixes: @@ -6167,10 +6256,22 @@ def test_compute_files_to_delete(self): if prefix.startswith('a.b'): for t in times: files.append('%s.log.%s' % (prefix, t)) - else: - rotator.namer = lambda name: name.replace('.log', '') + '.log' + elif prefix.startswith('d.e'): + def namer(filename): + dirname, basename = os.path.split(filename) + basename = basename.replace('.log', '') + '.log' + return os.path.join(dirname, basename) + rotator.namer = namer for t in times: files.append('%s.%s.log' % (prefix, t)) + elif prefix == 'g': + def namer(filename): + dirname, basename = os.path.split(filename) + basename = 'g' + basename[6:] + '.oldlog' + return os.path.join(dirname, basename) + rotator.namer = namer + for t in times: + files.append('g%s.oldlog' % t) # Create empty files for fn in files: p = os.path.join(wd, fn) @@ -6180,18 +6281,23 @@ def test_compute_files_to_delete(self): for i, prefix in enumerate(prefixes): rotator = rotators[i] candidates = rotator.getFilesToDelete() - self.assertEqual(len(candidates), 3) + self.assertEqual(len(candidates), 3, candidates) if prefix.startswith('a.b'): p = '%s.log.' % prefix for c in candidates: d, fn = os.path.split(c) self.assertTrue(fn.startswith(p)) - else: + elif prefix.startswith('d.e'): for c in candidates: d, fn = os.path.split(c) - self.assertTrue(fn.endswith('.log')) + self.assertTrue(fn.endswith('.log'), fn) self.assertTrue(fn.startswith(prefix + '.') and fn[len(prefix) + 2].isdigit()) + elif prefix == 'g': + for c in candidates: + d, fn = os.path.split(c) + self.assertTrue(fn.endswith('.oldlog')) + self.assertTrue(fn.startswith('g') and fn[1].isdigit()) def test_compute_files_to_delete_same_filename_different_extensions(self): # See GH-93205 for background @@ -6215,6 +6321,8 @@ def test_compute_files_to_delete_same_filename_different_extensions(self): rotators.append(rotator) for t in times: files.append('%s.%s' % (prefix, t)) + for t in times: + files.append('a.log.%s.c' % t) # Create empty files for f in files: (wd / f).touch() @@ -6223,14 +6331,375 @@ def test_compute_files_to_delete_same_filename_different_extensions(self): backupCount = i+1 rotator = rotators[i] candidates = rotator.getFilesToDelete() - self.assertEqual(len(candidates), n_files - backupCount) - matcher = re.compile(r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}(\.\w+)?$") + self.assertEqual(len(candidates), n_files - backupCount, candidates) + matcher = re.compile(r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\Z") for c in candidates: d, fn = os.path.split(c) - self.assertTrue(fn.startswith(prefix)) + self.assertTrue(fn.startswith(prefix+'.')) suffix = fn[(len(prefix)+1):] self.assertRegex(suffix, matcher) + # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in + # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0). + @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0') + def test_compute_rollover_MIDNIGHT_local(self): + # DST begins at 2012-3-11T02:00:00 and ends at 2012-11-4T02:00:00. + DT = datetime.datetime + def test(current, expected): + actual = fh.computeRollover(current.timestamp()) + diff = actual - expected.timestamp() + if diff: + self.assertEqual(diff, 0, datetime.timedelta(seconds=diff)) + + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when='MIDNIGHT', utc=False) + + test(DT(2012, 3, 10, 23, 59, 59), DT(2012, 3, 11, 0, 0)) + test(DT(2012, 3, 11, 0, 0), DT(2012, 3, 12, 0, 0)) + test(DT(2012, 3, 11, 1, 0), DT(2012, 3, 12, 0, 0)) + + test(DT(2012, 11, 3, 23, 59, 59), DT(2012, 11, 4, 0, 0)) + test(DT(2012, 11, 4, 0, 0), DT(2012, 11, 5, 0, 0)) + test(DT(2012, 11, 4, 1, 0), DT(2012, 11, 5, 0, 0)) + + fh.close() + + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when='MIDNIGHT', utc=False, + atTime=datetime.time(12, 0, 0)) + + test(DT(2012, 3, 10, 11, 59, 59), DT(2012, 3, 10, 12, 0)) + test(DT(2012, 3, 10, 12, 0), DT(2012, 3, 11, 12, 0)) + test(DT(2012, 3, 10, 13, 0), DT(2012, 3, 11, 12, 0)) + + test(DT(2012, 11, 3, 11, 59, 59), DT(2012, 11, 3, 12, 0)) + test(DT(2012, 11, 3, 12, 0), DT(2012, 11, 4, 12, 0)) + test(DT(2012, 11, 3, 13, 0), DT(2012, 11, 4, 12, 0)) + + fh.close() + + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when='MIDNIGHT', utc=False, + atTime=datetime.time(2, 0, 0)) + + test(DT(2012, 3, 10, 1, 59, 59), DT(2012, 3, 10, 2, 0)) + # 2:00:00 is the same as 3:00:00 at 2012-3-11. + test(DT(2012, 3, 10, 2, 0), DT(2012, 3, 11, 3, 0)) + test(DT(2012, 3, 10, 3, 0), DT(2012, 3, 11, 3, 0)) + + test(DT(2012, 3, 11, 1, 59, 59), DT(2012, 3, 11, 3, 0)) + # No time between 2:00:00 and 3:00:00 at 2012-3-11. + test(DT(2012, 3, 11, 3, 0), DT(2012, 3, 12, 2, 0)) + test(DT(2012, 3, 11, 4, 0), DT(2012, 3, 12, 2, 0)) + + test(DT(2012, 11, 3, 1, 59, 59), DT(2012, 11, 3, 2, 0)) + test(DT(2012, 11, 3, 2, 0), DT(2012, 11, 4, 2, 0)) + test(DT(2012, 11, 3, 3, 0), DT(2012, 11, 4, 2, 0)) + + # 1:00:00-2:00:00 is repeated twice at 2012-11-4. + test(DT(2012, 11, 4, 1, 59, 59), DT(2012, 11, 4, 2, 0)) + test(DT(2012, 11, 4, 1, 59, 59, fold=1), DT(2012, 11, 4, 2, 0)) + test(DT(2012, 11, 4, 2, 0), DT(2012, 11, 5, 2, 0)) + test(DT(2012, 11, 4, 3, 0), DT(2012, 11, 5, 2, 0)) + + fh.close() + + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when='MIDNIGHT', utc=False, + atTime=datetime.time(2, 30, 0)) + + test(DT(2012, 3, 10, 2, 29, 59), DT(2012, 3, 10, 2, 30)) + # No time 2:30:00 at 2012-3-11. + test(DT(2012, 3, 10, 2, 30), DT(2012, 3, 11, 3, 30)) + test(DT(2012, 3, 10, 3, 0), DT(2012, 3, 11, 3, 30)) + + test(DT(2012, 3, 11, 1, 59, 59), DT(2012, 3, 11, 3, 30)) + # No time between 2:00:00 and 3:00:00 at 2012-3-11. + test(DT(2012, 3, 11, 3, 0), DT(2012, 3, 12, 2, 30)) + test(DT(2012, 3, 11, 3, 30), DT(2012, 3, 12, 2, 30)) + + test(DT(2012, 11, 3, 2, 29, 59), DT(2012, 11, 3, 2, 30)) + test(DT(2012, 11, 3, 2, 30), DT(2012, 11, 4, 2, 30)) + test(DT(2012, 11, 3, 3, 0), DT(2012, 11, 4, 2, 30)) + + fh.close() + + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when='MIDNIGHT', utc=False, + atTime=datetime.time(1, 30, 0)) + + test(DT(2012, 3, 11, 1, 29, 59), DT(2012, 3, 11, 1, 30)) + test(DT(2012, 3, 11, 1, 30), DT(2012, 3, 12, 1, 30)) + test(DT(2012, 3, 11, 1, 59, 59), DT(2012, 3, 12, 1, 30)) + # No time between 2:00:00 and 3:00:00 at 2012-3-11. + test(DT(2012, 3, 11, 3, 0), DT(2012, 3, 12, 1, 30)) + test(DT(2012, 3, 11, 3, 30), DT(2012, 3, 12, 1, 30)) + + # 1:00:00-2:00:00 is repeated twice at 2012-11-4. + test(DT(2012, 11, 4, 1, 0), DT(2012, 11, 4, 1, 30)) + test(DT(2012, 11, 4, 1, 29, 59), DT(2012, 11, 4, 1, 30)) + test(DT(2012, 11, 4, 1, 30), DT(2012, 11, 5, 1, 30)) + test(DT(2012, 11, 4, 1, 59, 59), DT(2012, 11, 5, 1, 30)) + # It is weird, but the rollover date jumps back from 2012-11-5 + # to 2012-11-4. + test(DT(2012, 11, 4, 1, 0, fold=1), DT(2012, 11, 4, 1, 30, fold=1)) + test(DT(2012, 11, 4, 1, 29, 59, fold=1), DT(2012, 11, 4, 1, 30, fold=1)) + test(DT(2012, 11, 4, 1, 30, fold=1), DT(2012, 11, 5, 1, 30)) + test(DT(2012, 11, 4, 1, 59, 59, fold=1), DT(2012, 11, 5, 1, 30)) + test(DT(2012, 11, 4, 2, 0), DT(2012, 11, 5, 1, 30)) + test(DT(2012, 11, 4, 2, 30), DT(2012, 11, 5, 1, 30)) + + fh.close() + + # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in + # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0). + @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0') + def test_compute_rollover_W6_local(self): + # DST begins at 2012-3-11T02:00:00 and ends at 2012-11-4T02:00:00. + DT = datetime.datetime + def test(current, expected): + actual = fh.computeRollover(current.timestamp()) + diff = actual - expected.timestamp() + if diff: + self.assertEqual(diff, 0, datetime.timedelta(seconds=diff)) + + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when='W6', utc=False) + + test(DT(2012, 3, 4, 23, 59, 59), DT(2012, 3, 5, 0, 0)) + test(DT(2012, 3, 5, 0, 0), DT(2012, 3, 12, 0, 0)) + test(DT(2012, 3, 5, 1, 0), DT(2012, 3, 12, 0, 0)) + + test(DT(2012, 10, 28, 23, 59, 59), DT(2012, 10, 29, 0, 0)) + test(DT(2012, 10, 29, 0, 0), DT(2012, 11, 5, 0, 0)) + test(DT(2012, 10, 29, 1, 0), DT(2012, 11, 5, 0, 0)) + + fh.close() + + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when='W6', utc=False, + atTime=datetime.time(0, 0, 0)) + + test(DT(2012, 3, 10, 23, 59, 59), DT(2012, 3, 11, 0, 0)) + test(DT(2012, 3, 11, 0, 0), DT(2012, 3, 18, 0, 0)) + test(DT(2012, 3, 11, 1, 0), DT(2012, 3, 18, 0, 0)) + + test(DT(2012, 11, 3, 23, 59, 59), DT(2012, 11, 4, 0, 0)) + test(DT(2012, 11, 4, 0, 0), DT(2012, 11, 11, 0, 0)) + test(DT(2012, 11, 4, 1, 0), DT(2012, 11, 11, 0, 0)) + + fh.close() + + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when='W6', utc=False, + atTime=datetime.time(12, 0, 0)) + + test(DT(2012, 3, 4, 11, 59, 59), DT(2012, 3, 4, 12, 0)) + test(DT(2012, 3, 4, 12, 0), DT(2012, 3, 11, 12, 0)) + test(DT(2012, 3, 4, 13, 0), DT(2012, 3, 11, 12, 0)) + + test(DT(2012, 10, 28, 11, 59, 59), DT(2012, 10, 28, 12, 0)) + test(DT(2012, 10, 28, 12, 0), DT(2012, 11, 4, 12, 0)) + test(DT(2012, 10, 28, 13, 0), DT(2012, 11, 4, 12, 0)) + + fh.close() + + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when='W6', utc=False, + atTime=datetime.time(2, 0, 0)) + + test(DT(2012, 3, 4, 1, 59, 59), DT(2012, 3, 4, 2, 0)) + # 2:00:00 is the same as 3:00:00 at 2012-3-11. + test(DT(2012, 3, 4, 2, 0), DT(2012, 3, 11, 3, 0)) + test(DT(2012, 3, 4, 3, 0), DT(2012, 3, 11, 3, 0)) + + test(DT(2012, 3, 11, 1, 59, 59), DT(2012, 3, 11, 3, 0)) + # No time between 2:00:00 and 3:00:00 at 2012-3-11. + test(DT(2012, 3, 11, 3, 0), DT(2012, 3, 18, 2, 0)) + test(DT(2012, 3, 11, 4, 0), DT(2012, 3, 18, 2, 0)) + + test(DT(2012, 10, 28, 1, 59, 59), DT(2012, 10, 28, 2, 0)) + test(DT(2012, 10, 28, 2, 0), DT(2012, 11, 4, 2, 0)) + test(DT(2012, 10, 28, 3, 0), DT(2012, 11, 4, 2, 0)) + + # 1:00:00-2:00:00 is repeated twice at 2012-11-4. + test(DT(2012, 11, 4, 1, 59, 59), DT(2012, 11, 4, 2, 0)) + test(DT(2012, 11, 4, 1, 59, 59, fold=1), DT(2012, 11, 4, 2, 0)) + test(DT(2012, 11, 4, 2, 0), DT(2012, 11, 11, 2, 0)) + test(DT(2012, 11, 4, 3, 0), DT(2012, 11, 11, 2, 0)) + + fh.close() + + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when='W6', utc=False, + atTime=datetime.time(2, 30, 0)) + + test(DT(2012, 3, 4, 2, 29, 59), DT(2012, 3, 4, 2, 30)) + # No time 2:30:00 at 2012-3-11. + test(DT(2012, 3, 4, 2, 30), DT(2012, 3, 11, 3, 30)) + test(DT(2012, 3, 4, 3, 0), DT(2012, 3, 11, 3, 30)) + + test(DT(2012, 3, 11, 1, 59, 59), DT(2012, 3, 11, 3, 30)) + # No time between 2:00:00 and 3:00:00 at 2012-3-11. + test(DT(2012, 3, 11, 3, 0), DT(2012, 3, 18, 2, 30)) + test(DT(2012, 3, 11, 3, 30), DT(2012, 3, 18, 2, 30)) + + test(DT(2012, 10, 28, 2, 29, 59), DT(2012, 10, 28, 2, 30)) + test(DT(2012, 10, 28, 2, 30), DT(2012, 11, 4, 2, 30)) + test(DT(2012, 10, 28, 3, 0), DT(2012, 11, 4, 2, 30)) + + fh.close() + + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when='W6', utc=False, + atTime=datetime.time(1, 30, 0)) + + test(DT(2012, 3, 11, 1, 29, 59), DT(2012, 3, 11, 1, 30)) + test(DT(2012, 3, 11, 1, 30), DT(2012, 3, 18, 1, 30)) + test(DT(2012, 3, 11, 1, 59, 59), DT(2012, 3, 18, 1, 30)) + # No time between 2:00:00 and 3:00:00 at 2012-3-11. + test(DT(2012, 3, 11, 3, 0), DT(2012, 3, 18, 1, 30)) + test(DT(2012, 3, 11, 3, 30), DT(2012, 3, 18, 1, 30)) + + # 1:00:00-2:00:00 is repeated twice at 2012-11-4. + test(DT(2012, 11, 4, 1, 0), DT(2012, 11, 4, 1, 30)) + test(DT(2012, 11, 4, 1, 29, 59), DT(2012, 11, 4, 1, 30)) + test(DT(2012, 11, 4, 1, 30), DT(2012, 11, 11, 1, 30)) + test(DT(2012, 11, 4, 1, 59, 59), DT(2012, 11, 11, 1, 30)) + # It is weird, but the rollover date jumps back from 2012-11-11 + # to 2012-11-4. + test(DT(2012, 11, 4, 1, 0, fold=1), DT(2012, 11, 4, 1, 30, fold=1)) + test(DT(2012, 11, 4, 1, 29, 59, fold=1), DT(2012, 11, 4, 1, 30, fold=1)) + test(DT(2012, 11, 4, 1, 30, fold=1), DT(2012, 11, 11, 1, 30)) + test(DT(2012, 11, 4, 1, 59, 59, fold=1), DT(2012, 11, 11, 1, 30)) + test(DT(2012, 11, 4, 2, 0), DT(2012, 11, 11, 1, 30)) + test(DT(2012, 11, 4, 2, 30), DT(2012, 11, 11, 1, 30)) + + fh.close() + + # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in + # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0). + @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0') + def test_compute_rollover_MIDNIGHT_local_interval(self): + # DST begins at 2012-3-11T02:00:00 and ends at 2012-11-4T02:00:00. + DT = datetime.datetime + def test(current, expected): + actual = fh.computeRollover(current.timestamp()) + diff = actual - expected.timestamp() + if diff: + self.assertEqual(diff, 0, datetime.timedelta(seconds=diff)) + + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when='MIDNIGHT', utc=False, interval=3) + + test(DT(2012, 3, 8, 23, 59, 59), DT(2012, 3, 11, 0, 0)) + test(DT(2012, 3, 9, 0, 0), DT(2012, 3, 12, 0, 0)) + test(DT(2012, 3, 9, 1, 0), DT(2012, 3, 12, 0, 0)) + test(DT(2012, 3, 10, 23, 59, 59), DT(2012, 3, 13, 0, 0)) + test(DT(2012, 3, 11, 0, 0), DT(2012, 3, 14, 0, 0)) + test(DT(2012, 3, 11, 1, 0), DT(2012, 3, 14, 0, 0)) + + test(DT(2012, 11, 1, 23, 59, 59), DT(2012, 11, 4, 0, 0)) + test(DT(2012, 11, 2, 0, 0), DT(2012, 11, 5, 0, 0)) + test(DT(2012, 11, 2, 1, 0), DT(2012, 11, 5, 0, 0)) + test(DT(2012, 11, 3, 23, 59, 59), DT(2012, 11, 6, 0, 0)) + test(DT(2012, 11, 4, 0, 0), DT(2012, 11, 7, 0, 0)) + test(DT(2012, 11, 4, 1, 0), DT(2012, 11, 7, 0, 0)) + + fh.close() + + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when='MIDNIGHT', utc=False, interval=3, + atTime=datetime.time(12, 0, 0)) + + test(DT(2012, 3, 8, 11, 59, 59), DT(2012, 3, 10, 12, 0)) + test(DT(2012, 3, 8, 12, 0), DT(2012, 3, 11, 12, 0)) + test(DT(2012, 3, 8, 13, 0), DT(2012, 3, 11, 12, 0)) + test(DT(2012, 3, 10, 11, 59, 59), DT(2012, 3, 12, 12, 0)) + test(DT(2012, 3, 10, 12, 0), DT(2012, 3, 13, 12, 0)) + test(DT(2012, 3, 10, 13, 0), DT(2012, 3, 13, 12, 0)) + + test(DT(2012, 11, 1, 11, 59, 59), DT(2012, 11, 3, 12, 0)) + test(DT(2012, 11, 1, 12, 0), DT(2012, 11, 4, 12, 0)) + test(DT(2012, 11, 1, 13, 0), DT(2012, 11, 4, 12, 0)) + test(DT(2012, 11, 3, 11, 59, 59), DT(2012, 11, 5, 12, 0)) + test(DT(2012, 11, 3, 12, 0), DT(2012, 11, 6, 12, 0)) + test(DT(2012, 11, 3, 13, 0), DT(2012, 11, 6, 12, 0)) + + fh.close() + + # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in + # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0). + @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0') + def test_compute_rollover_W6_local_interval(self): + # DST begins at 2012-3-11T02:00:00 and ends at 2012-11-4T02:00:00. + DT = datetime.datetime + def test(current, expected): + actual = fh.computeRollover(current.timestamp()) + diff = actual - expected.timestamp() + if diff: + self.assertEqual(diff, 0, datetime.timedelta(seconds=diff)) + + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when='W6', utc=False, interval=3) + + test(DT(2012, 2, 19, 23, 59, 59), DT(2012, 3, 5, 0, 0)) + test(DT(2012, 2, 20, 0, 0), DT(2012, 3, 12, 0, 0)) + test(DT(2012, 2, 20, 1, 0), DT(2012, 3, 12, 0, 0)) + test(DT(2012, 3, 4, 23, 59, 59), DT(2012, 3, 19, 0, 0)) + test(DT(2012, 3, 5, 0, 0), DT(2012, 3, 26, 0, 0)) + test(DT(2012, 3, 5, 1, 0), DT(2012, 3, 26, 0, 0)) + + test(DT(2012, 10, 14, 23, 59, 59), DT(2012, 10, 29, 0, 0)) + test(DT(2012, 10, 15, 0, 0), DT(2012, 11, 5, 0, 0)) + test(DT(2012, 10, 15, 1, 0), DT(2012, 11, 5, 0, 0)) + test(DT(2012, 10, 28, 23, 59, 59), DT(2012, 11, 12, 0, 0)) + test(DT(2012, 10, 29, 0, 0), DT(2012, 11, 19, 0, 0)) + test(DT(2012, 10, 29, 1, 0), DT(2012, 11, 19, 0, 0)) + + fh.close() + + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when='W6', utc=False, interval=3, + atTime=datetime.time(0, 0, 0)) + + test(DT(2012, 2, 25, 23, 59, 59), DT(2012, 3, 11, 0, 0)) + test(DT(2012, 2, 26, 0, 0), DT(2012, 3, 18, 0, 0)) + test(DT(2012, 2, 26, 1, 0), DT(2012, 3, 18, 0, 0)) + test(DT(2012, 3, 10, 23, 59, 59), DT(2012, 3, 25, 0, 0)) + test(DT(2012, 3, 11, 0, 0), DT(2012, 4, 1, 0, 0)) + test(DT(2012, 3, 11, 1, 0), DT(2012, 4, 1, 0, 0)) + + test(DT(2012, 10, 20, 23, 59, 59), DT(2012, 11, 4, 0, 0)) + test(DT(2012, 10, 21, 0, 0), DT(2012, 11, 11, 0, 0)) + test(DT(2012, 10, 21, 1, 0), DT(2012, 11, 11, 0, 0)) + test(DT(2012, 11, 3, 23, 59, 59), DT(2012, 11, 18, 0, 0)) + test(DT(2012, 11, 4, 0, 0), DT(2012, 11, 25, 0, 0)) + test(DT(2012, 11, 4, 1, 0), DT(2012, 11, 25, 0, 0)) + + fh.close() + + fh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when='W6', utc=False, interval=3, + atTime=datetime.time(12, 0, 0)) + + test(DT(2012, 2, 18, 11, 59, 59), DT(2012, 3, 4, 12, 0)) + test(DT(2012, 2, 19, 12, 0), DT(2012, 3, 11, 12, 0)) + test(DT(2012, 2, 19, 13, 0), DT(2012, 3, 11, 12, 0)) + test(DT(2012, 3, 4, 11, 59, 59), DT(2012, 3, 18, 12, 0)) + test(DT(2012, 3, 4, 12, 0), DT(2012, 3, 25, 12, 0)) + test(DT(2012, 3, 4, 13, 0), DT(2012, 3, 25, 12, 0)) + + test(DT(2012, 10, 14, 11, 59, 59), DT(2012, 10, 28, 12, 0)) + test(DT(2012, 10, 14, 12, 0), DT(2012, 11, 4, 12, 0)) + test(DT(2012, 10, 14, 13, 0), DT(2012, 11, 4, 12, 0)) + test(DT(2012, 10, 28, 11, 59, 59), DT(2012, 11, 11, 12, 0)) + test(DT(2012, 10, 28, 12, 0), DT(2012, 11, 18, 12, 0)) + test(DT(2012, 10, 28, 13, 0), DT(2012, 11, 18, 12, 0)) + + fh.close() + + def secs(**kw): return datetime.timedelta(**kw) // datetime.timedelta(seconds=1) @@ -6242,40 +6711,49 @@ def secs(**kw): # current time (epoch start) is a Thursday, W0 means Monday ('W0', secs(days=4, hours=24)), ): - def test_compute_rollover(self, when=when, exp=exp): - rh = logging.handlers.TimedRotatingFileHandler( - self.fn, encoding="utf-8", when=when, interval=1, backupCount=0, utc=True) - currentTime = 0.0 - actual = rh.computeRollover(currentTime) - if exp != actual: - # Failures occur on some systems for MIDNIGHT and W0. - # Print detailed calculation for MIDNIGHT so we can try to see - # what's going on - if when == 'MIDNIGHT': - try: - if rh.utc: - t = time.gmtime(currentTime) - else: - t = time.localtime(currentTime) - currentHour = t[3] - currentMinute = t[4] - currentSecond = t[5] - # r is the number of seconds left between now and midnight - r = logging.handlers._MIDNIGHT - ((currentHour * 60 + - currentMinute) * 60 + - currentSecond) - result = currentTime + r - print('t: %s (%s)' % (t, rh.utc), file=sys.stderr) - print('currentHour: %s' % currentHour, file=sys.stderr) - print('currentMinute: %s' % currentMinute, file=sys.stderr) - print('currentSecond: %s' % currentSecond, file=sys.stderr) - print('r: %s' % r, file=sys.stderr) - print('result: %s' % result, file=sys.stderr) - except Exception as e: - print('exception in diagnostic code: %s' % e, file=sys.stderr) - self.assertEqual(exp, actual) - rh.close() - setattr(TimedRotatingFileHandlerTest, "test_compute_rollover_%s" % when, test_compute_rollover) + for interval in 1, 3: + def test_compute_rollover(self, when=when, interval=interval, exp=exp): + rh = logging.handlers.TimedRotatingFileHandler( + self.fn, encoding="utf-8", when=when, interval=interval, backupCount=0, utc=True) + currentTime = 0.0 + actual = rh.computeRollover(currentTime) + if when.startswith('W'): + exp += secs(days=7*(interval-1)) + else: + exp *= interval + if exp != actual: + # Failures occur on some systems for MIDNIGHT and W0. + # Print detailed calculation for MIDNIGHT so we can try to see + # what's going on + if when == 'MIDNIGHT': + try: + if rh.utc: + t = time.gmtime(currentTime) + else: + t = time.localtime(currentTime) + currentHour = t[3] + currentMinute = t[4] + currentSecond = t[5] + # r is the number of seconds left between now and midnight + r = logging.handlers._MIDNIGHT - ((currentHour * 60 + + currentMinute) * 60 + + currentSecond) + result = currentTime + r + print('t: %s (%s)' % (t, rh.utc), file=sys.stderr) + print('currentHour: %s' % currentHour, file=sys.stderr) + print('currentMinute: %s' % currentMinute, file=sys.stderr) + print('currentSecond: %s' % currentSecond, file=sys.stderr) + print('r: %s' % r, file=sys.stderr) + print('result: %s' % result, file=sys.stderr) + except Exception as e: + print('exception in diagnostic code: %s' % e, file=sys.stderr) + self.assertEqual(exp, actual) + rh.close() + name = "test_compute_rollover_%s" % when + if interval > 1: + name += "_interval" + test_compute_rollover.__name__ = name + setattr(TimedRotatingFileHandlerTest, name, test_compute_rollover) @unittest.skipUnless(win32evtlog, 'win32evtlog/win32evtlogutil/pywintypes required for this test.') diff --git a/Lib/test/test_lzma.py b/Lib/test/test_lzma.py index 65e6488c5d7b106..db290e139327e02 100644 --- a/Lib/test/test_lzma.py +++ b/Lib/test/test_lzma.py @@ -2,7 +2,6 @@ import array from io import BytesIO, UnsupportedOperation, DEFAULT_BUFFER_SIZE import os -import pathlib import pickle import random import sys @@ -12,7 +11,7 @@ from test.support import _4G, bigmemtest from test.support.import_helper import import_module from test.support.os_helper import ( - TESTFN, unlink + TESTFN, unlink, FakePath ) lzma = import_module("lzma") @@ -548,7 +547,7 @@ def test_init(self): pass def test_init_with_PathLike_filename(self): - filename = pathlib.Path(TESTFN) + filename = FakePath(TESTFN) with TempFile(filename, COMPRESSED_XZ): with LZMAFile(filename) as f: self.assertEqual(f.read(), INPUT) @@ -585,11 +584,10 @@ def test_init_with_x_mode(self): self.addCleanup(unlink, TESTFN) for mode in ("x", "xb"): unlink(TESTFN) - with LZMAFile(TESTFN, mode): + with LZMAFile(TESTFN, mode) as f: pass with self.assertRaises(FileExistsError): - with LZMAFile(TESTFN, mode): - pass + LZMAFile(TESTFN, mode) def test_init_bad_mode(self): with self.assertRaises(ValueError): @@ -867,17 +865,59 @@ def test_read_from_file(self): with LZMAFile(TESTFN) as f: self.assertEqual(f.read(), INPUT) self.assertEqual(f.read(), b"") + self.assertIsInstance(f.fileno(), int) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) def test_read_from_file_with_bytes_filename(self): - try: - bytes_filename = TESTFN.encode("ascii") - except UnicodeEncodeError: - self.skipTest("Temporary file name needs to be ASCII") + bytes_filename = os.fsencode(TESTFN) with TempFile(TESTFN, COMPRESSED_XZ): with LZMAFile(bytes_filename) as f: self.assertEqual(f.read(), INPUT) self.assertEqual(f.read(), b"") + def test_read_from_fileobj(self): + with TempFile(TESTFN, COMPRESSED_XZ): + with open(TESTFN, 'rb') as raw: + with LZMAFile(raw) as f: + self.assertEqual(f.read(), INPUT) + self.assertEqual(f.read(), b"") + self.assertEqual(f.fileno(), raw.fileno()) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + def test_read_from_fileobj_with_int_name(self): + with TempFile(TESTFN, COMPRESSED_XZ): + fd = os.open(TESTFN, os.O_RDONLY) + with open(fd, 'rb') as raw: + with LZMAFile(raw) as f: + self.assertEqual(f.read(), INPUT) + self.assertEqual(f.read(), b"") + self.assertEqual(f.fileno(), raw.fileno()) + self.assertIs(f.readable(), True) + self.assertIs(f.writable(), False) + self.assertIs(f.seekable(), True) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + def test_read_incomplete(self): with LZMAFile(BytesIO(COMPRESSED_XZ[:128])) as f: self.assertRaises(EOFError, f.read) @@ -1051,6 +1091,17 @@ def test_write_to_file(self): try: with LZMAFile(TESTFN, "w") as f: f.write(INPUT) + self.assertIsInstance(f.fileno(), int) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), False) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + expected = lzma.compress(INPUT) with open(TESTFN, "rb") as f: self.assertEqual(f.read(), expected) @@ -1058,10 +1109,7 @@ def test_write_to_file(self): unlink(TESTFN) def test_write_to_file_with_bytes_filename(self): - try: - bytes_filename = TESTFN.encode("ascii") - except UnicodeEncodeError: - self.skipTest("Temporary file name needs to be ASCII") + bytes_filename = os.fsencode(TESTFN) try: with LZMAFile(bytes_filename, "w") as f: f.write(INPUT) @@ -1071,6 +1119,51 @@ def test_write_to_file_with_bytes_filename(self): finally: unlink(TESTFN) + def test_write_to_fileobj(self): + try: + with open(TESTFN, "wb") as raw: + with LZMAFile(raw, "w") as f: + f.write(INPUT) + self.assertEqual(f.fileno(), raw.fileno()) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), False) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + expected = lzma.compress(INPUT) + with open(TESTFN, "rb") as f: + self.assertEqual(f.read(), expected) + finally: + unlink(TESTFN) + + def test_write_to_fileobj_with_int_name(self): + try: + fd = os.open(TESTFN, os.O_WRONLY | os.O_CREAT | os.O_TRUNC) + with open(fd, 'wb') as raw: + with LZMAFile(raw, "w") as f: + f.write(INPUT) + self.assertEqual(f.fileno(), raw.fileno()) + self.assertIs(f.readable(), False) + self.assertIs(f.writable(), True) + self.assertIs(f.seekable(), False) + self.assertIs(f.closed, False) + self.assertIs(f.closed, True) + self.assertRaises(ValueError, f.fileno) + self.assertRaises(ValueError, f.readable) + self.assertRaises(ValueError, f.writable) + self.assertRaises(ValueError, f.seekable) + + expected = lzma.compress(INPUT) + with open(TESTFN, "rb") as f: + self.assertEqual(f.read(), expected) + finally: + unlink(TESTFN) + def test_write_append_to_file(self): part1 = INPUT[:1024] part2 = INPUT[1024:1536] @@ -1276,7 +1369,7 @@ def test_filename(self): self.assertEqual(f.read(), INPUT * 2) def test_with_pathlike_filename(self): - filename = pathlib.Path(TESTFN) + filename = FakePath(TESTFN) with TempFile(filename): with lzma.open(filename, "wb") as f: f.write(INPUT) diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py index d4628f91daf7e85..bb24d5db1f9ed4f 100644 --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -702,8 +702,7 @@ def _check_basics(self, factory=None): self.assertEqual(self._box._factory, factory) for subdir in '', 'tmp', 'new', 'cur': path = os.path.join(self._path, subdir) - mode = os.stat(path)[stat.ST_MODE] - self.assertTrue(stat.S_ISDIR(mode), "Not a directory: '%s'" % path) + self.assertTrue(os.path.isdir(path), f"Not a directory: {path!r}") def test_list_folders(self): # List folders diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index ad382fc2b598914..aaa3b16d33fb7dc 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -2613,6 +2613,244 @@ def test_fractions(self): self.assertAllNotClose(fraction_examples, rel_tol=1e-9) +class FMATests(unittest.TestCase): + """ Tests for math.fma. """ + + def test_fma_nan_results(self): + # Selected representative values. + values = [ + -math.inf, -1e300, -2.3, -1e-300, -0.0, + 0.0, 1e-300, 2.3, 1e300, math.inf, math.nan + ] + + # If any input is a NaN, the result should be a NaN, too. + for a, b in itertools.product(values, repeat=2): + self.assertIsNaN(math.fma(math.nan, a, b)) + self.assertIsNaN(math.fma(a, math.nan, b)) + self.assertIsNaN(math.fma(a, b, math.nan)) + + def test_fma_infinities(self): + # Cases involving infinite inputs or results. + positives = [1e-300, 2.3, 1e300, math.inf] + finites = [-1e300, -2.3, -1e-300, -0.0, 0.0, 1e-300, 2.3, 1e300] + non_nans = [-math.inf, -2.3, -0.0, 0.0, 2.3, math.inf] + + # ValueError due to inf * 0 computation. + for c in non_nans: + for infinity in [math.inf, -math.inf]: + for zero in [0.0, -0.0]: + with self.assertRaises(ValueError): + math.fma(infinity, zero, c) + with self.assertRaises(ValueError): + math.fma(zero, infinity, c) + + # ValueError when a*b and c both infinite of opposite signs. + for b in positives: + with self.assertRaises(ValueError): + math.fma(math.inf, b, -math.inf) + with self.assertRaises(ValueError): + math.fma(math.inf, -b, math.inf) + with self.assertRaises(ValueError): + math.fma(-math.inf, -b, -math.inf) + with self.assertRaises(ValueError): + math.fma(-math.inf, b, math.inf) + with self.assertRaises(ValueError): + math.fma(b, math.inf, -math.inf) + with self.assertRaises(ValueError): + math.fma(-b, math.inf, math.inf) + with self.assertRaises(ValueError): + math.fma(-b, -math.inf, -math.inf) + with self.assertRaises(ValueError): + math.fma(b, -math.inf, math.inf) + + # Infinite result when a*b and c both infinite of the same sign. + for b in positives: + self.assertEqual(math.fma(math.inf, b, math.inf), math.inf) + self.assertEqual(math.fma(math.inf, -b, -math.inf), -math.inf) + self.assertEqual(math.fma(-math.inf, -b, math.inf), math.inf) + self.assertEqual(math.fma(-math.inf, b, -math.inf), -math.inf) + self.assertEqual(math.fma(b, math.inf, math.inf), math.inf) + self.assertEqual(math.fma(-b, math.inf, -math.inf), -math.inf) + self.assertEqual(math.fma(-b, -math.inf, math.inf), math.inf) + self.assertEqual(math.fma(b, -math.inf, -math.inf), -math.inf) + + # Infinite result when a*b finite, c infinite. + for a, b in itertools.product(finites, finites): + self.assertEqual(math.fma(a, b, math.inf), math.inf) + self.assertEqual(math.fma(a, b, -math.inf), -math.inf) + + # Infinite result when a*b infinite, c finite. + for b, c in itertools.product(positives, finites): + self.assertEqual(math.fma(math.inf, b, c), math.inf) + self.assertEqual(math.fma(-math.inf, b, c), -math.inf) + self.assertEqual(math.fma(-math.inf, -b, c), math.inf) + self.assertEqual(math.fma(math.inf, -b, c), -math.inf) + + self.assertEqual(math.fma(b, math.inf, c), math.inf) + self.assertEqual(math.fma(b, -math.inf, c), -math.inf) + self.assertEqual(math.fma(-b, -math.inf, c), math.inf) + self.assertEqual(math.fma(-b, math.inf, c), -math.inf) + + # gh-73468: On WASI and FreeBSD, libc fma() doesn't implement IEE 754-2008 + # properly: it doesn't use the right sign when the result is zero. + @unittest.skipIf(support.is_wasi, + "WASI fma() doesn't implement IEE 754-2008 properly") + @unittest.skipIf(sys.platform.startswith('freebsd'), + "FreeBSD fma() doesn't implement IEE 754-2008 properly") + def test_fma_zero_result(self): + nonnegative_finites = [0.0, 1e-300, 2.3, 1e300] + + # Zero results from exact zero inputs. + for b in nonnegative_finites: + self.assertIsPositiveZero(math.fma(0.0, b, 0.0)) + self.assertIsPositiveZero(math.fma(0.0, b, -0.0)) + self.assertIsNegativeZero(math.fma(0.0, -b, -0.0)) + self.assertIsPositiveZero(math.fma(0.0, -b, 0.0)) + self.assertIsPositiveZero(math.fma(-0.0, -b, 0.0)) + self.assertIsPositiveZero(math.fma(-0.0, -b, -0.0)) + self.assertIsNegativeZero(math.fma(-0.0, b, -0.0)) + self.assertIsPositiveZero(math.fma(-0.0, b, 0.0)) + + self.assertIsPositiveZero(math.fma(b, 0.0, 0.0)) + self.assertIsPositiveZero(math.fma(b, 0.0, -0.0)) + self.assertIsNegativeZero(math.fma(-b, 0.0, -0.0)) + self.assertIsPositiveZero(math.fma(-b, 0.0, 0.0)) + self.assertIsPositiveZero(math.fma(-b, -0.0, 0.0)) + self.assertIsPositiveZero(math.fma(-b, -0.0, -0.0)) + self.assertIsNegativeZero(math.fma(b, -0.0, -0.0)) + self.assertIsPositiveZero(math.fma(b, -0.0, 0.0)) + + # Exact zero result from nonzero inputs. + self.assertIsPositiveZero(math.fma(2.0, 2.0, -4.0)) + self.assertIsPositiveZero(math.fma(2.0, -2.0, 4.0)) + self.assertIsPositiveZero(math.fma(-2.0, -2.0, -4.0)) + self.assertIsPositiveZero(math.fma(-2.0, 2.0, 4.0)) + + # Underflow to zero. + tiny = 1e-300 + self.assertIsPositiveZero(math.fma(tiny, tiny, 0.0)) + self.assertIsNegativeZero(math.fma(tiny, -tiny, 0.0)) + self.assertIsPositiveZero(math.fma(-tiny, -tiny, 0.0)) + self.assertIsNegativeZero(math.fma(-tiny, tiny, 0.0)) + self.assertIsPositiveZero(math.fma(tiny, tiny, -0.0)) + self.assertIsNegativeZero(math.fma(tiny, -tiny, -0.0)) + self.assertIsPositiveZero(math.fma(-tiny, -tiny, -0.0)) + self.assertIsNegativeZero(math.fma(-tiny, tiny, -0.0)) + + # Corner case where rounding the multiplication would + # give the wrong result. + x = float.fromhex('0x1p-500') + y = float.fromhex('0x1p-550') + z = float.fromhex('0x1p-1000') + self.assertIsNegativeZero(math.fma(x-y, x+y, -z)) + self.assertIsPositiveZero(math.fma(y-x, x+y, z)) + self.assertIsNegativeZero(math.fma(y-x, -(x+y), -z)) + self.assertIsPositiveZero(math.fma(x-y, -(x+y), z)) + + def test_fma_overflow(self): + a = b = float.fromhex('0x1p512') + c = float.fromhex('0x1p1023') + # Overflow from multiplication. + with self.assertRaises(OverflowError): + math.fma(a, b, 0.0) + self.assertEqual(math.fma(a, b/2.0, 0.0), c) + # Overflow from the addition. + with self.assertRaises(OverflowError): + math.fma(a, b/2.0, c) + # No overflow, even though a*b overflows a float. + self.assertEqual(math.fma(a, b, -c), c) + + # Extreme case: a * b is exactly at the overflow boundary, so the + # tiniest offset makes a difference between overflow and a finite + # result. + a = float.fromhex('0x1.ffffffc000000p+511') + b = float.fromhex('0x1.0000002000000p+512') + c = float.fromhex('0x0.0000000000001p-1022') + with self.assertRaises(OverflowError): + math.fma(a, b, 0.0) + with self.assertRaises(OverflowError): + math.fma(a, b, c) + self.assertEqual(math.fma(a, b, -c), + float.fromhex('0x1.fffffffffffffp+1023')) + + # Another extreme case: here a*b is about as large as possible subject + # to math.fma(a, b, c) being finite. + a = float.fromhex('0x1.ae565943785f9p+512') + b = float.fromhex('0x1.3094665de9db8p+512') + c = float.fromhex('0x1.fffffffffffffp+1023') + self.assertEqual(math.fma(a, b, -c), c) + + def test_fma_single_round(self): + a = float.fromhex('0x1p-50') + self.assertEqual(math.fma(a - 1.0, a + 1.0, 1.0), a*a) + + def test_random(self): + # A collection of randomly generated inputs for which the naive FMA + # (with two rounds) gives a different result from a singly-rounded FMA. + + # tuples (a, b, c, expected) + test_values = [ + ('0x1.694adde428b44p-1', '0x1.371b0d64caed7p-1', + '0x1.f347e7b8deab8p-4', '0x1.19f10da56c8adp-1'), + ('0x1.605401ccc6ad6p-2', '0x1.ce3a40bf56640p-2', + '0x1.96e3bf7bf2e20p-2', '0x1.1af6d8aa83101p-1'), + ('0x1.e5abd653a67d4p-2', '0x1.a2e400209b3e6p-1', + '0x1.a90051422ce13p-1', '0x1.37d68cc8c0fbbp+0'), + ('0x1.f94e8efd54700p-2', '0x1.123065c812cebp-1', + '0x1.458f86fb6ccd0p-1', '0x1.ccdcee26a3ff3p-1'), + ('0x1.bd926f1eedc96p-1', '0x1.eee9ca68c5740p-1', + '0x1.960c703eb3298p-2', '0x1.3cdcfb4fdb007p+0'), + ('0x1.27348350fbccdp-1', '0x1.3b073914a53f1p-1', + '0x1.e300da5c2b4cbp-1', '0x1.4c51e9a3c4e29p+0'), + ('0x1.2774f00b3497bp-1', '0x1.7038ec336bff0p-2', + '0x1.2f6f2ccc3576bp-1', '0x1.99ad9f9c2688bp-1'), + ('0x1.51d5a99300e5cp-1', '0x1.5cd74abd445a1p-1', + '0x1.8880ab0bbe530p-1', '0x1.3756f96b91129p+0'), + ('0x1.73cb965b821b8p-2', '0x1.218fd3d8d5371p-1', + '0x1.d1ea966a1f758p-2', '0x1.5217b8fd90119p-1'), + ('0x1.4aa98e890b046p-1', '0x1.954d85dff1041p-1', + '0x1.122b59317ebdfp-1', '0x1.0bf644b340cc5p+0'), + ('0x1.e28f29e44750fp-1', '0x1.4bcc4fdcd18fep-1', + '0x1.fd47f81298259p-1', '0x1.9b000afbc9995p+0'), + ('0x1.d2e850717fe78p-3', '0x1.1dd7531c303afp-1', + '0x1.e0869746a2fc2p-2', '0x1.316df6eb26439p-1'), + ('0x1.cf89c75ee6fbap-2', '0x1.b23decdc66825p-1', + '0x1.3d1fe76ac6168p-1', '0x1.00d8ea4c12abbp+0'), + ('0x1.3265ae6f05572p-2', '0x1.16d7ec285f7a2p-1', + '0x1.0b8405b3827fbp-1', '0x1.5ef33c118a001p-1'), + ('0x1.c4d1bf55ec1a5p-1', '0x1.bc59618459e12p-2', + '0x1.ce5b73dc1773dp-1', '0x1.496cf6164f99bp+0'), + ('0x1.d350026ac3946p-1', '0x1.9a234e149a68cp-2', + '0x1.f5467b1911fd6p-2', '0x1.b5cee3225caa5p-1'), + ] + for a_hex, b_hex, c_hex, expected_hex in test_values: + a = float.fromhex(a_hex) + b = float.fromhex(b_hex) + c = float.fromhex(c_hex) + expected = float.fromhex(expected_hex) + self.assertEqual(math.fma(a, b, c), expected) + self.assertEqual(math.fma(b, a, c), expected) + + # Custom assertions. + def assertIsNaN(self, value): + self.assertTrue( + math.isnan(value), + msg="Expected a NaN, got {!r}".format(value) + ) + + def assertIsPositiveZero(self, value): + self.assertTrue( + value == 0 and math.copysign(1, value) > 0, + msg="Expected a positive zero, got {!r}".format(value) + ) + + def assertIsNegativeZero(self, value): + self.assertTrue( + value == 0 and math.copysign(1, value) < 0, + msg="Expected a negative zero, got {!r}".format(value) + ) + + def load_tests(loader, tests, pattern): from doctest import DocFileSuite tests.addTest(DocFileSuite(os.path.join("mathdata", "ieee754.txt"))) diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index ac759757d24659c..ee86227e026b678 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -837,7 +837,7 @@ def test_flush_return_value(self): mm.write(b'python') result = mm.flush() self.assertIsNone(result) - if sys.platform.startswith('linux'): + if sys.platform.startswith(('linux', 'android')): # 'offset' must be a multiple of mmap.PAGESIZE on Linux. # See bpo-34754 for details. self.assertRaises(OSError, mm.flush, 1, len(b'python')) diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index 2fd822036bcff5e..58441ef8b82fd05 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -9,6 +9,8 @@ import types import unittest import asyncio +from test import support +from test.support import requires_specialization, script_helper PAIR = (0,1) @@ -815,6 +817,9 @@ def func1(): self.check_events(func1, [("raise", KeyError)]) + # gh-116090: This test doesn't really require specialization, but running + # it without specialization exposes a monitoring bug. + @requires_specialization def test_implicit_stop_iteration(self): def gen(): @@ -963,6 +968,7 @@ def func(): ) self.assertEqual(events[0], ("throw", IndexError)) + @requires_specialization def test_no_unwind_for_shim_frame(self): class B: @@ -1801,6 +1807,24 @@ def test_gh108976(self): sys.monitoring.set_events(0, E.LINE | E.INSTRUCTION) sys.monitoring.set_events(0, 0) + def test_call_function_ex(self): + def f(a=1, b=2): + return a + b + args = (1, 2) + empty_args = [] + + call_data = [] + sys.monitoring.use_tool_id(0, "test") + self.addCleanup(sys.monitoring.free_tool_id, 0) + sys.monitoring.set_events(0, 0) + sys.monitoring.register_callback(0, E.CALL, lambda code, offset, callable, arg0: call_data.append((callable, arg0))) + sys.monitoring.set_events(0, E.CALL) + f(*args) + f(*empty_args) + sys.monitoring.set_events(0, 0) + self.assertEqual(call_data[0], (f, 1)) + self.assertEqual(call_data[1], (f, sys.monitoring.MISSING)) + class TestOptimizer(MonitoringTestBase, unittest.TestCase): @@ -1853,3 +1877,12 @@ def test_func(recorder): sys.monitoring.register_callback(TEST_TOOL, E.LINE, None) sys.monitoring.set_events(TEST_TOOL, 0) self.assertGreater(len(events), 250) + +class TestMonitoringAtShutdown(unittest.TestCase): + + def test_monitoring_live_at_shutdown(self): + # gh-115832: An object destructor running during the final GC of + # interpreter shutdown triggered an infinite loop in the + # instrumentation code. + script = support.findfile("_test_monitoring_shutdown.py") + script_helper.run_test_script(script) diff --git a/Lib/test/test_multibytecodec.py b/Lib/test/test_multibytecodec.py index 6451df146969334..ccdf3a6cdc0dc77 100644 --- a/Lib/test/test_multibytecodec.py +++ b/Lib/test/test_multibytecodec.py @@ -303,7 +303,7 @@ def test_setstate_validates_input(self): self.assertRaises(TypeError, decoder.setstate, 123) self.assertRaises(TypeError, decoder.setstate, ("invalid", 0)) self.assertRaises(TypeError, decoder.setstate, (b"1234", "invalid")) - self.assertRaises(UnicodeError, decoder.setstate, (b"123456789", 0)) + self.assertRaises(UnicodeDecodeError, decoder.setstate, (b"123456789", 0)) class Test_StreamReader(unittest.TestCase): def test_bug1728403(self): diff --git a/Lib/test/test_named_expressions.py b/Lib/test/test_named_expressions.py index f2017bdffcf9680..cf44080670dc2e6 100644 --- a/Lib/test/test_named_expressions.py +++ b/Lib/test/test_named_expressions.py @@ -298,6 +298,72 @@ def test_named_expression_invalid_set_comprehension_iterable_expression(self): with self.assertRaisesRegex(SyntaxError, msg): exec(f"lambda: {code}", {}) # Function scope + def test_named_expression_invalid_rebinding_dict_comprehension_iteration_variable(self): + cases = [ + ("Key reuse", 'i', "{(i := 0): 1 for i in range(5)}"), + ("Value reuse", 'i', "{1: (i := 0) for i in range(5)}"), + ("Both reuse", 'i', "{(i := 0): (i := 0) for i in range(5)}"), + ("Nested reuse", 'j', "{{(j := 0): 1 for i in range(5)} for j in range(5)}"), + ("Reuse inner loop target", 'j', "{(j := 0): 1 for i in range(5) for j in range(5)}"), + ("Unpacking key reuse", 'i', "{(i := 0): 1 for i, j in {(0, 1)}}"), + ("Unpacking value reuse", 'i', "{1: (i := 0) for i, j in {(0, 1)}}"), + ("Reuse in loop condition", 'i', "{i+1: 1 for i in range(5) if (i := 0)}"), + ("Unreachable reuse", 'i', "{(False or (i:=0)): 1 for i in range(5)}"), + ("Unreachable nested reuse", 'i', + "{i: j for i in range(5) for j in range(5) if True or (i:=10)}"), + # Regression tests from https://github.com/python/cpython/issues/87447 + ("Complex expression: a", "a", + "{(a := 1): 1 for a, (*b, c[d+e::f(g)], h.i) in j}"), + ("Complex expression: b", "b", + "{(b := 1): 1 for a, (*b, c[d+e::f(g)], h.i) in j}"), + ] + for case, target, code in cases: + msg = f"assignment expression cannot rebind comprehension iteration variable '{target}'" + with self.subTest(case=case): + with self.assertRaisesRegex(SyntaxError, msg): + exec(code, {}) # Module scope + with self.assertRaisesRegex(SyntaxError, msg): + exec(code, {}, {}) # Class scope + with self.assertRaisesRegex(SyntaxError, msg): + exec(f"lambda: {code}", {}) # Function scope + + def test_named_expression_invalid_rebinding_dict_comprehension_inner_loop(self): + cases = [ + ("Inner reuse", 'j', "{i: 1 for i in range(5) if (j := 0) for j in range(5)}"), + ("Inner unpacking reuse", 'j', "{i: 1 for i in range(5) if (j := 0) for j, k in {(0, 1)}}"), + ] + for case, target, code in cases: + msg = f"comprehension inner loop cannot rebind assignment expression target '{target}'" + with self.subTest(case=case): + with self.assertRaisesRegex(SyntaxError, msg): + exec(code, {}) # Module scope + with self.assertRaisesRegex(SyntaxError, msg): + exec(code, {}, {}) # Class scope + with self.assertRaisesRegex(SyntaxError, msg): + exec(f"lambda: {code}", {}) # Function scope + + def test_named_expression_invalid_dict_comprehension_iterable_expression(self): + cases = [ + ("Top level", "{i: 1 for i in (i := range(5))}"), + ("Inside tuple", "{i: 1 for i in (2, 3, i := range(5))}"), + ("Inside list", "{i: 1 for i in [2, 3, i := range(5)]}"), + ("Different name", "{i: 1 for i in (j := range(5))}"), + ("Lambda expression", "{i: 1 for i in (lambda:(j := range(5)))()}"), + ("Inner loop", "{i: 1 for i in range(5) for j in (i := range(5))}"), + ("Nested comprehension", "{i: 1 for i in {j: 2 for j in (k := range(5))}}"), + ("Nested comprehension condition", "{i: 1 for i in {j: 2 for j in range(5) if (j := True)}}"), + ("Nested comprehension body", "{i: 1 for i in {(j := True) for j in range(5)}}"), + ] + msg = "assignment expression cannot be used in a comprehension iterable expression" + for case, code in cases: + with self.subTest(case=case): + with self.assertRaisesRegex(SyntaxError, msg): + exec(code, {}) # Module scope + with self.assertRaisesRegex(SyntaxError, msg): + exec(code, {}, {}) # Class scope + with self.assertRaisesRegex(SyntaxError, msg): + exec(f"lambda: {code}", {}) # Function scope + def test_named_expression_invalid_mangled_class_variables(self): code = """class Foo: def bar(self): @@ -361,7 +427,7 @@ def test_named_expression_assignment_09(self): def test_named_expression_assignment_10(self): if (match := 10) == 10: - pass + self.assertEqual(match, 10) else: self.fail("variable was not assigned using named expression") def test_named_expression_assignment_11(self): @@ -403,7 +469,7 @@ def test_named_expression_assignment_14(self): def test_named_expression_assignment_15(self): while a := False: - pass # This will not run + self.fail("While body executed") # This will not run self.assertEqual(a, False) diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 2b2783d57be8f41..5fb4b815c95d07e 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -4,7 +4,7 @@ import threading import types import unittest -from test.support import threading_helper, check_impl_detail +from test.support import threading_helper, check_impl_detail, requires_specialization # Skip this module on other interpreters, it is cpython specific: if check_impl_detail(cpython=False): @@ -506,6 +506,7 @@ def f(x, y): @threading_helper.requires_working_threading() +@requires_specialization class TestRacesDoNotCrash(unittest.TestCase): # Careful with these. Bigger numbers have a higher chance of catching bugs, # but you can also burn through a *ton* of type/dict/function versions: @@ -1021,6 +1022,7 @@ def write(items): class C: pass +@requires_specialization class TestInstanceDict(unittest.TestCase): def setUp(self): diff --git a/Lib/test/test_optimizer.py b/Lib/test/test_optimizer.py index dfea8be3c6956f3..899a45073173347 100644 --- a/Lib/test/test_optimizer.py +++ b/Lib/test/test_optimizer.py @@ -77,5 +77,12 @@ def func(x=0): _testinternalcapi.get_rare_event_counters()["func_modification"] ) + +class TestOptimizerSymbols(unittest.TestCase): + + def test_optimizer_symbols(self): + _testinternalcapi.uop_symbols_test() + + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 2372ac4c21efd9f..4c157842d95523b 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -34,7 +34,7 @@ from test.support import import_helper from test.support import os_helper from test.support import socket_helper -from test.support import set_recursion_limit +from test.support import infinite_recursion from test.support import warnings_helper from platform import win32_is_iot @@ -1298,6 +1298,7 @@ def test_ror_operator(self): class WalkTests(unittest.TestCase): """Tests for os.walk().""" + is_fwalk = False # Wrapper to hide minor differences between os.walk and os.fwalk # to tests both functions with the same code base @@ -1332,14 +1333,14 @@ def setUp(self): self.sub11_path = join(self.sub1_path, "SUB11") sub2_path = join(self.walk_path, "SUB2") sub21_path = join(sub2_path, "SUB21") - tmp1_path = join(self.walk_path, "tmp1") + self.tmp1_path = join(self.walk_path, "tmp1") tmp2_path = join(self.sub1_path, "tmp2") tmp3_path = join(sub2_path, "tmp3") tmp5_path = join(sub21_path, "tmp3") self.link_path = join(sub2_path, "link") t2_path = join(os_helper.TESTFN, "TEST2") tmp4_path = join(os_helper.TESTFN, "TEST2", "tmp4") - broken_link_path = join(sub2_path, "broken_link") + self.broken_link_path = join(sub2_path, "broken_link") broken_link2_path = join(sub2_path, "broken_link2") broken_link3_path = join(sub2_path, "broken_link3") @@ -1349,13 +1350,13 @@ def setUp(self): os.makedirs(sub21_path) os.makedirs(t2_path) - for path in tmp1_path, tmp2_path, tmp3_path, tmp4_path, tmp5_path: + for path in self.tmp1_path, tmp2_path, tmp3_path, tmp4_path, tmp5_path: with open(path, "x", encoding='utf-8') as f: f.write("I'm " + path + " and proud of it. Blame test_os.\n") if os_helper.can_symlink(): os.symlink(os.path.abspath(t2_path), self.link_path) - os.symlink('broken', broken_link_path, True) + os.symlink('broken', self.broken_link_path, True) os.symlink(join('tmp3', 'broken'), broken_link2_path, True) os.symlink(join('SUB21', 'tmp5'), broken_link3_path, True) self.sub2_tree = (sub2_path, ["SUB21", "link"], @@ -1451,6 +1452,11 @@ def test_walk_symlink(self): else: self.fail("Didn't follow symlink with followlinks=True") + walk_it = self.walk(self.broken_link_path, follow_symlinks=True) + if self.is_fwalk: + self.assertRaises(FileNotFoundError, next, walk_it) + self.assertRaises(StopIteration, next, walk_it) + def test_walk_bad_dir(self): # Walk top-down. errors = [] @@ -1472,6 +1478,73 @@ def test_walk_bad_dir(self): finally: os.rename(path1new, path1) + def test_walk_bad_dir2(self): + walk_it = self.walk('nonexisting') + if self.is_fwalk: + self.assertRaises(FileNotFoundError, next, walk_it) + self.assertRaises(StopIteration, next, walk_it) + + walk_it = self.walk('nonexisting', follow_symlinks=True) + if self.is_fwalk: + self.assertRaises(FileNotFoundError, next, walk_it) + self.assertRaises(StopIteration, next, walk_it) + + walk_it = self.walk(self.tmp1_path) + self.assertRaises(StopIteration, next, walk_it) + + walk_it = self.walk(self.tmp1_path, follow_symlinks=True) + if self.is_fwalk: + self.assertRaises(NotADirectoryError, next, walk_it) + self.assertRaises(StopIteration, next, walk_it) + + @unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()') + @unittest.skipIf(sys.platform == "vxworks", + "fifo requires special path on VxWorks") + def test_walk_named_pipe(self): + path = os_helper.TESTFN + '-pipe' + os.mkfifo(path) + self.addCleanup(os.unlink, path) + + walk_it = self.walk(path) + self.assertRaises(StopIteration, next, walk_it) + + walk_it = self.walk(path, follow_symlinks=True) + if self.is_fwalk: + self.assertRaises(NotADirectoryError, next, walk_it) + self.assertRaises(StopIteration, next, walk_it) + + @unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()') + @unittest.skipIf(sys.platform == "vxworks", + "fifo requires special path on VxWorks") + def test_walk_named_pipe2(self): + path = os_helper.TESTFN + '-dir' + os.mkdir(path) + self.addCleanup(shutil.rmtree, path) + os.mkfifo(os.path.join(path, 'mypipe')) + + errors = [] + walk_it = self.walk(path, onerror=errors.append) + next(walk_it) + self.assertRaises(StopIteration, next, walk_it) + self.assertEqual(errors, []) + + errors = [] + walk_it = self.walk(path, onerror=errors.append) + root, dirs, files = next(walk_it) + self.assertEqual(root, path) + self.assertEqual(dirs, []) + self.assertEqual(files, ['mypipe']) + dirs.extend(files) + files.clear() + if self.is_fwalk: + self.assertRaises(NotADirectoryError, next, walk_it) + self.assertRaises(StopIteration, next, walk_it) + if self.is_fwalk: + self.assertEqual(errors, []) + else: + self.assertEqual(len(errors), 1, errors) + self.assertIsInstance(errors[0], NotADirectoryError) + def test_walk_many_open_files(self): depth = 30 base = os.path.join(os_helper.TESTFN, 'deep') @@ -1496,7 +1569,7 @@ def test_walk_many_open_files(self): def test_walk_above_recursion_limit(self): depth = 50 os.makedirs(os.path.join(self.walk_path, *(['d'] * depth))) - with set_recursion_limit(depth - 5): + with infinite_recursion(depth - 5): all = list(self.walk(self.walk_path)) sub2_path = self.sub2_tree[0] @@ -1537,6 +1610,7 @@ def test_walk_above_recursion_limit(self): @unittest.skipUnless(hasattr(os, 'fwalk'), "Test needs os.fwalk()") class FwalkTests(WalkTests): """Tests for os.fwalk().""" + is_fwalk = True def walk(self, top, **kwargs): for root, dirs, files, root_fd in self.fwalk(top, **kwargs): @@ -1592,6 +1666,9 @@ def test_yields_correct_dir_fd(self): @unittest.skipIf( support.is_emscripten, "Cannot dup stdout on Emscripten" ) + @unittest.skipIf( + support.is_android, "dup return value is unpredictable on Android" + ) def test_fd_leak(self): # Since we're opening a lot of FDs, we must be careful to avoid leaks: # we both check that calling fwalk() a large number of times doesn't @@ -2492,8 +2569,10 @@ def test_listdir(self): # test listdir without arguments current_directory = os.getcwd() try: - os.chdir(os.sep) - self.assertEqual(set(os.listdir()), set(os.listdir(os.sep))) + # The root directory is not readable on Android, so use a directory + # we created ourselves. + os.chdir(self.dir) + self.assertEqual(set(os.listdir()), expected) finally: os.chdir(current_directory) @@ -3528,9 +3607,8 @@ def test_set_get_priority(self): class TestSendfile(unittest.IsolatedAsyncioTestCase): DATA = b"12345abcde" * 16 * 1024 # 160 KiB - SUPPORT_HEADERS_TRAILERS = not sys.platform.startswith("linux") and \ - not sys.platform.startswith("solaris") and \ - not sys.platform.startswith("sunos") + SUPPORT_HEADERS_TRAILERS = ( + not sys.platform.startswith(("linux", "android", "solaris", "sunos"))) requires_headers_trailers = unittest.skipUnless(SUPPORT_HEADERS_TRAILERS, 'requires headers and trailers support') requires_32b = unittest.skipUnless(sys.maxsize < 2**32, @@ -4838,7 +4916,7 @@ def check_entry(self, entry, name, is_dir, is_file, is_symlink): os.name == 'nt') def test_attributes(self): - link = hasattr(os, 'link') + link = os_helper.can_hardlink() symlink = os_helper.can_symlink() dirname = os.path.join(self.path, "dir") @@ -5251,7 +5329,7 @@ def test_fork(self): else: assert_python_ok("-c", code, PYTHONMALLOC="malloc_debug") - @unittest.skipUnless(sys.platform in ("linux", "darwin"), + @unittest.skipUnless(sys.platform in ("linux", "android", "darwin"), "Only Linux and macOS detect this today.") def test_fork_warns_when_non_python_thread_exists(self): code = """if 1: diff --git a/Lib/test/test_pathlib/test_pathlib.py b/Lib/test/test_pathlib/test_pathlib.py index c0dcf314da4bfc3..6509c08d2273463 100644 --- a/Lib/test/test_pathlib/test_pathlib.py +++ b/Lib/test/test_pathlib/test_pathlib.py @@ -15,7 +15,7 @@ from test.support import import_helper from test.support import is_emscripten, is_wasi -from test.support import set_recursion_limit +from test.support import infinite_recursion from test.support import os_helper from test.support.os_helper import TESTFN, FakePath from test.test_pathlib import test_pathlib_abc @@ -796,7 +796,7 @@ def test_rmdir(self): self.assertFileNotFound(p.stat) self.assertFileNotFound(p.unlink) - @unittest.skipUnless(hasattr(os, "link"), "os.link() is not present") + @os_helper.skip_unless_hardlink def test_hardlink_to(self): P = self.cls(self.base) target = P / 'fileA' @@ -1199,7 +1199,7 @@ def test_walk_above_recursion_limit(self): path = base.joinpath(*(['d'] * directory_depth)) path.mkdir(parents=True) - with set_recursion_limit(recursion_limit): + with infinite_recursion(recursion_limit): list(base.walk()) list(base.walk(top_down=False)) @@ -1239,7 +1239,7 @@ def test_glob_above_recursion_limit(self): path = base.joinpath(*(['d'] * directory_depth)) path.mkdir(parents=True) - with set_recursion_limit(recursion_limit): + with infinite_recursion(recursion_limit): list(base.glob('**/')) def test_glob_pathlike(self): diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 1d30deca8f7a1bb..840fb903fd53383 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -512,8 +512,6 @@ def test_full_match_common(self): self.assertFalse(P('a/b/c.py').full_match('**/a/b/c./**')) self.assertFalse(P('a/b/c.py').full_match('/a/b/c.py/**')) self.assertFalse(P('a/b/c.py').full_match('/**/a/b/c.py')) - self.assertRaises(ValueError, P('a').full_match, '**a/b/c') - self.assertRaises(ValueError, P('a').full_match, 'a/b/c**') # Case-sensitive flag self.assertFalse(P('A.py').full_match('a.PY', case_sensitive=True)) self.assertTrue(P('A.py').full_match('a.PY', case_sensitive=False)) @@ -957,6 +955,8 @@ def test_with_stem_empty(self): self.assertEqual(P('/').with_stem('d'), P('/d')) self.assertEqual(P('a/b').with_stem(''), P('a/')) self.assertEqual(P('a/b').with_stem('.'), P('a/.')) + self.assertRaises(ValueError, P('foo.gz').with_stem, '') + self.assertRaises(ValueError, P('/a/b/foo.gz').with_stem, '') def test_with_stem_seps(self): P = self.cls @@ -1842,51 +1842,46 @@ def _check(path, glob, expected): _check(p, "*/dirD/**", ["dirC/dirD/", "dirC/dirD/fileD"]) _check(p, "*/dirD/**/", ["dirC/dirD/"]) - def test_rglob_common(self): - def _check(glob, expected): - self.assertEqual(set(glob), {P(self.base, q) for q in expected}) + def test_rglob_follow_symlinks_none(self): + def _check(path, glob, expected): + actual = set(path.rglob(glob, follow_symlinks=None)) + self.assertEqual(actual, { P(self.base, q) for q in expected }) P = self.cls p = P(self.base) it = p.rglob("fileA") self.assertIsInstance(it, collections.abc.Iterator) - _check(it, ["fileA"]) - _check(p.rglob("fileB"), ["dirB/fileB"]) - _check(p.rglob("**/fileB"), ["dirB/fileB"]) - _check(p.rglob("*/fileA"), []) - if not self.can_symlink: - _check(p.rglob("*/fileB"), ["dirB/fileB"]) - else: - _check(p.rglob("*/fileB"), ["dirB/fileB", "dirB/linkD/fileB", - "linkB/fileB", "dirA/linkC/fileB"]) - _check(p.rglob("file*"), ["fileA", "dirB/fileB", - "dirC/fileC", "dirC/dirD/fileD"]) - if not self.can_symlink: - _check(p.rglob("*/"), [ - "dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/", - ]) - else: - _check(p.rglob("*/"), [ + _check(p, "fileA", ["fileA"]) + _check(p, "fileB", ["dirB/fileB"]) + _check(p, "**/fileB", ["dirB/fileB"]) + _check(p, "*/fileA", []) + + if self.can_symlink: + _check(p, "*/fileB", ["dirB/fileB", "dirB/linkD/fileB", + "linkB/fileB", "dirA/linkC/fileB"]) + _check(p, "*/", [ "dirA/", "dirA/linkC/", "dirB/", "dirB/linkD/", "dirC/", - "dirC/dirD/", "dirE/", "linkB/", - ]) - _check(p.rglob(""), ["", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"]) + "dirC/dirD/", "dirE/", "linkB/"]) + else: + _check(p, "*/fileB", ["dirB/fileB"]) + _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"]) + _check(p, "file*", ["fileA", "dirB/fileB", "dirC/fileC", "dirC/dirD/fileD"]) + _check(p, "", ["", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"]) p = P(self.base, "dirC") - _check(p.rglob("*"), ["dirC/fileC", "dirC/novel.txt", + _check(p, "*", ["dirC/fileC", "dirC/novel.txt", "dirC/dirD", "dirC/dirD/fileD"]) - _check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"]) - _check(p.rglob("**/file*"), ["dirC/fileC", "dirC/dirD/fileD"]) - _check(p.rglob("dir*/**"), ["dirC/dirD/", "dirC/dirD/fileD"]) - _check(p.rglob("dir*/**/"), ["dirC/dirD/"]) - _check(p.rglob("*/*"), ["dirC/dirD/fileD"]) - _check(p.rglob("*/"), ["dirC/dirD/"]) - _check(p.rglob(""), ["dirC/", "dirC/dirD/"]) - _check(p.rglob("**"), [ - "dirC/", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt"]) - _check(p.rglob("**/"), ["dirC/", "dirC/dirD/"]) + _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"]) + _check(p, "**/file*", ["dirC/fileC", "dirC/dirD/fileD"]) + _check(p, "dir*/**", ["dirC/dirD/", "dirC/dirD/fileD"]) + _check(p, "dir*/**/", ["dirC/dirD/"]) + _check(p, "*/*", ["dirC/dirD/fileD"]) + _check(p, "*/", ["dirC/dirD/"]) + _check(p, "", ["dirC/", "dirC/dirD/"]) + _check(p, "**", ["dirC/", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt"]) + _check(p, "**/", ["dirC/", "dirC/dirD/"]) # gh-91616, a re module regression - _check(p.rglob("*.txt"), ["dirC/novel.txt"]) - _check(p.rglob("*.*"), ["dirC/novel.txt"]) + _check(p, "*.txt", ["dirC/novel.txt"]) + _check(p, "*.*", ["dirC/novel.txt"]) @needs_posix def test_rglob_posix(self): @@ -1967,7 +1962,7 @@ def test_rglob_symlink_loop(self): # Don't get fooled by symlink loops (Issue #26012). P = self.cls p = P(self.base) - given = set(p.rglob('*')) + given = set(p.rglob('*', follow_symlinks=None)) expect = {'brokenLink', 'dirA', 'dirA/linkC', 'dirB', 'dirB/fileB', 'dirB/linkD', diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 2b0795cdad707e1..69691e930562bc7 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -2618,13 +2618,29 @@ def _run_pdb(self, pdb_args, commands, def run_pdb_script(self, script, commands, expected_returncode=0, - extra_env=None): + extra_env=None, + pdbrc=None, + remove_home=False): """Run 'script' lines with pdb and the pdb 'commands'.""" filename = 'main.py' with open(filename, 'w') as f: f.write(textwrap.dedent(script)) + + if pdbrc is not None: + with open('.pdbrc', 'w') as f: + f.write(textwrap.dedent(pdbrc)) + self.addCleanup(os_helper.unlink, '.pdbrc') self.addCleanup(os_helper.unlink, filename) - return self._run_pdb([filename], commands, expected_returncode, extra_env) + + homesave = None + if remove_home: + homesave = os.environ.pop('HOME', None) + try: + stdout, stderr = self._run_pdb([filename], commands, expected_returncode, extra_env) + finally: + if homesave is not None: + os.environ['HOME'] = homesave + return stdout, stderr def run_pdb_module(self, script, commands): """Runs the script code as part of a module""" @@ -2695,6 +2711,18 @@ def bœr(): ('bœr', 2), ) + def test_spec(self): + # Test that __main__.__spec__ is set to None when running a script + script = """ + import __main__ + print(__main__.__spec__) + """ + + commands = "continue" + + stdout, _ = self.run_pdb_script(script, commands) + self.assertIn('None', stdout) + def test_find_function_first_executable_line(self): code = textwrap.dedent("""\ def foo(): pass @@ -2892,37 +2920,99 @@ def test_issue26053(self): self.assertRegex(res, "Restarting .* with arguments:\na b c") self.assertRegex(res, "Restarting .* with arguments:\nd e f") - def test_readrc_kwarg(self): + def test_pdbrc_basic(self): script = textwrap.dedent(""" - import pdb; pdb.Pdb(readrc=False).set_trace() + a = 1 + b = 2 + """) - print('hello') + pdbrc = textwrap.dedent(""" + # Comments should be fine + n + p f"{a+8=}" """) - save_home = os.environ.pop('HOME', None) - try: - with os_helper.temp_cwd(): - with open('.pdbrc', 'w') as f: - f.write("invalid\n") - - with open('main.py', 'w') as f: - f.write(script) - - cmd = [sys.executable, 'main.py'] - proc = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stdin=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - with proc: - stdout, stderr = proc.communicate(b'q\n') - self.assertNotIn(b"NameError: name 'invalid' is not defined", - stdout) + stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True) + self.assertNotIn("SyntaxError", stdout) + self.assertIn("a+8=9", stdout) - finally: - if save_home is not None: - os.environ['HOME'] = save_home + def test_pdbrc_empty_line(self): + """Test that empty lines in .pdbrc are ignored.""" + + script = textwrap.dedent(""" + a = 1 + b = 2 + c = 3 + """) + + pdbrc = textwrap.dedent(""" + n + + """) + + stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True) + self.assertIn("b = 2", stdout) + self.assertNotIn("c = 3", stdout) + + def test_pdbrc_alias(self): + script = textwrap.dedent(""" + class A: + def __init__(self): + self.attr = 1 + a = A() + b = 2 + """) + + pdbrc = textwrap.dedent(""" + alias pi for k in %1.__dict__.keys(): print(f"%1.{k} = {%1.__dict__[k]}") + until 6 + pi a + """) + + stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True) + self.assertIn("a.attr = 1", stdout) + + def test_pdbrc_semicolon(self): + script = textwrap.dedent(""" + class A: + def __init__(self): + self.attr = 1 + a = A() + b = 2 + """) + + pdbrc = textwrap.dedent(""" + b 5;;c;;n + """) + + stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True) + self.assertIn("-> b = 2", stdout) + + def test_pdbrc_commands(self): + script = textwrap.dedent(""" + class A: + def __init__(self): + self.attr = 1 + a = A() + b = 2 + """) + + pdbrc = textwrap.dedent(""" + b 6 + commands 1 ;; p a;; end + c + """) + + stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc=pdbrc, remove_home=True) + self.assertIn("<__main__.A object at", stdout) + + def test_readrc_kwarg(self): + script = textwrap.dedent(""" + print('hello') + """) + + stdout, stderr = self.run_pdb_script(script, 'q\n', pdbrc='invalid', remove_home=True) + self.assertIn("NameError: name 'invalid' is not defined", stdout) def test_readrc_homedir(self): save_home = os.environ.pop("HOME", None) @@ -2937,40 +3027,6 @@ def test_readrc_homedir(self): if save_home is not None: os.environ["HOME"] = save_home - def test_read_pdbrc_with_ascii_encoding(self): - script = textwrap.dedent(""" - import pdb; pdb.Pdb().set_trace() - print('hello') - """) - save_home = os.environ.pop('HOME', None) - try: - with os_helper.temp_cwd(): - with open('.pdbrc', 'w', encoding='utf-8') as f: - f.write("Fran\u00E7ais") - - with open('main.py', 'w', encoding='utf-8') as f: - f.write(script) - - cmd = [sys.executable, 'main.py'] - env = {'PYTHONIOENCODING': 'ascii'} - if sys.platform == 'win32': - env['PYTHONLEGACYWINDOWSSTDIO'] = 'non-empty-string' - proc = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stdin=subprocess.PIPE, - stderr=subprocess.PIPE, - env={**os.environ, **env} - ) - with proc: - stdout, stderr = proc.communicate(b'c\n') - self.assertIn(b"UnicodeEncodeError: \'ascii\' codec can\'t encode character " - b"\'\\xe7\' in position 21: ordinal not in range(128)", stderr) - - finally: - if save_home is not None: - os.environ['HOME'] = save_home - def test_header(self): stdout = StringIO() header = 'Nobody expects... blah, blah, blah' diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 648e18d0150ef08..9f8aeeea2573112 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -318,12 +318,44 @@ def raises_oserror(*a): platform._uname_cache = None def test_java_ver(self): - res = platform.java_ver() - if sys.platform == 'java': # Is never actually checked in CI - self.assertTrue(all(res)) + import re + msg = re.escape( + "'java_ver' is deprecated and slated for removal in Python 3.15" + ) + with self.assertWarnsRegex(DeprecationWarning, msg): + res = platform.java_ver() + self.assertEqual(len(res), 4) + @unittest.skipUnless(support.MS_WINDOWS, 'This test only makes sense on Windows') def test_win32_ver(self): - res = platform.win32_ver() + release1, version1, csd1, ptype1 = 'a', 'b', 'c', 'd' + res = platform.win32_ver(release1, version1, csd1, ptype1) + self.assertEqual(len(res), 4) + release, version, csd, ptype = res + if release: + # Currently, release names always come from internal dicts, + # but this could change over time. For now, we just check that + # release is something different from what we have passed. + self.assertNotEqual(release, release1) + if version: + # It is rather hard to test explicit version without + # going deep into the details. + self.assertIn('.', version) + for v in version.split('.'): + int(v) # should not fail + if csd: + self.assertTrue(csd.startswith('SP'), msg=csd) + if ptype: + if os.cpu_count() > 1: + self.assertIn('Multiprocessor', ptype) + else: + self.assertIn('Uniprocessor', ptype) + + @unittest.skipIf(support.MS_WINDOWS, 'This test only makes sense on non Windows') + def test_win32_ver_on_non_windows(self): + release, version, csd, ptype = 'a', '1.0', 'c', 'd' + res = platform.win32_ver(release, version, csd, ptype) + self.assertSequenceEqual(res, (release, version, csd, ptype), seq_type=tuple) def test_mac_ver(self): res = platform.mac_ver() diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index a45f620e18dc1d0..2706d5eb6d98305 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -1270,9 +1270,10 @@ def test_get_and_set_scheduler_and_param(self): self.assertIn(mine, possible_schedulers) try: parent = posix.sched_getscheduler(os.getppid()) - except OSError as e: - if e.errno != errno.EPERM: - raise + except PermissionError: + # POSIX specifies EPERM, but Android returns EACCES. Both errno + # values are mapped to PermissionError. + pass else: self.assertIn(parent, possible_schedulers) self.assertRaises(OSError, posix.sched_getscheduler, -1) @@ -1287,9 +1288,8 @@ def test_get_and_set_scheduler_and_param(self): try: posix.sched_setscheduler(0, mine, param) posix.sched_setparam(0, param) - except OSError as e: - if e.errno != errno.EPERM: - raise + except PermissionError: + pass self.assertRaises(OSError, posix.sched_setparam, -1, param) self.assertRaises(OSError, posix.sched_setscheduler, -1, mine, param) diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py index 3f2bac0155fd9e8..dee94533c745491 100644 --- a/Lib/test/test_pty.py +++ b/Lib/test/test_pty.py @@ -1,7 +1,6 @@ -import sys import unittest from test.support import ( - is_apple_mobile, is_emscripten, is_wasi, reap_children, verbose + is_android, is_apple_mobile, is_emscripten, is_wasi, reap_children, verbose ) from test.support.import_helper import import_module from test.support.os_helper import TESTFN, unlink @@ -9,9 +8,8 @@ # Skip these tests if termios is not available import_module('termios') -# Skip tests on WASM platforms, plus iOS/tvOS/watchOS -if is_apple_mobile or is_emscripten or is_wasi: - raise unittest.SkipTest(f"pty tests not required on {sys.platform}") +if is_android or is_apple_mobile or is_emscripten or is_wasi: + raise unittest.SkipTest("pty is not available on this platform") import errno import os diff --git a/Lib/test/test_pydoc/pydocfodder.py b/Lib/test/test_pydoc/pydocfodder.py index 27037e048db8198..3cc2d5bd57fe5b3 100644 --- a/Lib/test/test_pydoc/pydocfodder.py +++ b/Lib/test/test_pydoc/pydocfodder.py @@ -81,6 +81,8 @@ def B_classmethod(cls, x): A_method_ref = A().A_method A_method_alias = A.A_method B_method_alias = B_method + count = list.count # same name + list_count = list.count __repr__ = object.__repr__ # same name object_repr = object.__repr__ get = {}.get # same name @@ -180,5 +182,7 @@ def __call__(self, inst): B_method2 = B.B_method count = list.count # same name list_count = list.count +__repr__ = object.__repr__ # same name +object_repr = object.__repr__ get = {}.get # same name dict_get = {}.get diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index 32de6aa4df34658..7d61f6758f508c2 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -1694,6 +1694,8 @@ def test_text_doc_routines_in_class(self, cls=pydocfodder.B): self.assertIn(' | global_func(x, y) from test.test_pydoc.pydocfodder', lines) self.assertIn(' | global_func_alias = global_func(x, y)', lines) self.assertIn(' | global_func2_alias = global_func2(x, y) from test.test_pydoc.pydocfodder', lines) + self.assertIn(' | count(self, value, /) from builtins.list', lines) + self.assertIn(' | list_count = count(self, value, /)', lines) self.assertIn(' | __repr__(self, /) from builtins.object', lines) self.assertIn(' | object_repr = __repr__(self, /)', lines) @@ -1722,6 +1724,8 @@ def test_html_doc_routines_in_class(self, cls=pydocfodder.B): self.assertIn('global_func(x, y) from test.test_pydoc.pydocfodder', lines) self.assertIn('global_func_alias = global_func(x, y)', lines) self.assertIn('global_func2_alias = global_func2(x, y) from test.test_pydoc.pydocfodder', lines) + self.assertIn('count(self, value, /) from builtins.list', lines) + self.assertIn('list_count = count(self, value, /)', lines) self.assertIn('__repr__(self, /) from builtins.object', lines) self.assertIn('object_repr = __repr__(self, /)', lines) @@ -1765,6 +1769,10 @@ def test_text_doc_routines_in_module(self): # unbound methods self.assertIn(' B_method(self)', lines) self.assertIn(' B_method2 = B_method(self)', lines) + self.assertIn(' count(self, value, /) unbound builtins.list method', lines) + self.assertIn(' list_count = count(self, value, /) unbound builtins.list method', lines) + self.assertIn(' __repr__(self, /) unbound builtins.object method', lines) + self.assertIn(' object_repr = __repr__(self, /) unbound builtins.object method', lines) def test_html_doc_routines_in_module(self): doc = pydoc.HTMLDoc() @@ -1790,6 +1798,10 @@ def test_html_doc_routines_in_module(self): # unbound methods self.assertIn(' B_method(self)', lines) self.assertIn(' B_method2 = B_method(self)', lines) + self.assertIn(' count(self, value, /) unbound builtins.list method', lines) + self.assertIn(' list_count = count(self, value, /) unbound builtins.list method', lines) + self.assertIn(' __repr__(self, /) unbound builtins.object method', lines) + self.assertIn(' object_repr = __repr__(self, /) unbound builtins.object method', lines) @unittest.skipIf( diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index d941a1a8f9ebc6b..1d56ccd71cf962b 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -755,5 +755,59 @@ def resolve_entity(context, base, system_id, public_id): self.assertEqual(handler_call_args, [("bar", "baz")]) +class ReparseDeferralTest(unittest.TestCase): + def test_getter_setter_round_trip(self): + parser = expat.ParserCreate() + enabled = (expat.version_info >= (2, 6, 0)) + + self.assertIs(parser.GetReparseDeferralEnabled(), enabled) + parser.SetReparseDeferralEnabled(False) + self.assertIs(parser.GetReparseDeferralEnabled(), False) + parser.SetReparseDeferralEnabled(True) + self.assertIs(parser.GetReparseDeferralEnabled(), enabled) + + def test_reparse_deferral_enabled(self): + if expat.version_info < (2, 6, 0): + self.skipTest(f'Expat {expat.version_info} does not ' + 'support reparse deferral') + + started = [] + + def start_element(name, _): + started.append(name) + + parser = expat.ParserCreate() + parser.StartElementHandler = start_element + self.assertTrue(parser.GetReparseDeferralEnabled()) + + for chunk in (b''): + parser.Parse(chunk, False) + + # The key test: Have handlers already fired? Expecting: no. + self.assertEqual(started, []) + + parser.Parse(b'', True) + + self.assertEqual(started, ['doc']) + + def test_reparse_deferral_disabled(self): + started = [] + + def start_element(name, _): + started.append(name) + + parser = expat.ParserCreate() + parser.StartElementHandler = start_element + if expat.version_info >= (2, 6, 0): + parser.SetReparseDeferralEnabled(False) + self.assertFalse(parser.GetReparseDeferralEnabled()) + + for chunk in (b''): + parser.Parse(chunk, False) + + # The key test: Have handlers already fired? Expecting: yes. + self.assertEqual(started, ['doc']) + + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_queue.py b/Lib/test/test_queue.py index 92d670ca6f8f5bd..ad31ba1af03b6f6 100644 --- a/Lib/test/test_queue.py +++ b/Lib/test/test_queue.py @@ -567,7 +567,6 @@ def _shutdown_put_join(self, immediate): results = [] go = threading.Event() q.put("Y") - nb = q.qsize() # queue not fulled thrds = ( @@ -578,13 +577,19 @@ def _shutdown_put_join(self, immediate): for func, params in thrds: threads.append(threading.Thread(target=func, args=params)) threads[-1].start() - self.assertEqual(q.unfinished_tasks, nb) - for i in range(nb): - t = threading.Thread(target=q.task_done) - t.start() - threads.append(t) + self.assertEqual(q.unfinished_tasks, 1) + q.shutdown(immediate) go.set() + + if immediate: + with self.assertRaises(self.queue.ShutDown): + q.get_nowait() + else: + result = q.get() + self.assertEqual(result, "Y") + q.task_done() + for t in threads: t.join() diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py index 993a7d6e264a1f4..b1ac22c28cf7c13 100644 --- a/Lib/test/test_re.py +++ b/Lib/test/test_re.py @@ -1,7 +1,7 @@ from test.support import (gc_collect, bigmemtest, _2G, cpython_only, captured_stdout, check_disallow_instantiation, is_emscripten, is_wasi, - warnings_helper, SHORT_TIMEOUT) + warnings_helper, SHORT_TIMEOUT, CPUStopwatch) import locale import re import string @@ -2284,17 +2284,16 @@ def test_bug_40736(self): def test_search_anchor_at_beginning(self): s = 'x'*10**7 - start = time.perf_counter() - for p in r'\Ay', r'^y': - self.assertIsNone(re.search(p, s)) - self.assertEqual(re.split(p, s), [s]) - self.assertEqual(re.findall(p, s), []) - self.assertEqual(list(re.finditer(p, s)), []) - self.assertEqual(re.sub(p, '', s), s) - t = time.perf_counter() - start + with CPUStopwatch() as stopwatch: + for p in r'\Ay', r'^y': + self.assertIsNone(re.search(p, s)) + self.assertEqual(re.split(p, s), [s]) + self.assertEqual(re.findall(p, s), []) + self.assertEqual(list(re.finditer(p, s)), []) + self.assertEqual(re.sub(p, '', s), s) # Without optimization it takes 1 second on my computer. # With optimization -- 0.0003 seconds. - self.assertLess(t, 0.1) + self.assertLess(stopwatch.seconds, 0.1) def test_possessive_quantifiers(self): """Test Possessive Quantifiers diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py index b80e0524593fc7d..903ad50ba088e86 100644 --- a/Lib/test/test_regrtest.py +++ b/Lib/test/test_regrtest.py @@ -27,7 +27,7 @@ from test.libregrtest import main from test.libregrtest import setup from test.libregrtest import utils -from test.libregrtest.filter import set_match_tests, match_test +from test.libregrtest.filter import get_match_tests, set_match_tests, match_test from test.libregrtest.result import TestStats from test.libregrtest.utils import normalize_test_name @@ -1171,8 +1171,8 @@ def check_leak(self, code, what, *, run_workers=False): stderr=subprocess.STDOUT) self.check_executed_tests(output, [test], failed=test, stats=1) - line = 'beginning 6 repetitions\n123456\n......\n' - self.check_line(output, re.escape(line)) + line = r'beginning 6 repetitions. .*\n123:456\n[.0-9X]{3} 111\n' + self.check_line(output, line) line2 = '%s leaked [1, 1, 1] %s, sum=3\n' % (test, what) self.assertIn(line2, output) @@ -2298,6 +2298,10 @@ def __init__(self, test_id): def id(self): return self.test_id + # Restore patterns once the test completes + patterns = get_match_tests() + self.addCleanup(set_match_tests, patterns) + test_access = Test('test.test_os.FileTests.test_access') test_chdir = Test('test.test_os.Win32ErrorTests.test_chdir') test_copy = Test('test.test_shutil.TestCopy.test_copy') diff --git a/Lib/test/test_resource.py b/Lib/test/test_resource.py index 317e7ca8f8c8537..d23d3623235f389 100644 --- a/Lib/test/test_resource.py +++ b/Lib/test/test_resource.py @@ -138,7 +138,7 @@ def test_pagesize(self): self.assertIsInstance(pagesize, int) self.assertGreaterEqual(pagesize, 0) - @unittest.skipUnless(sys.platform == 'linux', 'test requires Linux') + @unittest.skipUnless(sys.platform in ('linux', 'android'), 'Linux only') def test_linux_constants(self): for attr in ['MSGQUEUE', 'NICE', 'RTPRIO', 'RTTIME', 'SIGPENDING']: with contextlib.suppress(AttributeError): diff --git a/Lib/test/test_sax.py b/Lib/test/test_sax.py index eda4e6a46df437a..97e96668f85c8ab 100644 --- a/Lib/test/test_sax.py +++ b/Lib/test/test_sax.py @@ -19,6 +19,7 @@ from io import BytesIO, StringIO import codecs import os.path +import pyexpat import shutil import sys from urllib.error import URLError @@ -1214,6 +1215,56 @@ def test_expat_incremental_reset(self): self.assertEqual(result.getvalue(), start + b"text") + def test_flush_reparse_deferral_enabled(self): + if pyexpat.version_info < (2, 6, 0): + self.skipTest(f'Expat {pyexpat.version_info} does not support reparse deferral') + + result = BytesIO() + xmlgen = XMLGenerator(result) + parser = create_parser() + parser.setContentHandler(xmlgen) + + for chunk in (""): + parser.feed(chunk) + + self.assertEqual(result.getvalue(), start) # i.e. no elements started + self.assertTrue(parser._parser.GetReparseDeferralEnabled()) + + parser.flush() + + self.assertTrue(parser._parser.GetReparseDeferralEnabled()) + self.assertEqual(result.getvalue(), start + b"") + + parser.feed("") + parser.close() + + self.assertEqual(result.getvalue(), start + b"") + + def test_flush_reparse_deferral_disabled(self): + result = BytesIO() + xmlgen = XMLGenerator(result) + parser = create_parser() + parser.setContentHandler(xmlgen) + + for chunk in (""): + parser.feed(chunk) + + if pyexpat.version_info >= (2, 6, 0): + parser._parser.SetReparseDeferralEnabled(False) + + self.assertEqual(result.getvalue(), start) # i.e. no elements started + self.assertFalse(parser._parser.GetReparseDeferralEnabled()) + + parser.flush() + + self.assertFalse(parser._parser.GetReparseDeferralEnabled()) + self.assertEqual(result.getvalue(), start + b"") + + parser.feed("") + parser.close() + + self.assertEqual(result.getvalue(), start + b"") + # ===== Locator support def test_expat_locator_noinfo(self): diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index d96dad4eb9475d2..60e88d57b2b23de 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -667,6 +667,23 @@ def test_rmtree_on_junction(self): finally: shutil.rmtree(TESTFN, ignore_errors=True) + @unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()') + @unittest.skipIf(sys.platform == "vxworks", + "fifo requires special path on VxWorks") + def test_rmtree_on_named_pipe(self): + os.mkfifo(TESTFN) + try: + with self.assertRaises(NotADirectoryError): + shutil.rmtree(TESTFN) + self.assertTrue(os.path.exists(TESTFN)) + finally: + os.unlink(TESTFN) + + os.mkdir(TESTFN) + os.mkfifo(os.path.join(TESTFN, 'mypipe')) + shutil.rmtree(TESTFN) + self.assertFalse(os.path.exists(TESTFN)) + @unittest.skipIf(sys.platform[:6] == 'cygwin', "This test can't be run on Cygwin (issue #1071513).") @os_helper.skip_if_dac_override diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index b936e9ae91daca0..a7e657f57185242 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1199,8 +1199,8 @@ def testGetServBy(self): # I've ordered this by protocols that have both a tcp and udp # protocol, at least for modern Linuxes. if ( - sys.platform.startswith(('freebsd', 'netbsd', 'gnukfreebsd')) - or sys.platform == 'linux' + sys.platform.startswith( + ('linux', 'android', 'freebsd', 'netbsd', 'gnukfreebsd')) or is_apple ): # avoid the 'echo' service on this platform, as there is an @@ -1218,8 +1218,7 @@ def testGetServBy(self): raise OSError # Try same call with optional protocol omitted # Issue #26936: Android getservbyname() was broken before API 23. - if (not hasattr(sys, 'getandroidapilevel') or - sys.getandroidapilevel() >= 23): + if (not support.is_android) or sys.getandroidapilevel() >= 23: port2 = socket.getservbyname(service) eq(port, port2) # Try udp, but don't barf if it doesn't exist @@ -1577,8 +1576,7 @@ def testGetaddrinfo(self): # port can be a string service name such as "http", a numeric # port number or None # Issue #26936: Android getaddrinfo() was broken before API level 23. - if (not hasattr(sys, 'getandroidapilevel') or - sys.getandroidapilevel() >= 23): + if (not support.is_android) or sys.getandroidapilevel() >= 23: socket.getaddrinfo(HOST, "http") socket.getaddrinfo(HOST, 80) socket.getaddrinfo(HOST, None) @@ -3196,7 +3194,7 @@ def _testSendmsgTimeout(self): # Linux supports MSG_DONTWAIT when sending, but in general, it # only works when receiving. Could add other platforms if they # support it too. - @skipWithClientIf(sys.platform not in {"linux"}, + @skipWithClientIf(sys.platform not in {"linux", "android"}, "MSG_DONTWAIT not known to work on this platform when " "sending") def testSendmsgDontWait(self): @@ -5634,7 +5632,7 @@ def test_setblocking_invalidfd(self): sock.setblocking(False) -@unittest.skipUnless(sys.platform == 'linux', 'Linux specific test') +@unittest.skipUnless(sys.platform in ('linux', 'android'), 'Linux specific test') class TestLinuxAbstractNamespace(unittest.TestCase): UNIX_PATH_MAX = 108 @@ -5759,7 +5757,8 @@ def testUnencodableAddr(self): self.addCleanup(os_helper.unlink, path) self.assertEqual(self.sock.getsockname(), path) - @unittest.skipIf(sys.platform == 'linux', 'Linux specific test') + @unittest.skipIf(sys.platform in ('linux', 'android'), + 'Linux behavior is tested by TestLinuxAbstractNamespace') def testEmptyAddress(self): # Test that binding empty address fails. self.assertRaises(OSError, self.sock.bind, "") diff --git a/Lib/test/test_sort.py b/Lib/test/test_sort.py index 3b6ad4d17b0416a..2a7cfb7affaa21b 100644 --- a/Lib/test/test_sort.py +++ b/Lib/test/test_sort.py @@ -128,6 +128,27 @@ def bad_key(x): x = [e for e, i in augmented] # a stable sort of s check("stability", x, s) + def test_small_stability(self): + from itertools import product + from operator import itemgetter + + # Exhaustively test stability across all lists of small lengths + # and only a few distinct elements. + # This can provoke edge cases that randomization is unlikely to find. + # But it can grow very expensive quickly, so don't overdo it. + NELTS = 3 + MAXSIZE = 9 + + pick0 = itemgetter(0) + for length in range(MAXSIZE + 1): + # There are NELTS ** length distinct lists. + for t in product(range(NELTS), repeat=length): + xs = list(zip(t, range(length))) + # Stability forced by index in each element. + forced = sorted(xs) + # Use key= to hide the index from compares. + native = sorted(xs, key=pick0) + self.assertEqual(forced, native) #============================================================================== class TestBugs(unittest.TestCase): diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 1b18230d83577dc..489cb5e23ba57eb 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -38,7 +38,7 @@ ssl = import_helper.import_module("ssl") import _ssl -from ssl import TLSVersion, _TLSContentType, _TLSMessageType, _TLSAlertType +from ssl import Purpose, TLSVersion, _TLSContentType, _TLSMessageType, _TLSAlertType Py_DEBUG_WIN32 = support.Py_DEBUG and sys.platform == 'win32' @@ -87,9 +87,9 @@ def data_file(*name): (('localityName', 'Castle Anthrax'),), (('organizationName', 'Python Software Foundation'),), (('commonName', 'localhost'),)), - 'notAfter': 'Aug 26 14:23:15 2028 GMT', - 'notBefore': 'Aug 29 14:23:15 2018 GMT', - 'serialNumber': '98A7CF88C74A32ED', + 'notAfter': 'Jan 24 04:21:36 2043 GMT', + 'notBefore': 'Nov 25 04:21:36 2023 GMT', + 'serialNumber': '53E14833F7546C29256DD0F034F776C5E983004C', 'subject': ((('countryName', 'XY'),), (('localityName', 'Castle Anthrax'),), (('organizationName', 'Python Software Foundation'),), @@ -128,6 +128,13 @@ def data_file(*name): SIGNED_CERTFILE_ECC = data_file("keycertecc.pem") SIGNED_CERTFILE_ECC_HOSTNAME = 'localhost-ecc' +# A custom testcase, extracted from `rfc5280::aki::leaf-missing-aki` in x509-limbo: +# The leaf (server) certificate has no AKI, which is forbidden under RFC 5280. +# See: https://x509-limbo.com/testcases/rfc5280/#rfc5280akileaf-missing-aki +LEAF_MISSING_AKI_CERTFILE = data_file("leaf-missing-aki.keycert.pem") +LEAF_MISSING_AKI_CERTFILE_HOSTNAME = "example.com" +LEAF_MISSING_AKI_CA = data_file("leaf-missing-aki.ca.pem") + # Same certificate as pycacert.pem, but without extra text in file SIGNING_CA = data_file("capath", "ceff1710.0") # cert with all kinds of subject alt names @@ -1497,6 +1504,10 @@ def test_create_default_context(self): self.assertEqual(ctx.protocol, ssl.PROTOCOL_TLS_CLIENT) self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) + self.assertEqual(ctx.verify_flags & ssl.VERIFY_X509_PARTIAL_CHAIN, + ssl.VERIFY_X509_PARTIAL_CHAIN) + self.assertEqual(ctx.verify_flags & ssl.VERIFY_X509_STRICT, + ssl.VERIFY_X509_STRICT) self.assertTrue(ctx.check_hostname) self._assert_context_options(ctx) @@ -2946,6 +2957,38 @@ def test_ecc_cert(self): cipher = s.cipher()[0].split('-') self.assertTrue(cipher[:2], ('ECDHE', 'ECDSA')) + @unittest.skipUnless(IS_OPENSSL_3_0_0, + "test requires RFC 5280 check added in OpenSSL 3.0+") + def test_verify_strict(self): + # verification fails by default, since the server cert is non-conforming + client_context = ssl.create_default_context() + client_context.load_verify_locations(LEAF_MISSING_AKI_CA) + hostname = LEAF_MISSING_AKI_CERTFILE_HOSTNAME + + server_context = ssl.create_default_context(purpose=Purpose.CLIENT_AUTH) + server_context.load_cert_chain(LEAF_MISSING_AKI_CERTFILE) + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + with self.assertRaises(ssl.SSLError): + s.connect((HOST, server.port)) + + # explicitly disabling VERIFY_X509_STRICT allows it to succeed + client_context = ssl.create_default_context() + client_context.load_verify_locations(LEAF_MISSING_AKI_CA) + client_context.verify_flags &= ~ssl.VERIFY_X509_STRICT + + server_context = ssl.create_default_context(purpose=Purpose.CLIENT_AUTH) + server_context.load_cert_chain(LEAF_MISSING_AKI_CERTFILE) + server = ThreadedEchoServer(context=server_context, chatty=True) + with server: + with client_context.wrap_socket(socket.socket(), + server_hostname=hostname) as s: + s.connect((HOST, server.port)) + cert = s.getpeercert() + self.assertTrue(cert, "Can't get peer certificate.") + def test_dual_rsa_ecc(self): client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) client_context.load_verify_locations(SIGNING_CA) @@ -4871,7 +4914,7 @@ def run(self): pass # closed, protocol error, etc. def non_linux_skip_if_other_okay_error(self, err): - if sys.platform == "linux": + if sys.platform in ("linux", "android"): return # Expect the full test setup to always work on Linux. if (isinstance(err, ConnectionResetError) or (isinstance(err, OSError) and err.errno == errno.EINVAL) or diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 8bd373976426ef3..117c27d27b38dcb 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -706,7 +706,9 @@ def test_windows_feature_macros(self): "PyType_GenericAlloc", "PyType_GenericNew", "PyType_GetFlags", + "PyType_GetFullyQualifiedName", "PyType_GetModule", + "PyType_GetModuleName", "PyType_GetModuleState", "PyType_GetName", "PyType_GetQualName", diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py index bf2c254c9ee7d9a..1cf41638a7f01a3 100644 --- a/Lib/test/test_statistics.py +++ b/Lib/test/test_statistics.py @@ -2353,6 +2353,66 @@ def test_mixed_int_and_float(self): self.assertAlmostEqual(actual_mean, expected_mean, places=5) +class TestKDE(unittest.TestCase): + + def test_kde(self): + kde = statistics.kde + StatisticsError = statistics.StatisticsError + + kernels = ['normal', 'gauss', 'logistic', 'sigmoid', 'rectangular', + 'uniform', 'triangular', 'parabolic', 'epanechnikov', + 'quartic', 'biweight', 'triweight', 'cosine'] + + sample = [-2.1, -1.3, -0.4, 1.9, 5.1, 6.2] + + # The approximate integral of a PDF should be close to 1.0 + + def integrate(func, low, high, steps=10_000): + "Numeric approximation of a definite function integral." + dx = (high - low) / steps + midpoints = (low + (i + 1/2) * dx for i in range(steps)) + return sum(map(func, midpoints)) * dx + + for kernel in kernels: + with self.subTest(kernel=kernel): + f_hat = kde(sample, h=1.5, kernel=kernel) + area = integrate(f_hat, -20, 20) + self.assertAlmostEqual(area, 1.0, places=4) + + # Check error cases + + with self.assertRaises(StatisticsError): + kde([], h=1.0) # Empty dataset + with self.assertRaises(TypeError): + kde(['abc', 'def'], 1.5) # Non-numeric data + with self.assertRaises(TypeError): + kde(iter(sample), 1.5) # Data is not a sequence + with self.assertRaises(StatisticsError): + kde(sample, h=0.0) # Zero bandwidth + with self.assertRaises(StatisticsError): + kde(sample, h=0.0) # Negative bandwidth + with self.assertRaises(TypeError): + kde(sample, h='str') # Wrong bandwidth type + with self.assertRaises(StatisticsError): + kde(sample, h=1.0, kernel='bogus') # Invalid kernel + + # Test name and docstring of the generated function + + h = 1.5 + kernel = 'cosine' + f_hat = kde(sample, h, kernel) + self.assertEqual(f_hat.__name__, 'pdf') + self.assertIn(kernel, f_hat.__doc__) + self.assertIn(str(h), f_hat.__doc__) + + # Test closed interval for the support boundaries. + # In particular, 'uniform' should non-zero at the boundaries. + + f_hat = kde([0], 1.0, 'uniform') + self.assertEqual(f_hat(-1.0), 1/2) + self.assertEqual(f_hat(1.0), 1/2) + + class TestQuantiles(unittest.TestCase): def test_specific_cases(self): diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 71671a5a984256d..37c16cd10478850 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -668,7 +668,7 @@ def test_thread_info(self): self.assertEqual(len(info), 3) self.assertIn(info.name, ('nt', 'pthread', 'pthread-stubs', 'solaris', None)) self.assertIn(info.lock, ('semaphore', 'mutex+cond', None)) - if sys.platform.startswith(("linux", "freebsd")): + if sys.platform.startswith(("linux", "android", "freebsd")): self.assertEqual(info.name, "pthread") elif sys.platform == "win32": self.assertEqual(info.name, "nt") @@ -729,7 +729,7 @@ def test_subinterp_intern_dynamically_allocated(self): self.assertIs(t, s) interp = interpreters.create() - interp.exec_sync(textwrap.dedent(f''' + interp.exec(textwrap.dedent(f''' import sys t = sys.intern({s!r}) assert id(t) != {id(s)}, (id(t), {id(s)}) @@ -744,7 +744,7 @@ def test_subinterp_intern_statically_allocated(self): t = sys.intern(s) interp = interpreters.create() - interp.exec_sync(textwrap.dedent(f''' + interp.exec(textwrap.dedent(f''' import sys t = sys.intern({s!r}) assert id(t) == {id(t)}, (id(t), {id(t)}) @@ -1101,8 +1101,7 @@ def __del__(self): self.assertEqual(stdout.rstrip(), b"") self.assertEqual(stderr.rstrip(), b"") - @unittest.skipUnless(hasattr(sys, 'getandroidapilevel'), - 'need sys.getandroidapilevel()') + @unittest.skipUnless(sys.platform == "android", "Android only") def test_getandroidapilevel(self): level = sys.getandroidapilevel() self.assertIsInstance(level, int) diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index bb87bf00dc2d1a7..c8315bbc8b727d5 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -1,3 +1,5 @@ +import platform +import re import unittest import sys import os @@ -516,12 +518,9 @@ def test_EXT_SUFFIX_in_vars(self): vars = sysconfig.get_config_vars() self.assertEqual(vars['EXT_SUFFIX'], _imp.extension_suffixes()[0]) - @unittest.skipUnless(sys.platform == 'linux' and - hasattr(sys.implementation, '_multiarch'), - 'multiarch-specific test') - def test_triplet_in_ext_suffix(self): + @unittest.skipUnless(sys.platform == 'linux', 'Linux-specific test') + def test_linux_ext_suffix(self): ctypes = import_module('ctypes') - import platform, re machine = platform.machine() suffix = sysconfig.get_config_var('EXT_SUFFIX') if re.match('(aarch64|arm|mips|ppc|powerpc|s390|sparc)', machine): @@ -534,6 +533,19 @@ def test_triplet_in_ext_suffix(self): self.assertTrue(suffix.endswith(expected_suffixes), f'unexpected suffix {suffix!r}') + @unittest.skipUnless(sys.platform == 'android', 'Android-specific test') + def test_android_ext_suffix(self): + machine = platform.machine() + suffix = sysconfig.get_config_var('EXT_SUFFIX') + expected_triplet = { + "x86_64": "x86_64-linux-android", + "i686": "i686-linux-android", + "aarch64": "aarch64-linux-android", + "armv7l": "arm-linux-androideabi", + }[machine] + self.assertTrue(suffix.endswith(f"-{expected_triplet}.so"), + f"{machine=}, {suffix=}") + @unittest.skipUnless(sys.platform == 'darwin', 'OS X-specific test') def test_osx_ext_suffix(self): suffix = sysconfig.get_config_var('EXT_SUFFIX') diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 51f070e96047a6e..39541faa237b24b 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -507,14 +507,32 @@ def test_length_zero_header(self): with tarfile.open(support.findfile('recursion.tar', subdir='archivetestdata')): pass - def test_extractfile_name(self): + def test_extractfile_attrs(self): # gh-74468: TarFile.name must name a file, not a parent archive. file = self.tar.getmember('ustar/regtype') with self.tar.extractfile(file) as fobj: self.assertEqual(fobj.name, 'ustar/regtype') + self.assertRaises(AttributeError, fobj.fileno) + self.assertIs(fobj.readable(), True) + self.assertIs(fobj.writable(), False) + if self.is_stream: + self.assertRaises(AttributeError, fobj.seekable) + else: + self.assertIs(fobj.seekable(), True) + self.assertIs(fobj.closed, False) + self.assertIs(fobj.closed, True) + self.assertEqual(fobj.name, 'ustar/regtype') + self.assertRaises(AttributeError, fobj.fileno) + self.assertIs(fobj.readable(), True) + self.assertIs(fobj.writable(), False) + if self.is_stream: + self.assertRaises(AttributeError, fobj.seekable) + else: + self.assertIs(fobj.seekable(), True) class MiscReadTestBase(CommonReadTest): + is_stream = False def requires_name_attribute(self): pass @@ -807,6 +825,7 @@ def requires_name_attribute(self): class StreamReadTest(CommonReadTest, unittest.TestCase): prefix="r|" + is_stream = True def test_read_through(self): # Issue #11224: A poorly designed _FileInFile.read() method @@ -1167,7 +1186,7 @@ def _fs_supports_holes(): # # The function returns False if page size is larger than 4 KiB. # For example, ppc64 uses pages of 64 KiB. - if sys.platform.startswith("linux"): + if sys.platform.startswith(("linux", "android")): # Linux evidentially has 512 byte st_blocks units. name = os.path.join(TEMPDIR, "sparse-test") with open(name, "wb") as fobj: diff --git a/Lib/test/test_thread.py b/Lib/test/test_thread.py index 931cb4b797e0b21..d94e04250c93076 100644 --- a/Lib/test/test_thread.py +++ b/Lib/test/test_thread.py @@ -189,8 +189,8 @@ def task(): with threading_helper.wait_threads_exit(): handle = thread.start_joinable_thread(task) handle.join() - with self.assertRaisesRegex(ValueError, "not joinable"): - handle.join() + # Subsequent join() calls should succeed + handle.join() def test_joinable_not_joined(self): handle_destroyed = thread.allocate_lock() @@ -233,58 +233,109 @@ def task(): with self.assertRaisesRegex(RuntimeError, "Cannot join current thread"): raise errors[0] - def test_detach_from_self(self): - errors = [] - handles = [] - start_joinable_thread_returned = thread.allocate_lock() - start_joinable_thread_returned.acquire() - thread_detached = thread.allocate_lock() - thread_detached.acquire() + def test_join_then_self_join(self): + # make sure we can't deadlock in the following scenario with + # threads t0 and t1 (see comment in `ThreadHandle_join()` for more + # details): + # + # - t0 joins t1 + # - t1 self joins + def make_lock(): + lock = thread.allocate_lock() + lock.acquire() + return lock + + error = None + self_joiner_handle = None + self_joiner_started = make_lock() + self_joiner_barrier = make_lock() + def self_joiner(): + nonlocal error + + self_joiner_started.release() + self_joiner_barrier.acquire() - def task(): - start_joinable_thread_returned.acquire() try: - handles[0].detach() + self_joiner_handle.join() except Exception as e: - errors.append(e) - finally: - thread_detached.release() + error = e + + joiner_started = make_lock() + def joiner(): + joiner_started.release() + self_joiner_handle.join() with threading_helper.wait_threads_exit(): - handle = thread.start_joinable_thread(task) - handles.append(handle) - start_joinable_thread_returned.release() - thread_detached.acquire() - with self.assertRaisesRegex(ValueError, "not joinable"): - handle.join() + self_joiner_handle = thread.start_joinable_thread(self_joiner) + # Wait for the self-joining thread to start + self_joiner_started.acquire() + + # Start the thread that joins the self-joiner + joiner_handle = thread.start_joinable_thread(joiner) + + # Wait for the joiner to start + joiner_started.acquire() + + # Not great, but I don't think there's a deterministic way to make + # sure that the self-joining thread has been joined. + time.sleep(0.1) - assert len(errors) == 0 + # Unblock the self-joiner + self_joiner_barrier.release() - def test_detach_then_join(self): + self_joiner_handle.join() + joiner_handle.join() + + with self.assertRaisesRegex(RuntimeError, "Cannot join current thread"): + raise error + + def test_join_with_timeout(self): lock = thread.allocate_lock() lock.acquire() - def task(): + def thr(): lock.acquire() with threading_helper.wait_threads_exit(): - handle = thread.start_joinable_thread(task) - # detach() returns even though the thread is blocked on lock - handle.detach() - # join() then cannot be called anymore - with self.assertRaisesRegex(ValueError, "not joinable"): - handle.join() + handle = thread.start_joinable_thread(thr) + handle.join(0.1) + self.assertFalse(handle.is_done()) lock.release() + handle.join() + self.assertTrue(handle.is_done()) - def test_join_then_detach(self): - def task(): + def test_join_unstarted(self): + handle = thread._ThreadHandle() + with self.assertRaisesRegex(RuntimeError, "thread not started"): + handle.join() + + def test_set_done_unstarted(self): + handle = thread._ThreadHandle() + with self.assertRaisesRegex(RuntimeError, "thread not started"): + handle._set_done() + + def test_start_duplicate_handle(self): + lock = thread.allocate_lock() + lock.acquire() + + def func(): + lock.acquire() + + handle = thread._ThreadHandle() + with threading_helper.wait_threads_exit(): + thread.start_joinable_thread(func, handle=handle) + with self.assertRaisesRegex(RuntimeError, "thread already started"): + thread.start_joinable_thread(func, handle=handle) + lock.release() + handle.join() + + def test_start_with_none_handle(self): + def func(): pass with threading_helper.wait_threads_exit(): - handle = thread.start_joinable_thread(task) + handle = thread.start_joinable_thread(func, handle=None) handle.join() - with self.assertRaisesRegex(ValueError, "not joinable"): - handle.detach() class Barrier: diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 1ab223b81e939e5..f1dc12944cb6cc7 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -47,6 +47,8 @@ def skip_unless_reliable_fork(test): return unittest.skip("due to known OS bug related to thread+fork")(test) if support.HAVE_ASAN_FORK_BUG: return unittest.skip("libasan has a pthread_create() dead lock related to thread+fork")(test) + if support.check_sanitizer(thread=True): + return unittest.skip("TSAN doesn't support threads after fork") return test @@ -406,7 +408,7 @@ def run(self): def test_limbo_cleanup(self): # Issue 7481: Failure to start thread should cleanup the limbo map. - def fail_new_thread(*args): + def fail_new_thread(*args, **kwargs): raise threading.ThreadError() _start_joinable_thread = threading._start_joinable_thread threading._start_joinable_thread = fail_new_thread @@ -423,6 +425,10 @@ def test_finalize_running_thread(self): # Issue 1402: the PyGILState_Ensure / _Release functions may be called # very late on python exit: on deallocation of a running thread for # example. + if support.check_sanitizer(thread=True): + # the thread running `time.sleep(100)` below will still be alive + # at process exit + self.skipTest("TSAN would report thread leak") import_module("ctypes") rc, out, err = assert_python_failure("-c", """if 1: @@ -455,6 +461,11 @@ def waitingThread(): def test_finalize_with_trace(self): # Issue1733757 # Avoid a deadlock when sys.settrace steps into threading._shutdown + if support.check_sanitizer(thread=True): + # the thread running `time.sleep(2)` below will still be alive + # at process exit + self.skipTest("TSAN would report thread leak") + assert_python_ok("-c", """if 1: import sys, threading @@ -901,41 +912,6 @@ def f(): rc, out, err = assert_python_ok("-c", code) self.assertEqual(err, b"") - def test_tstate_lock(self): - # Test an implementation detail of Thread objects. - started = _thread.allocate_lock() - finish = _thread.allocate_lock() - started.acquire() - finish.acquire() - def f(): - started.release() - finish.acquire() - time.sleep(0.01) - # The tstate lock is None until the thread is started - t = threading.Thread(target=f) - self.assertIs(t._tstate_lock, None) - t.start() - started.acquire() - self.assertTrue(t.is_alive()) - # The tstate lock can't be acquired when the thread is running - # (or suspended). - tstate_lock = t._tstate_lock - self.assertFalse(tstate_lock.acquire(timeout=0), False) - finish.release() - # When the thread ends, the state_lock can be successfully - # acquired. - self.assertTrue(tstate_lock.acquire(timeout=support.SHORT_TIMEOUT), False) - # But is_alive() is still True: we hold _tstate_lock now, which - # prevents is_alive() from knowing the thread's end-of-life C code - # is done. - self.assertTrue(t.is_alive()) - # Let is_alive() find out the C code is done. - tstate_lock.release() - self.assertFalse(t.is_alive()) - # And verify the thread disposed of _tstate_lock. - self.assertIsNone(t._tstate_lock) - t.join() - def test_repr_stopped(self): # Verify that "stopped" shows up in repr(Thread) appropriately. started = _thread.allocate_lock() @@ -1101,30 +1077,6 @@ def checker(): self.assertEqual(threading.getprofile(), old_profile) self.assertEqual(sys.getprofile(), old_profile) - @cpython_only - def test_shutdown_locks(self): - for daemon in (False, True): - with self.subTest(daemon=daemon): - event = threading.Event() - thread = threading.Thread(target=event.wait, daemon=daemon) - - # Thread.start() must add lock to _shutdown_locks, - # but only for non-daemon thread - thread.start() - tstate_lock = thread._tstate_lock - if not daemon: - self.assertIn(tstate_lock, threading._shutdown_locks) - else: - self.assertNotIn(tstate_lock, threading._shutdown_locks) - - # unblock the thread and join it - event.set() - thread.join() - - # Thread._stop() must remove tstate_lock from _shutdown_locks. - # Daemon threads must never add it to _shutdown_locks. - self.assertNotIn(tstate_lock, threading._shutdown_locks) - def test_locals_at_exit(self): # bpo-19466: thread locals must not be deleted before destructors # are called @@ -1303,6 +1255,11 @@ def test_4_daemon_threads(self): # Check that a daemon thread cannot crash the interpreter on shutdown # by manipulating internal structures that are being disposed of in # the main thread. + if support.check_sanitizer(thread=True): + # some of the threads running `random_io` below will still be alive + # at process exit + self.skipTest("TSAN would report thread leak") + script = """if True: import os import random @@ -1478,7 +1435,7 @@ def test_threads_join_with_no_main(self): DONE = b'D' interp = interpreters.create() - interp.exec_sync(f"""if True: + interp.exec(f"""if True: import os import threading import time diff --git a/Lib/test/test_threadsignals.py b/Lib/test/test_threadsignals.py index 6a53d655015cdb4..bf241ada90ea35f 100644 --- a/Lib/test/test_threadsignals.py +++ b/Lib/test/test_threadsignals.py @@ -32,39 +32,28 @@ def handle_signals(sig,frame): # a function that will be spawned as a separate thread. def send_signals(): - os.kill(process_pid, signal.SIGUSR1) - os.kill(process_pid, signal.SIGUSR2) + # We use `raise_signal` rather than `kill` because: + # * It verifies that a signal delivered to a background thread still has + # its Python-level handler called on the main thread. + # * It ensures the signal is handled before the thread exits. + signal.raise_signal(signal.SIGUSR1) + signal.raise_signal(signal.SIGUSR2) signalled_all.release() @threading_helper.requires_working_threading() -@unittest.skipUnless(hasattr(signal, "alarm"), "test requires signal.alarm") class ThreadSignals(unittest.TestCase): def test_signals(self): with threading_helper.wait_threads_exit(): # Test signal handling semantics of threads. - # We spawn a thread, have the thread send two signals, and + # We spawn a thread, have the thread send itself two signals, and # wait for it to finish. Check that we got both signals # and that they were run by the main thread. signalled_all.acquire() self.spawnSignallingThread() signalled_all.acquire() - # the signals that we asked the kernel to send - # will come back, but we don't know when. - # (it might even be after the thread exits - # and might be out of order.) If we haven't seen - # the signals yet, send yet another signal and - # wait for it return. - if signal_blackboard[signal.SIGUSR1]['tripped'] == 0 \ - or signal_blackboard[signal.SIGUSR2]['tripped'] == 0: - try: - signal.alarm(1) - signal.pause() - finally: - signal.alarm(0) - self.assertEqual( signal_blackboard[signal.SIGUSR1]['tripped'], 1) self.assertEqual( signal_blackboard[signal.SIGUSR1]['tripped_by'], thread.get_ident()) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index a0aeea515afbd69..fb234b7bc5962ad 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -511,7 +511,7 @@ def test_process_time(self): def test_thread_time(self): if not hasattr(time, 'thread_time'): - if sys.platform.startswith(('linux', 'win')): + if sys.platform.startswith(('linux', 'android', 'win')): self.fail("time.thread_time() should be available on %r" % (sys.platform,)) else: diff --git a/Lib/test/test_tools/test_makefile.py b/Lib/test/test_tools/test_makefile.py new file mode 100644 index 000000000000000..7222a054dcd61ca --- /dev/null +++ b/Lib/test/test_tools/test_makefile.py @@ -0,0 +1,64 @@ +""" +Tests for `Makefile`. +""" + +import os +import unittest +from test import support +import sysconfig + +MAKEFILE = sysconfig.get_makefile_filename() + +if not support.check_impl_detail(cpython=True): + raise unittest.SkipTest('cpython only') +if not os.path.exists(MAKEFILE) or not os.path.isfile(MAKEFILE): + raise unittest.SkipTest('Makefile could not be found') + + +class TestMakefile(unittest.TestCase): + def list_test_dirs(self): + result = [] + found_testsubdirs = False + with open(MAKEFILE, 'r', encoding='utf-8') as f: + for line in f: + if line.startswith('TESTSUBDIRS='): + found_testsubdirs = True + result.append( + line.removeprefix('TESTSUBDIRS=').replace( + '\\', '', + ).strip(), + ) + continue + if found_testsubdirs: + if '\t' not in line: + break + result.append(line.replace('\\', '').strip()) + return result + + def test_makefile_test_folders(self): + test_dirs = self.list_test_dirs() + idle_test = 'idlelib/idle_test' + self.assertIn(idle_test, test_dirs) + + used = [idle_test] + for dirpath, _, _ in os.walk(support.TEST_HOME_DIR): + dirname = os.path.basename(dirpath) + if dirname == '__pycache__': + continue + + relpath = os.path.relpath(dirpath, support.STDLIB_DIR) + with self.subTest(relpath=relpath): + self.assertIn( + relpath, + test_dirs, + msg=( + f"{relpath!r} is not included in the Makefile's list " + "of test directories to install" + ) + ) + used.append(relpath) + + # Check that there are no extra entries: + unique_test_dirs = set(test_dirs) + self.assertSetEqual(unique_test_dirs, set(used)) + self.assertEqual(len(test_dirs), len(unique_test_dirs)) diff --git a/Lib/test/test_ttk/test_widgets.py b/Lib/test/test_ttk/test_widgets.py index fd1a748a498ac51..e3e440c45859f7a 100644 --- a/Lib/test/test_ttk/test_widgets.py +++ b/Lib/test/test_ttk/test_widgets.py @@ -285,9 +285,29 @@ def test_unique_variables(self): b.pack() buttons.append(b) variables = [str(b['variable']) for b in buttons] - print(variables) self.assertEqual(len(set(variables)), 4, variables) + def test_unique_variables2(self): + buttons = [] + f = ttk.Frame(self.root) + f.pack() + f = ttk.Frame(self.root) + f.pack() + for j in 'AB': + b = tkinter.Checkbutton(f, text=j) + b.pack() + buttons.append(b) + # Should be larger than the number of all previously created + # tkinter.Checkbutton widgets: + for j in range(100): + b = ttk.Checkbutton(f, text=str(j)) + b.pack() + buttons.append(b) + names = [str(b) for b in buttons] + self.assertEqual(len(set(names)), len(buttons), names) + variables = [str(b['variable']) for b in buttons] + self.assertEqual(len(set(variables)), len(buttons), variables) + @add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) class EntryTest(AbstractWidgetTest, unittest.TestCase): diff --git a/Lib/test/test_type_cache.py b/Lib/test/test_type_cache.py index 58572c6f4d31578..e90e315c8083618 100644 --- a/Lib/test/test_type_cache.py +++ b/Lib/test/test_type_cache.py @@ -2,7 +2,7 @@ import unittest import dis from test import support -from test.support import import_helper +from test.support import import_helper, requires_specialization try: from sys import _clear_type_cache except ImportError: @@ -94,6 +94,7 @@ class C: @support.cpython_only +@requires_specialization class TypeCacheWithSpecializationTests(unittest.TestCase): def tearDown(self): _clear_type_cache() diff --git a/Lib/test/test_type_comments.py b/Lib/test/test_type_comments.py index 5a911da56f8f8a1..ee8939f62d082cd 100644 --- a/Lib/test/test_type_comments.py +++ b/Lib/test/test_type_comments.py @@ -309,7 +309,7 @@ def test_withstmt(self): self.assertEqual(tree.body[0].type_comment, None) def test_parenthesized_withstmt(self): - for tree in self.parse_all(parenthesized_withstmt, minver=9): + for tree in self.parse_all(parenthesized_withstmt): self.assertEqual(tree.body[0].type_comment, "int") self.assertEqual(tree.body[1].type_comment, "int") tree = self.classic_parse(parenthesized_withstmt) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 1acb2a4d81adf30..16985122bc0219d 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -713,6 +713,26 @@ def test_hash(self): self.assertEqual(hash(int | str), hash(str | int)) self.assertEqual(hash(int | str), hash(typing.Union[int, str])) + def test_union_of_unhashable(self): + class UnhashableMeta(type): + __hash__ = None + + class A(metaclass=UnhashableMeta): ... + class B(metaclass=UnhashableMeta): ... + + self.assertEqual((A | B).__args__, (A, B)) + union1 = A | B + with self.assertRaises(TypeError): + hash(union1) + + union2 = int | B + with self.assertRaises(TypeError): + hash(union2) + + union3 = A | int + with self.assertRaises(TypeError): + hash(union3) + def test_instancecheck_and_subclasscheck(self): for x in (int | str, typing.Union[int, str]): with self.subTest(x=x): diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 176623171c98888..54c7b976185585b 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2,10 +2,11 @@ import collections import collections.abc from collections import defaultdict -from functools import lru_cache, wraps +from functools import lru_cache, wraps, reduce import gc import inspect import itertools +import operator import pickle import re import sys @@ -30,7 +31,7 @@ from typing import dataclass_transform from typing import no_type_check, no_type_check_decorator from typing import Type -from typing import NamedTuple, NotRequired, Required, TypedDict +from typing import NamedTuple, NotRequired, Required, ReadOnly, TypedDict from typing import IO, TextIO, BinaryIO from typing import Pattern, Match from typing import Annotated, ForwardRef @@ -1769,6 +1770,26 @@ def test_union_union(self): v = Union[u, Employee] self.assertEqual(v, Union[int, float, Employee]) + def test_union_of_unhashable(self): + class UnhashableMeta(type): + __hash__ = None + + class A(metaclass=UnhashableMeta): ... + class B(metaclass=UnhashableMeta): ... + + self.assertEqual(Union[A, B].__args__, (A, B)) + union1 = Union[A, B] + with self.assertRaises(TypeError): + hash(union1) + + union2 = Union[int, B] + with self.assertRaises(TypeError): + hash(union2) + + union3 = Union[A, int] + with self.assertRaises(TypeError): + hash(union3) + def test_repr(self): self.assertEqual(repr(Union), 'typing.Union') u = Union[Employee, int] @@ -4634,8 +4655,6 @@ def test_fail_with_bare_union(self): List[Union] with self.assertRaises(TypeError): Tuple[Optional] - with self.assertRaises(TypeError): - ClassVar[ClassVar[int]] with self.assertRaises(TypeError): List[ClassVar[int]] @@ -5506,10 +5525,8 @@ def some(self): self.assertFalse(hasattr(WithOverride.some, "__override__")) def test_multiple_decorators(self): - import functools - def with_wraps(f): # similar to `lru_cache` definition - @functools.wraps(f) + @wraps(f) def wrapper(*args, **kwargs): return f(*args, **kwargs) return wrapper @@ -5848,6 +5865,12 @@ def foo(a: 'Node[T'): with self.assertRaises(SyntaxError): get_type_hints(foo) + def test_syntax_error_empty_string(self): + for form in [typing.List, typing.Set, typing.Type, typing.Deque]: + with self.subTest(form=form): + with self.assertRaises(SyntaxError): + form[''] + def test_name_error(self): def foo(a: 'Noode[T]'): @@ -5989,16 +6012,6 @@ class F: for clazz in [C, D, E, F]: self.assertEqual(get_type_hints(clazz), expected_result) - def test_nested_classvar_fails_forward_ref_check(self): - class E: - foo: 'typing.ClassVar[typing.ClassVar[int]]' = 7 - class F: - foo: ClassVar['ClassVar[int]'] = 7 - - for clazz in [E, F]: - with self.assertRaises(TypeError): - get_type_hints(clazz) - def test_meta_no_type_check(self): depr_msg = ( "'typing.no_type_check_decorator' is deprecated " @@ -8309,6 +8322,69 @@ class T4(TypedDict, Generic[S]): pass self.assertEqual(klass.__optional_keys__, set()) self.assertIsInstance(klass(), dict) + def test_readonly_inheritance(self): + class Base1(TypedDict): + a: ReadOnly[int] + + class Child1(Base1): + b: str + + self.assertEqual(Child1.__readonly_keys__, frozenset({'a'})) + self.assertEqual(Child1.__mutable_keys__, frozenset({'b'})) + + class Base2(TypedDict): + a: ReadOnly[int] + + class Child2(Base2): + b: str + + self.assertEqual(Child1.__readonly_keys__, frozenset({'a'})) + self.assertEqual(Child1.__mutable_keys__, frozenset({'b'})) + + def test_cannot_make_mutable_key_readonly(self): + class Base(TypedDict): + a: int + + with self.assertRaises(TypeError): + class Child(Base): + a: ReadOnly[int] + + def test_can_make_readonly_key_mutable(self): + class Base(TypedDict): + a: ReadOnly[int] + + class Child(Base): + a: int + + self.assertEqual(Child.__readonly_keys__, frozenset()) + self.assertEqual(Child.__mutable_keys__, frozenset({'a'})) + + def test_combine_qualifiers(self): + class AllTheThings(TypedDict): + a: Annotated[Required[ReadOnly[int]], "why not"] + b: Required[Annotated[ReadOnly[int], "why not"]] + c: ReadOnly[NotRequired[Annotated[int, "why not"]]] + d: NotRequired[Annotated[int, "why not"]] + + self.assertEqual(AllTheThings.__required_keys__, frozenset({'a', 'b'})) + self.assertEqual(AllTheThings.__optional_keys__, frozenset({'c', 'd'})) + self.assertEqual(AllTheThings.__readonly_keys__, frozenset({'a', 'b', 'c'})) + self.assertEqual(AllTheThings.__mutable_keys__, frozenset({'d'})) + + self.assertEqual( + get_type_hints(AllTheThings, include_extras=False), + {'a': int, 'b': int, 'c': int, 'd': int}, + ) + self.assertEqual( + get_type_hints(AllTheThings, include_extras=True), + { + 'a': Annotated[Required[ReadOnly[int]], 'why not'], + 'b': Required[Annotated[ReadOnly[int], 'why not']], + 'c': ReadOnly[NotRequired[Annotated[int, 'why not']]], + 'd': NotRequired[Annotated[int, 'why not']], + }, + ) + class RequiredTests(BaseTestCase): @@ -8524,6 +8600,76 @@ def test_flatten(self): self.assertEqual(A.__metadata__, (4, 5)) self.assertEqual(A.__origin__, int) + def test_deduplicate_from_union(self): + # Regular: + self.assertEqual(get_args(Annotated[int, 1] | int), + (Annotated[int, 1], int)) + self.assertEqual(get_args(Union[Annotated[int, 1], int]), + (Annotated[int, 1], int)) + self.assertEqual(get_args(Annotated[int, 1] | Annotated[int, 2] | int), + (Annotated[int, 1], Annotated[int, 2], int)) + self.assertEqual(get_args(Union[Annotated[int, 1], Annotated[int, 2], int]), + (Annotated[int, 1], Annotated[int, 2], int)) + self.assertEqual(get_args(Annotated[int, 1] | Annotated[str, 1] | int), + (Annotated[int, 1], Annotated[str, 1], int)) + self.assertEqual(get_args(Union[Annotated[int, 1], Annotated[str, 1], int]), + (Annotated[int, 1], Annotated[str, 1], int)) + + # Duplicates: + self.assertEqual(Annotated[int, 1] | Annotated[int, 1] | int, + Annotated[int, 1] | int) + self.assertEqual(Union[Annotated[int, 1], Annotated[int, 1], int], + Union[Annotated[int, 1], int]) + + # Unhashable metadata: + self.assertEqual(get_args(str | Annotated[int, {}] | Annotated[int, set()] | int), + (str, Annotated[int, {}], Annotated[int, set()], int)) + self.assertEqual(get_args(Union[str, Annotated[int, {}], Annotated[int, set()], int]), + (str, Annotated[int, {}], Annotated[int, set()], int)) + self.assertEqual(get_args(str | Annotated[int, {}] | Annotated[str, {}] | int), + (str, Annotated[int, {}], Annotated[str, {}], int)) + self.assertEqual(get_args(Union[str, Annotated[int, {}], Annotated[str, {}], int]), + (str, Annotated[int, {}], Annotated[str, {}], int)) + + self.assertEqual(get_args(Annotated[int, 1] | str | Annotated[str, {}] | int), + (Annotated[int, 1], str, Annotated[str, {}], int)) + self.assertEqual(get_args(Union[Annotated[int, 1], str, Annotated[str, {}], int]), + (Annotated[int, 1], str, Annotated[str, {}], int)) + + import dataclasses + @dataclasses.dataclass + class ValueRange: + lo: int + hi: int + v = ValueRange(1, 2) + self.assertEqual(get_args(Annotated[int, v] | None), + (Annotated[int, v], types.NoneType)) + self.assertEqual(get_args(Union[Annotated[int, v], None]), + (Annotated[int, v], types.NoneType)) + self.assertEqual(get_args(Optional[Annotated[int, v]]), + (Annotated[int, v], types.NoneType)) + + # Unhashable metadata duplicated: + self.assertEqual(Annotated[int, {}] | Annotated[int, {}] | int, + Annotated[int, {}] | int) + self.assertEqual(Annotated[int, {}] | Annotated[int, {}] | int, + int | Annotated[int, {}]) + self.assertEqual(Union[Annotated[int, {}], Annotated[int, {}], int], + Union[Annotated[int, {}], int]) + self.assertEqual(Union[Annotated[int, {}], Annotated[int, {}], int], + Union[int, Annotated[int, {}]]) + + def test_order_in_union(self): + expr1 = Annotated[int, 1] | str | Annotated[str, {}] | int + for args in itertools.permutations(get_args(expr1)): + with self.subTest(args=args): + self.assertEqual(expr1, reduce(operator.or_, args)) + + expr2 = Union[Annotated[int, 1], str, Annotated[str, {}], int] + for args in itertools.permutations(get_args(expr2)): + with self.subTest(args=args): + self.assertEqual(expr2, Union[args]) + def test_specialize(self): L = Annotated[List[T], "my decoration"] LI = Annotated[List[int], "my decoration"] @@ -8544,6 +8690,16 @@ def test_hash_eq(self): {Annotated[int, 4, 5], Annotated[int, 4, 5], Annotated[T, 4, 5]}, {Annotated[int, 4, 5], Annotated[T, 4, 5]} ) + # Unhashable `metadata` raises `TypeError`: + a1 = Annotated[int, []] + with self.assertRaises(TypeError): + hash(a1) + + class A: + __hash__ = None + a2 = Annotated[int, A()] + with self.assertRaises(TypeError): + hash(a2) def test_instantiate(self): class C: @@ -8611,6 +8767,34 @@ class C: self.assertEqual(get_type_hints(C, globals())['classvar'], ClassVar[int]) self.assertEqual(get_type_hints(C, globals())['const'], Final[int]) + def test_special_forms_nesting(self): + # These are uncommon types and are to ensure runtime + # is lax on validation. See gh-89547 for more context. + class CF: + x: ClassVar[Final[int]] + + class FC: + x: Final[ClassVar[int]] + + class ACF: + x: Annotated[ClassVar[Final[int]], "a decoration"] + + class CAF: + x: ClassVar[Annotated[Final[int], "a decoration"]] + + class AFC: + x: Annotated[Final[ClassVar[int]], "a decoration"] + + class FAC: + x: Final[Annotated[ClassVar[int], "a decoration"]] + + self.assertEqual(get_type_hints(CF, globals())['x'], ClassVar[Final[int]]) + self.assertEqual(get_type_hints(FC, globals())['x'], Final[ClassVar[int]]) + self.assertEqual(get_type_hints(ACF, globals())['x'], ClassVar[Final[int]]) + self.assertEqual(get_type_hints(CAF, globals())['x'], ClassVar[Final[int]]) + self.assertEqual(get_type_hints(AFC, globals())['x'], Final[ClassVar[int]]) + self.assertEqual(get_type_hints(FAC, globals())['x'], Final[ClassVar[int]]) + def test_cannot_subclass(self): with self.assertRaisesRegex(TypeError, "Cannot subclass .*Annotated"): class C(Annotated): diff --git a/Lib/test/test_unittest/testmock/testmock.py b/Lib/test/test_unittest/testmock/testmock.py index 1725406bcfb9e41..b81b3049d56bf82 100644 --- a/Lib/test/test_unittest/testmock/testmock.py +++ b/Lib/test/test_unittest/testmock/testmock.py @@ -245,6 +245,65 @@ class B(object): with mock.patch('builtins.open', mock.mock_open()): mock.mock_open() # should still be valid with open() mocked + def test_create_autospec_wraps_class(self): + """Autospec a class with wraps & test if the call is passed to the + wrapped object.""" + result = "real result" + + class Result: + def get_result(self): + return result + class_mock = create_autospec(spec=Result, wraps=Result) + # Have to reassign the return_value to DEFAULT to return the real + # result (actual instance of "Result") when the mock is called. + class_mock.return_value = mock.DEFAULT + self.assertEqual(class_mock().get_result(), result) + # Autospec should also wrap child attributes of parent. + self.assertEqual(class_mock.get_result._mock_wraps, Result.get_result) + + def test_create_autospec_instance_wraps_class(self): + """Autospec a class instance with wraps & test if the call is passed + to the wrapped object.""" + result = "real result" + + class Result: + @staticmethod + def get_result(): + """This is a static method because when the mocked instance of + 'Result' will call this method, it won't be able to consume + 'self' argument.""" + return result + instance_mock = create_autospec(spec=Result, instance=True, wraps=Result) + # Have to reassign the return_value to DEFAULT to return the real + # result from "Result.get_result" when the mocked instance of "Result" + # calls "get_result". + instance_mock.get_result.return_value = mock.DEFAULT + self.assertEqual(instance_mock.get_result(), result) + # Autospec should also wrap child attributes of the instance. + self.assertEqual(instance_mock.get_result._mock_wraps, Result.get_result) + + def test_create_autospec_wraps_function_type(self): + """Autospec a function or a method with wraps & test if the call is + passed to the wrapped object.""" + result = "real result" + + class Result: + def get_result(self): + return result + func_mock = create_autospec(spec=Result.get_result, wraps=Result.get_result) + self.assertEqual(func_mock(Result()), result) + + def test_explicit_return_value_even_if_mock_wraps_object(self): + """If the mock has an explicit return_value set then calls are not + passed to the wrapped object and the return_value is returned instead. + """ + def my_func(): + return None + func_mock = create_autospec(spec=my_func, wraps=my_func) + return_value = "explicit return value" + func_mock.return_value = return_value + self.assertEqual(func_mock(), return_value) + def test_explicit_parent(self): parent = Mock() mock1 = Mock(parent=parent, return_value=None) @@ -622,6 +681,14 @@ def test_wraps_calls(self): real = Mock() mock = Mock(wraps=real) + # If "Mock" wraps an object, just accessing its + # "return_value" ("NonCallableMock.__get_return_value") should not + # trigger its descriptor ("NonCallableMock.__set_return_value") so + # the default "return_value" should always be "sentinel.DEFAULT". + self.assertEqual(mock.return_value, DEFAULT) + # It will not be "sentinel.DEFAULT" if the mock is not wrapping any + # object. + self.assertNotEqual(real.return_value, DEFAULT) self.assertEqual(mock(), real()) real.reset_mock() diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py index 77ce18cbf4cbfb5..bb15f64c59dbd13 100644 --- a/Lib/test/test_unparse.py +++ b/Lib/test/test_unparse.py @@ -370,13 +370,13 @@ def test_slices(self): self.check_ast_roundtrip("a[i:j, k]") def test_invalid_raise(self): - self.check_invalid(ast.Raise(exc=None, cause=ast.Name(id="X"))) + self.check_invalid(ast.Raise(exc=None, cause=ast.Name(id="X", ctx=ast.Load()))) def test_invalid_fstring_value(self): self.check_invalid( ast.JoinedStr( values=[ - ast.Name(id="test"), + ast.Name(id="test", ctx=ast.Load()), ast.Constant(value="test") ] ) @@ -650,9 +650,18 @@ def test_multiquote_joined_string(self): self.check_ast_roundtrip("""f'''""\"''\\'{""\"\\n\\"'''""\" '''\\n'''}''' """) def test_backslash_in_format_spec(self): - self.check_ast_roundtrip("""f"{x:\\ }" """) + import re + msg = re.escape("invalid escape sequence '\\ '") + with self.assertWarnsRegex(SyntaxWarning, msg): + self.check_ast_roundtrip("""f"{x:\\ }" """) + self.check_ast_roundtrip("""f"{x:\\n}" """) + self.check_ast_roundtrip("""f"{x:\\\\ }" """) - self.check_ast_roundtrip("""f"{x:\\\\\\ }" """) + + with self.assertWarnsRegex(SyntaxWarning, msg): + self.check_ast_roundtrip("""f"{x:\\\\\\ }" """) + self.check_ast_roundtrip("""f"{x:\\\\\\n}" """) + self.check_ast_roundtrip("""f"{x:\\\\\\\\ }" """) def test_quote_in_format_spec(self): @@ -709,7 +718,7 @@ def test_function_with_type_params_and_bound(self): body=[ast.Pass()], decorator_list=[], returns=None, - type_params=[ast.TypeVar("T", bound=ast.Name("int"))], + type_params=[ast.TypeVar("T", bound=ast.Name("int", ctx=ast.Load()))], ) ast.fix_missing_locations(node) self.assertEqual(ast.unparse(node), "def f[T: int]():\n pass") diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py index fa528a675892b5e..739c15df13de210 100644 --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -15,10 +15,11 @@ import subprocess import urllib.request -# The proxy bypass method imported below has logic specific to the OSX -# proxy config data structure but is testable on all platforms. +# The proxy bypass method imported below has logic specific to the +# corresponding system but is testable on all platforms. from urllib.request import (Request, OpenerDirector, HTTPBasicAuthHandler, HTTPPasswordMgrWithPriorAuth, _parse_proxy, + _proxy_bypass_winreg_override, _proxy_bypass_macosx_sysconf, AbstractDigestAuthHandler) from urllib.parse import urlparse @@ -1485,6 +1486,30 @@ def test_proxy_https_proxy_authorization(self): self.assertEqual(req.host, "proxy.example.com:3128") self.assertEqual(req.get_header("Proxy-authorization"), "FooBar") + @unittest.skipUnless(os.name == "nt", "only relevant for Windows") + def test_winreg_proxy_bypass(self): + proxy_override = "www.example.com;*.example.net; 192.168.0.1" + proxy_bypass = _proxy_bypass_winreg_override + for host in ("www.example.com", "www.example.net", "192.168.0.1"): + self.assertTrue(proxy_bypass(host, proxy_override), + "expected bypass of %s to be true" % host) + + for host in ("example.com", "www.example.org", "example.net", + "192.168.0.2"): + self.assertFalse(proxy_bypass(host, proxy_override), + "expected bypass of %s to be False" % host) + + # check intranet address bypass + proxy_override = "example.com; " + self.assertTrue(proxy_bypass("example.com", proxy_override), + "expected bypass of %s to be true" % host) + self.assertFalse(proxy_bypass("example.net", proxy_override), + "expected bypass of %s to be False" % host) + for host in ("test", "localhost"): + self.assertTrue(proxy_bypass(host, proxy_override), + "expect to bypass intranet address '%s'" + % host) + @unittest.skipUnless(sys.platform == 'darwin', "only relevant for OSX") def test_osx_proxy_bypass(self): bypass = { diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py index 625c6dc88796b61..236b6e4516490a1 100644 --- a/Lib/test/test_urlparse.py +++ b/Lib/test/test_urlparse.py @@ -19,6 +19,10 @@ ("=a", [('', 'a')]), ("a", [('a', '')]), ("a=", [('a', '')]), + ("a=b=c", [('a', 'b=c')]), + ("a%3Db=c", [('a=b', 'c')]), + ("a=b&c=d", [('a', 'b'), ('c', 'd')]), + ("a=b%26c=d", [('a', 'b&c=d')]), ("&a=b", [('a', 'b')]), ("a=a+b&b=b+c", [('a', 'a b'), ('b', 'b c')]), ("a=1&a=2", [('a', '1'), ('a', '2')]), @@ -29,6 +33,10 @@ (b"=a", [(b'', b'a')]), (b"a", [(b'a', b'')]), (b"a=", [(b'a', b'')]), + (b"a=b=c", [(b'a', b'b=c')]), + (b"a%3Db=c", [(b'a=b', b'c')]), + (b"a=b&c=d", [(b'a', b'b'), (b'c', b'd')]), + (b"a=b%26c=d", [(b'a', b'b&c=d')]), (b"&a=b", [(b'a', b'b')]), (b"a=a+b&b=b+c", [(b'a', b'a b'), (b'b', b'b c')]), (b"a=1&a=2", [(b'a', b'1'), (b'a', b'2')]), @@ -36,6 +44,14 @@ ("a=a+b;b=b+c", [('a', 'a b;b=b c')]), (b";a=b", [(b';a', b'b')]), (b"a=a+b;b=b+c", [(b'a', b'a b;b=b c')]), + + ("\u0141=\xE9", [('\u0141', '\xE9')]), + ("%C5%81=%C3%A9", [('\u0141', '\xE9')]), + ("%81=%A9", [('\ufffd', '\ufffd')]), + (b"\xc5\x81=\xc3\xa9", [(b'\xc5\x81', b'\xc3\xa9')]), + (b"%C5%81=%C3%A9", [(b'\xc5\x81', b'\xc3\xa9')]), + (b"\x81=\xA9", [(b'\x81', b'\xa9')]), + (b"%81=%A9", [(b'\x81', b'\xa9')]), ] # Each parse_qs testcase is a two-tuple that contains @@ -49,6 +65,10 @@ ("=a", {'': ['a']}), ("a", {'a': ['']}), ("a=", {'a': ['']}), + ("a=b=c", {'a': ['b=c']}), + ("a%3Db=c", {'a=b': ['c']}), + ("a=b&c=d", {'a': ['b'], 'c': ['d']}), + ("a=b%26c=d", {'a': ['b&c=d']}), ("&a=b", {'a': ['b']}), ("a=a+b&b=b+c", {'a': ['a b'], 'b': ['b c']}), ("a=1&a=2", {'a': ['1', '2']}), @@ -59,6 +79,10 @@ (b"=a", {b'': [b'a']}), (b"a", {b'a': [b'']}), (b"a=", {b'a': [b'']}), + (b"a=b=c", {b'a': [b'b=c']}), + (b"a%3Db=c", {b'a=b': [b'c']}), + (b"a=b&c=d", {b'a': [b'b'], b'c': [b'd']}), + (b"a=b%26c=d", {b'a': [b'b&c=d']}), (b"&a=b", {b'a': [b'b']}), (b"a=a+b&b=b+c", {b'a': [b'a b'], b'b': [b'b c']}), (b"a=1&a=2", {b'a': [b'1', b'2']}), @@ -66,6 +90,15 @@ ("a=a+b;b=b+c", {'a': ['a b;b=b c']}), (b";a=b", {b';a': [b'b']}), (b"a=a+b;b=b+c", {b'a':[ b'a b;b=b c']}), + (b"a=a%E2%80%99b", {b'a': [b'a\xe2\x80\x99b']}), + + ("\u0141=\xE9", {'\u0141': ['\xE9']}), + ("%C5%81=%C3%A9", {'\u0141': ['\xE9']}), + ("%81=%A9", {'\ufffd': ['\ufffd']}), + (b"\xc5\x81=\xc3\xa9", {b'\xc5\x81': [b'\xc3\xa9']}), + (b"%C5%81=%C3%A9", {b'\xc5\x81': [b'\xc3\xa9']}), + (b"\x81=\xA9", {b'\x81': [b'\xa9']}), + (b"%81=%A9", {b'\x81': [b'\xa9']}), ] class UrlParseTestCase(unittest.TestCase): @@ -995,8 +1028,8 @@ def test_parse_qsl_encoding(self): def test_parse_qsl_max_num_fields(self): with self.assertRaises(ValueError): - urllib.parse.parse_qs('&'.join(['a=a']*11), max_num_fields=10) - urllib.parse.parse_qs('&'.join(['a=a']*10), max_num_fields=10) + urllib.parse.parse_qsl('&'.join(['a=a']*11), max_num_fields=10) + urllib.parse.parse_qsl('&'.join(['a=a']*10), max_num_fields=10) def test_parse_qs_separator(self): parse_qs_semicolon_cases = [ @@ -1039,6 +1072,30 @@ def test_parse_qsl_separator(self): result_bytes = urllib.parse.parse_qsl(orig, separator=b';') self.assertEqual(result_bytes, expect, "Error parsing %r" % orig) + def test_parse_qsl_bytes(self): + self.assertEqual(urllib.parse.parse_qsl(b'a=b'), [(b'a', b'b')]) + self.assertEqual(urllib.parse.parse_qsl(bytearray(b'a=b')), [(b'a', b'b')]) + self.assertEqual(urllib.parse.parse_qsl(memoryview(b'a=b')), [(b'a', b'b')]) + + def test_parse_qsl_false_value(self): + kwargs = dict(keep_blank_values=True, strict_parsing=True) + for x in '', b'', None, 0, 0.0, [], {}, memoryview(b''): + self.assertEqual(urllib.parse.parse_qsl(x, **kwargs), []) + self.assertRaises(ValueError, urllib.parse.parse_qsl, x, separator=1) + + def test_parse_qsl_errors(self): + self.assertRaises(TypeError, urllib.parse.parse_qsl, list(b'a=b')) + self.assertRaises(TypeError, urllib.parse.parse_qsl, iter(b'a=b')) + self.assertRaises(TypeError, urllib.parse.parse_qsl, 1) + self.assertRaises(TypeError, urllib.parse.parse_qsl, object()) + + for separator in '', b'', None, 0, 1, 0.0, 1.5: + with self.assertRaises(ValueError): + urllib.parse.parse_qsl('a=b', separator=separator) + with self.assertRaises(UnicodeEncodeError): + urllib.parse.parse_qsl(b'a=b', separator='\xa6') + with self.assertRaises(UnicodeDecodeError): + urllib.parse.parse_qsl('a=b', separator=b'\xa6') def test_urlencode_sequences(self): # Other tests incidentally urlencode things; test non-covered cases: diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 9cec1e87fd3c2d5..e177464c00f7a65 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -530,7 +530,14 @@ def test_uuid1(self): @support.requires_mac_ver(10, 5) @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') def test_uuid1_safe(self): - if not self.uuid._has_uuid_generate_time_safe: + try: + import _uuid + except ImportError: + has_uuid_generate_time_safe = False + else: + has_uuid_generate_time_safe = _uuid.has_uuid_generate_time_safe + + if not has_uuid_generate_time_safe or not self.uuid._generate_time_safe: self.skipTest('requires uuid_generate_time_safe(3)') u = self.uuid.uuid1() @@ -546,7 +553,6 @@ def mock_generate_time_safe(self, safe_value): """ if os.name != 'posix': self.skipTest('POSIX-only test') - self.uuid._load_system_functions() f = self.uuid._generate_time_safe if f is None: self.skipTest('need uuid._generate_time_safe') @@ -581,8 +587,7 @@ def test_uuid1_bogus_return_value(self): self.assertEqual(u.is_safe, self.uuid.SafeUUID.unknown) def test_uuid1_time(self): - with mock.patch.object(self.uuid, '_has_uuid_generate_time_safe', False), \ - mock.patch.object(self.uuid, '_generate_time_safe', None), \ + with mock.patch.object(self.uuid, '_generate_time_safe', None), \ mock.patch.object(self.uuid, '_last_timestamp', None), \ mock.patch.object(self.uuid, 'getnode', return_value=93328246233727), \ mock.patch('time.time_ns', return_value=1545052026752910643), \ @@ -590,8 +595,7 @@ def test_uuid1_time(self): u = self.uuid.uuid1() self.assertEqual(u, self.uuid.UUID('a7a55b92-01fc-11e9-94c5-54e1acf6da7f')) - with mock.patch.object(self.uuid, '_has_uuid_generate_time_safe', False), \ - mock.patch.object(self.uuid, '_generate_time_safe', None), \ + with mock.patch.object(self.uuid, '_generate_time_safe', None), \ mock.patch.object(self.uuid, '_last_timestamp', None), \ mock.patch('time.time_ns', return_value=1545052026752910643): u = self.uuid.uuid1(node=93328246233727, clock_seq=5317) diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index ba31beb81e80b09..f60c662d322e382 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -19,8 +19,9 @@ import tempfile from test.support import (captured_stdout, captured_stderr, skip_if_broken_multiprocessing_synchronize, verbose, - requires_subprocess, is_apple_mobile, is_emscripten, - is_wasi, requires_venv_with_pip, TEST_HOME_DIR, + requires_subprocess, is_android, is_apple_mobile, + is_emscripten, is_wasi, + requires_venv_with_pip, TEST_HOME_DIR, requires_resource, copy_python_src_ignore) from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree) import unittest @@ -39,10 +40,8 @@ or sys._base_executable != sys.executable, 'cannot run venv.create from within a venv on this platform') -# Skip tests on WASM platforms, plus iOS/tvOS/watchOS -if is_apple_mobile or is_emscripten or is_wasi: - raise unittest.SkipTest(f"venv tests not required on {sys.platform}") - +if is_android or is_apple_mobile or is_emscripten or is_wasi: + raise unittest.SkipTest("venv is not available on this platform") @requires_subprocess() def check_output(cmd, encoding=None): diff --git a/Lib/test/test_winconsoleio.py b/Lib/test/test_winconsoleio.py index 209e4464e1a5c0c..a10d63dfdc97539 100644 --- a/Lib/test/test_winconsoleio.py +++ b/Lib/test/test_winconsoleio.py @@ -43,6 +43,9 @@ def test_open_fd(self): self.assertEqual(0, f.fileno()) f.close() # multiple close should not crash f.close() + with self.assertWarns(RuntimeWarning): + with ConIO(False): + pass try: f = ConIO(1, 'w') @@ -55,6 +58,9 @@ def test_open_fd(self): self.assertEqual(1, f.fileno()) f.close() f.close() + with self.assertWarns(RuntimeWarning): + with ConIO(False): + pass try: f = ConIO(2, 'w') diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index c535d631bb646f7..14df482ba6c207f 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -121,10 +121,6 @@ """ -fails_with_expat_2_6_0 = (unittest.expectedFailure - if pyexpat.version_info >= (2, 6, 0) else - lambda test: test) - def checkwarnings(*filters, quiet=False): def decorator(test): def newtest(*args, **kwargs): @@ -1462,12 +1458,14 @@ def test_attlist_default(self): class XMLPullParserTest(unittest.TestCase): - def _feed(self, parser, data, chunk_size=None): + def _feed(self, parser, data, chunk_size=None, flush=False): if chunk_size is None: parser.feed(data) else: for i in range(0, len(data), chunk_size): parser.feed(data[i:i+chunk_size]) + if flush: + parser.flush() def assert_events(self, parser, expected, max_events=None): self.assertEqual( @@ -1485,34 +1483,32 @@ def assert_event_tags(self, parser, expected, max_events=None): self.assertEqual([(action, elem.tag) for action, elem in events], expected) - def test_simple_xml(self, chunk_size=None): + def test_simple_xml(self, chunk_size=None, flush=False): parser = ET.XMLPullParser() self.assert_event_tags(parser, []) - self._feed(parser, "\n", chunk_size) + self._feed(parser, "\n", chunk_size, flush) self.assert_event_tags(parser, []) self._feed(parser, "\n text\n", chunk_size) + self._feed(parser, ">\n", chunk_size, flush) self.assert_event_tags(parser, [('end', 'element')]) - self._feed(parser, "texttail\n", chunk_size) - self._feed(parser, "\n", chunk_size) + self._feed(parser, "texttail\n", chunk_size, flush) + self._feed(parser, "\n", chunk_size, flush) self.assert_event_tags(parser, [ ('end', 'element'), ('end', 'empty-element'), ]) - self._feed(parser, "\n", chunk_size) + self._feed(parser, "\n", chunk_size, flush) self.assert_event_tags(parser, [('end', 'root')]) self.assertIsNone(parser.close()) - @fails_with_expat_2_6_0 def test_simple_xml_chunk_1(self): - self.test_simple_xml(chunk_size=1) + self.test_simple_xml(chunk_size=1, flush=True) - @fails_with_expat_2_6_0 def test_simple_xml_chunk_5(self): - self.test_simple_xml(chunk_size=5) + self.test_simple_xml(chunk_size=5, flush=True) def test_simple_xml_chunk_22(self): self.test_simple_xml(chunk_size=22) @@ -1711,6 +1707,57 @@ def test_unknown_event(self): with self.assertRaises(ValueError): ET.XMLPullParser(events=('start', 'end', 'bogus')) + def test_flush_reparse_deferral_enabled(self): + if pyexpat.version_info < (2, 6, 0): + self.skipTest(f'Expat {pyexpat.version_info} does not ' + 'support reparse deferral') + + parser = ET.XMLPullParser(events=('start', 'end')) + + for chunk in (""): + parser.feed(chunk) + + self.assert_event_tags(parser, []) # i.e. no elements started + if ET is pyET: + self.assertTrue(parser._parser._parser.GetReparseDeferralEnabled()) + + parser.flush() + + self.assert_event_tags(parser, [('start', 'doc')]) + if ET is pyET: + self.assertTrue(parser._parser._parser.GetReparseDeferralEnabled()) + + parser.feed("") + parser.close() + + self.assert_event_tags(parser, [('end', 'doc')]) + + def test_flush_reparse_deferral_disabled(self): + parser = ET.XMLPullParser(events=('start', 'end')) + + for chunk in (""): + parser.feed(chunk) + + if pyexpat.version_info >= (2, 6, 0): + if not ET is pyET: + self.skipTest(f'XMLParser.(Get|Set)ReparseDeferralEnabled ' + 'methods not available in C') + parser._parser._parser.SetReparseDeferralEnabled(False) + + self.assert_event_tags(parser, []) # i.e. no elements started + if ET is pyET: + self.assertFalse(parser._parser._parser.GetReparseDeferralEnabled()) + + parser.flush() + + self.assert_event_tags(parser, [('start', 'doc')]) + if ET is pyET: + self.assertFalse(parser._parser._parser.GetReparseDeferralEnabled()) + + parser.feed("") + parser.close() + + self.assert_event_tags(parser, [('end', 'doc')]) # # xinclude tests (samples from appendix C of the xinclude specification) diff --git a/Lib/test/test_zipfile/_path/test_complexity.py b/Lib/test/test_zipfile/_path/test_complexity.py index 7050937738af18f..fd7ce57551b7a55 100644 --- a/Lib/test/test_zipfile/_path/test_complexity.py +++ b/Lib/test/test_zipfile/_path/test_complexity.py @@ -43,13 +43,17 @@ def make_zip_path(self, depth=1, width=1) -> zipfile.Path: @classmethod def make_names(cls, width, letters=string.ascii_lowercase): """ + >>> list(TestComplexity.make_names(1)) + ['a'] >>> list(TestComplexity.make_names(2)) ['a', 'b'] >>> list(TestComplexity.make_names(30)) ['aa', 'ab', ..., 'bd'] + >>> list(TestComplexity.make_names(17124)) + ['aaa', 'aab', ..., 'zip'] """ # determine how many products are needed to produce width - n_products = math.ceil(math.log(width, len(letters))) + n_products = max(1, math.ceil(math.log(width, len(letters)))) inputs = (letters,) * n_products combinations = itertools.product(*inputs) names = map(''.join, combinations) @@ -80,7 +84,7 @@ def test_glob_depth(self): max_n=100, min_n=1, ) - assert best <= big_o.complexities.Quadratic + assert best <= big_o.complexities.Linear @pytest.mark.flaky def test_glob_width(self): diff --git a/Lib/test/test_zipfile/_path/test_path.py b/Lib/test/test_zipfile/_path/test_path.py index 171ab6fdb5fc28e..df5b8c9d8fea40a 100644 --- a/Lib/test/test_zipfile/_path/test_path.py +++ b/Lib/test/test_zipfile/_path/test_path.py @@ -6,6 +6,7 @@ import sys import unittest import zipfile +import zipfile._path from ._functools import compose from ._itertools import Counter @@ -20,16 +21,6 @@ class itertools: Counter = Counter -def add_dirs(zf): - """ - Given a writable zip file zf, inject directory entries for - any directories implied by the presence of children. - """ - for name in zipfile.CompleteDirs._implied_dirs(zf.namelist()): - zf.writestr(name, b"") - return zf - - def build_alpharep_fixture(): """ Create a zip file with this structure: @@ -76,7 +67,7 @@ def build_alpharep_fixture(): alpharep_generators = [ Invoked.wrap(build_alpharep_fixture), - Invoked.wrap(compose(add_dirs, build_alpharep_fixture)), + Invoked.wrap(compose(zipfile._path.CompleteDirs.inject, build_alpharep_fixture)), ] pass_alpharep = parameterize(['alpharep'], alpharep_generators) @@ -210,11 +201,12 @@ def test_open_write(self): with zf.joinpath('file.txt').open('w', encoding="utf-8") as strm: strm.write('text file') - def test_open_extant_directory(self): + @pass_alpharep + def test_open_extant_directory(self, alpharep): """ Attempting to open a directory raises IsADirectoryError. """ - zf = zipfile.Path(add_dirs(build_alpharep_fixture())) + zf = zipfile.Path(alpharep) with self.assertRaises(IsADirectoryError): zf.joinpath('b').open() @@ -226,11 +218,12 @@ def test_open_binary_invalid_args(self, alpharep): with self.assertRaises(ValueError): root.joinpath('a.txt').open('rb', 'utf-8') - def test_open_missing_directory(self): + @pass_alpharep + def test_open_missing_directory(self, alpharep): """ Attempting to open a missing directory raises FileNotFoundError. """ - zf = zipfile.Path(add_dirs(build_alpharep_fixture())) + zf = zipfile.Path(alpharep) with self.assertRaises(FileNotFoundError): zf.joinpath('z').open() @@ -577,15 +570,3 @@ def test_getinfo_missing(self, alpharep): zipfile.Path(alpharep) with self.assertRaises(KeyError): alpharep.getinfo('does-not-exist') - - def test_root_folder_in_zipfile(self): - """ - gh-112795: Some tools or self constructed codes will add '/' folder to - the zip file, this is a strange behavior, but we should support it. - """ - in_memory_file = io.BytesIO() - zf = zipfile.ZipFile(in_memory_file, "w") - zf.mkdir('/') - zf.writestr('./a.txt', 'aaa') - tmpdir = pathlib.Path(self.fixtures.enter_context(temp_dir())) - zf.extractall(tmpdir) diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index 087fa8d65cc3368..368e60a37b05557 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -4,7 +4,6 @@ import io import itertools import os -import pathlib import posixpath import struct import subprocess @@ -25,7 +24,7 @@ captured_stdout, captured_stderr, requires_subprocess ) from test.support.os_helper import ( - TESTFN, unlink, rmtree, temp_dir, temp_cwd, fd_count + TESTFN, unlink, rmtree, temp_dir, temp_cwd, fd_count, FakePath ) @@ -160,7 +159,7 @@ def test_open(self): self.zip_open_test(f, self.compression) def test_open_with_pathlike(self): - path = pathlib.Path(TESTFN2) + path = FakePath(TESTFN2) self.zip_open_test(path, self.compression) with zipfile.ZipFile(path, "r", self.compression) as zipfp: self.assertIsInstance(zipfp.filename, str) @@ -447,6 +446,27 @@ def write(self, data): self.assertEqual(zipfp.read('file1'), b'data1') self.assertEqual(zipfp.read('file2'), b'data2') + def test_zipextfile_attrs(self): + fname = "somefile.txt" + with zipfile.ZipFile(TESTFN2, mode="w") as zipfp: + zipfp.writestr(fname, "bogus") + + with zipfile.ZipFile(TESTFN2, mode="r") as zipfp: + with zipfp.open(fname) as fid: + self.assertEqual(fid.name, fname) + self.assertRaises(io.UnsupportedOperation, fid.fileno) + self.assertEqual(fid.mode, 'r') + self.assertIs(fid.readable(), True) + self.assertIs(fid.writable(), False) + self.assertIs(fid.seekable(), True) + self.assertIs(fid.closed, False) + self.assertIs(fid.closed, True) + self.assertEqual(fid.name, fname) + self.assertEqual(fid.mode, 'r') + self.assertRaises(io.UnsupportedOperation, fid.fileno) + self.assertRaises(ValueError, fid.readable) + self.assertIs(fid.writable(), False) + self.assertRaises(ValueError, fid.seekable) def tearDown(self): unlink(TESTFN) @@ -578,17 +598,16 @@ def test_write_default_name(self): def test_io_on_closed_zipextfile(self): fname = "somefile.txt" - with zipfile.ZipFile(TESTFN2, mode="w") as zipfp: + with zipfile.ZipFile(TESTFN2, mode="w", compression=self.compression) as zipfp: zipfp.writestr(fname, "bogus") with zipfile.ZipFile(TESTFN2, mode="r") as zipfp: with zipfp.open(fname) as fid: fid.close() + self.assertIs(fid.closed, True) self.assertRaises(ValueError, fid.read) self.assertRaises(ValueError, fid.seek, 0) self.assertRaises(ValueError, fid.tell) - self.assertRaises(ValueError, fid.readable) - self.assertRaises(ValueError, fid.seekable) def test_write_to_readonly(self): """Check that trying to call write() on a readonly ZipFile object @@ -1285,6 +1304,21 @@ def test_issue44439(self): self.assertEqual(data.write(q), LENGTH) self.assertEqual(zip.getinfo('data').file_size, LENGTH) + def test_zipwritefile_attrs(self): + fname = "somefile.txt" + with zipfile.ZipFile(TESTFN2, mode="w", compression=self.compression) as zipfp: + with zipfp.open(fname, 'w') as fid: + self.assertRaises(io.UnsupportedOperation, fid.fileno) + self.assertIs(fid.readable(), False) + self.assertIs(fid.writable(), True) + self.assertIs(fid.seekable(), False) + self.assertIs(fid.closed, False) + self.assertIs(fid.closed, True) + self.assertRaises(io.UnsupportedOperation, fid.fileno) + self.assertIs(fid.readable(), False) + self.assertIs(fid.writable(), True) + self.assertIs(fid.seekable(), False) + class StoredWriterTests(AbstractWriterTests, unittest.TestCase): compression = zipfile.ZIP_STORED @@ -1487,7 +1521,7 @@ def test_write_pathlike(self): fp.write("print(42)\n") with TemporaryFile() as t, zipfile.PyZipFile(t, "w") as zipfp: - zipfp.writepy(pathlib.Path(TESTFN2) / "mod1.py") + zipfp.writepy(FakePath(os.path.join(TESTFN2, "mod1.py"))) names = zipfp.namelist() self.assertCompiledIn('mod1.py', names) finally: @@ -1545,7 +1579,7 @@ def test_extract_with_target(self): def test_extract_with_target_pathlike(self): with temp_dir() as extdir: - self._test_extract_with_target(pathlib.Path(extdir)) + self._test_extract_with_target(FakePath(extdir)) def test_extract_all(self): with temp_cwd(): @@ -1580,7 +1614,7 @@ def test_extract_all_with_target(self): def test_extract_all_with_target_pathlike(self): with temp_dir() as extdir: - self._test_extract_all_with_target(pathlib.Path(extdir)) + self._test_extract_all_with_target(FakePath(extdir)) def check_file(self, filename, content): self.assertTrue(os.path.isfile(filename)) @@ -1893,7 +1927,7 @@ def test_is_zip_erroneous_file(self): fp.write("this is not a legal zip file\n") self.assertFalse(zipfile.is_zipfile(TESTFN)) # - passing a path-like object - self.assertFalse(zipfile.is_zipfile(pathlib.Path(TESTFN))) + self.assertFalse(zipfile.is_zipfile(FakePath(TESTFN))) # - passing a file object with open(TESTFN, "rb") as fp: self.assertFalse(zipfile.is_zipfile(fp)) @@ -2999,6 +3033,17 @@ def test_create_directory_with_write(self): self.assertEqual(set(os.listdir(target)), {"directory", "directory2"}) + def test_root_folder_in_zipfile(self): + """ + gh-112795: Some tools or self constructed codes will add '/' folder to + the zip file, this is a strange behavior, but we should support it. + """ + in_memory_file = io.BytesIO() + zf = zipfile.ZipFile(in_memory_file, "w") + zf.mkdir('/') + zf.writestr('./a.txt', 'aaa') + zf.extractall(TESTFN2) + def tearDown(self): rmtree(TESTFN2) if os.path.exists(TESTFN): @@ -3013,7 +3058,7 @@ def test_from_file(self): self.assertEqual(zi.file_size, os.path.getsize(__file__)) def test_from_file_pathlike(self): - zi = zipfile.ZipInfo.from_file(pathlib.Path(__file__)) + zi = zipfile.ZipInfo.from_file(FakePath(__file__)) self.assertEqual(posixpath.basename(zi.filename), 'test_core.py') self.assertFalse(zi.is_dir()) self.assertEqual(zi.file_size, os.path.getsize(__file__)) diff --git a/Lib/threading.py b/Lib/threading.py index b6ff00acadd58fe..31ab77c92b1c205 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -36,8 +36,11 @@ _daemon_threads_allowed = _thread.daemon_threads_allowed _allocate_lock = _thread.allocate_lock _LockType = _thread.LockType -_set_sentinel = _thread._set_sentinel +_thread_shutdown = _thread._shutdown +_make_thread_handle = _thread._make_thread_handle +_ThreadHandle = _thread._ThreadHandle get_ident = _thread.get_ident +_get_main_thread_ident = _thread._get_main_thread_ident _is_main_interpreter = _thread._is_main_interpreter try: get_native_id = _thread.get_native_id @@ -847,25 +850,6 @@ def _newname(name_template): _limbo = {} _dangling = WeakSet() -# Set of Thread._tstate_lock locks of non-daemon threads used by _shutdown() -# to wait until all Python thread states get deleted: -# see Thread._set_tstate_lock(). -_shutdown_locks_lock = _allocate_lock() -_shutdown_locks = set() - -def _maintain_shutdown_locks(): - """ - Drop any shutdown locks that don't correspond to running threads anymore. - - Calling this from time to time avoids an ever-growing _shutdown_locks - set when Thread objects are not joined explicitly. See bpo-37788. - - This must be called with _shutdown_locks_lock acquired. - """ - # If a lock was released, the corresponding thread has exited - to_remove = [lock for lock in _shutdown_locks if not lock.locked()] - _shutdown_locks.difference_update(to_remove) - # Main class for threads @@ -930,11 +914,8 @@ class is implemented. self._ident = None if _HAVE_THREAD_NATIVE_ID: self._native_id = None - self._tstate_lock = None - self._join_lock = None - self._handle = None + self._handle = _ThreadHandle() self._started = Event() - self._is_stopped = False self._initialized = True # Copy of sys.stderr used by self._invoke_excepthook() self._stderr = _sys.stderr @@ -948,31 +929,18 @@ def _after_fork(self, new_ident=None): if new_ident is not None: # This thread is alive. self._ident = new_ident - if self._handle is not None: - assert self._handle.ident == new_ident - # bpo-42350: If the fork happens when the thread is already stopped - # (ex: after threading._shutdown() has been called), _tstate_lock - # is None. Do nothing in this case. - if self._tstate_lock is not None: - self._tstate_lock._at_fork_reinit() - self._tstate_lock.acquire() - if self._join_lock is not None: - self._join_lock._at_fork_reinit() + assert self._handle.ident == new_ident else: - # This thread isn't alive after fork: it doesn't have a tstate - # anymore. - self._is_stopped = True - self._tstate_lock = None - self._join_lock = None - self._handle = None + # Otherwise, the thread is dead, Jim. _PyThread_AfterFork() + # already marked our handle done. + pass def __repr__(self): assert self._initialized, "Thread.__init__() was not called" status = "initial" if self._started.is_set(): status = "started" - self.is_alive() # easy way to get ._is_stopped set when appropriate - if self._is_stopped: + if self._handle.is_done(): status = "stopped" if self._daemonic: status += " daemon" @@ -996,13 +964,12 @@ def start(self): if self._started.is_set(): raise RuntimeError("threads can only be started once") - self._join_lock = _allocate_lock() - with _active_limbo_lock: _limbo[self] = self try: # Start joinable thread - self._handle = _start_joinable_thread(self._bootstrap) + _start_joinable_thread(self._bootstrap, handle=self._handle, + daemon=self.daemon) except Exception: with _active_limbo_lock: del _limbo[self] @@ -1053,23 +1020,9 @@ def _set_ident(self): def _set_native_id(self): self._native_id = get_native_id() - def _set_tstate_lock(self): - """ - Set a lock object which will be released by the interpreter when - the underlying thread state (see pystate.h) gets deleted. - """ - self._tstate_lock = _set_sentinel() - self._tstate_lock.acquire() - - if not self.daemon: - with _shutdown_locks_lock: - _maintain_shutdown_locks() - _shutdown_locks.add(self._tstate_lock) - def _bootstrap_inner(self): try: self._set_ident() - self._set_tstate_lock() if _HAVE_THREAD_NATIVE_ID: self._set_native_id() self._started.set() @@ -1089,33 +1042,6 @@ def _bootstrap_inner(self): finally: self._delete() - def _stop(self): - # After calling ._stop(), .is_alive() returns False and .join() returns - # immediately. ._tstate_lock must be released before calling ._stop(). - # - # Normal case: C code at the end of the thread's life - # (release_sentinel in _threadmodule.c) releases ._tstate_lock, and - # that's detected by our ._wait_for_tstate_lock(), called by .join() - # and .is_alive(). Any number of threads _may_ call ._stop() - # simultaneously (for example, if multiple threads are blocked in - # .join() calls), and they're not serialized. That's harmless - - # they'll just make redundant rebindings of ._is_stopped and - # ._tstate_lock. Obscure: we rebind ._tstate_lock last so that the - # "assert self._is_stopped" in ._wait_for_tstate_lock() always works - # (the assert is executed only if ._tstate_lock is None). - # - # Special case: _main_thread releases ._tstate_lock via this - # module's _shutdown() function. - lock = self._tstate_lock - if lock is not None: - assert not lock.locked() - self._is_stopped = True - self._tstate_lock = None - if not self.daemon: - with _shutdown_locks_lock: - # Remove our lock and other released locks from _shutdown_locks - _maintain_shutdown_locks() - def _delete(self): "Remove current thread from the dict of currently running threads." with _active_limbo_lock: @@ -1156,55 +1082,12 @@ def join(self, timeout=None): if self is current_thread(): raise RuntimeError("cannot join current thread") - if timeout is None: - self._wait_for_tstate_lock() - else: - # the behavior of a negative timeout isn't documented, but - # historically .join(timeout=x) for x<0 has acted as if timeout=0 - self._wait_for_tstate_lock(timeout=max(timeout, 0)) - - if self._is_stopped: - self._join_os_thread() + # the behavior of a negative timeout isn't documented, but + # historically .join(timeout=x) for x<0 has acted as if timeout=0 + if timeout is not None: + timeout = max(timeout, 0) - def _join_os_thread(self): - join_lock = self._join_lock - if join_lock is None: - return - with join_lock: - # Calling join() multiple times would raise an exception - # in one of the callers. - if self._handle is not None: - self._handle.join() - self._handle = None - # No need to keep this around - self._join_lock = None - - def _wait_for_tstate_lock(self, block=True, timeout=-1): - # Issue #18808: wait for the thread state to be gone. - # At the end of the thread's life, after all knowledge of the thread - # is removed from C data structures, C code releases our _tstate_lock. - # This method passes its arguments to _tstate_lock.acquire(). - # If the lock is acquired, the C code is done, and self._stop() is - # called. That sets ._is_stopped to True, and ._tstate_lock to None. - lock = self._tstate_lock - if lock is None: - # already determined that the C code is done - assert self._is_stopped - return - - try: - if lock.acquire(block, timeout): - lock.release() - self._stop() - except: - if lock.locked(): - # bpo-45274: lock.acquire() acquired the lock, but the function - # was interrupted with an exception before reaching the - # lock.release(). It can happen if a signal handler raises an - # exception, like CTRL+C which raises KeyboardInterrupt. - lock.release() - self._stop() - raise + self._handle.join(timeout) @property def name(self): @@ -1255,13 +1138,7 @@ def is_alive(self): """ assert self._initialized, "Thread.__init__() not called" - if self._is_stopped or not self._started.is_set(): - return False - self._wait_for_tstate_lock(False) - if not self._is_stopped: - return True - self._join_os_thread() - return False + return self._started.is_set() and not self._handle.is_done() @property def daemon(self): @@ -1470,9 +1347,9 @@ class _MainThread(Thread): def __init__(self): Thread.__init__(self, name="MainThread", daemon=False) - self._set_tstate_lock() self._started.set() - self._set_ident() + self._ident = _get_main_thread_ident() + self._handle = _make_thread_handle(self._ident) if _HAVE_THREAD_NATIVE_ID: self._set_native_id() with _active_limbo_lock: @@ -1520,17 +1397,15 @@ def __init__(self): daemon=_daemon_threads_allowed()) self._started.set() self._set_ident() + self._handle = _make_thread_handle(self._ident) if _HAVE_THREAD_NATIVE_ID: self._set_native_id() with _active_limbo_lock: _active[self._ident] = self _DeleteDummyThreadOnDel(self) - def _stop(self): - pass - def is_alive(self): - if not self._is_stopped and self._started.is_set(): + if not self._handle.is_done() and self._started.is_set(): return True raise RuntimeError("thread is not alive") @@ -1542,7 +1417,6 @@ def _after_fork(self, new_ident=None): self.__class__ = _MainThread self._name = 'MainThread' self._daemonic = False - self._set_tstate_lock() Thread._after_fork(self, new_ident=new_ident) @@ -1641,12 +1515,11 @@ def _shutdown(): """ Wait until the Python thread state of all non-daemon threads get deleted. """ - # Obscure: other threads may be waiting to join _main_thread. That's - # dubious, but some code does it. We can't wait for C code to release - # the main thread's tstate_lock - that won't happen until the interpreter - # is nearly dead. So we release it here. Note that just calling _stop() - # isn't enough: other threads may already be waiting on _tstate_lock. - if _main_thread._is_stopped and _is_main_interpreter(): + # Obscure: other threads may be waiting to join _main_thread. That's + # dubious, but some code does it. We can't wait for it to be marked as done + # normally - that won't happen until the interpreter is nearly dead. So + # mark it done here. + if _main_thread._handle.is_done() and _is_main_interpreter(): # _shutdown() was already called return @@ -1658,42 +1531,11 @@ def _shutdown(): for atexit_call in reversed(_threading_atexits): atexit_call() - # Main thread - if _main_thread.ident == get_ident(): - tlock = _main_thread._tstate_lock - # The main thread isn't finished yet, so its thread state lock can't - # have been released. - assert tlock is not None - if tlock.locked(): - # It should have been released already by - # _PyInterpreterState_SetNotRunningMain(), but there may be - # embedders that aren't calling that yet. - tlock.release() - _main_thread._stop() - else: - # bpo-1596321: _shutdown() must be called in the main thread. - # If the threading module was not imported by the main thread, - # _main_thread is the thread which imported the threading module. - # In this case, ignore _main_thread, similar behavior than for threads - # spawned by C libraries or using _thread.start_new_thread(). - pass - - # Join all non-deamon threads - while True: - with _shutdown_locks_lock: - locks = list(_shutdown_locks) - _shutdown_locks.clear() - - if not locks: - break - - for lock in locks: - # mimic Thread.join() - lock.acquire() - lock.release() - - # new threads can be spawned while we were waiting for the other - # threads to complete + if _is_main_interpreter(): + _main_thread._handle._set_done() + + # Wait for all non-daemon threads to exit. + _thread_shutdown() def main_thread(): @@ -1713,7 +1555,6 @@ def _after_fork(): # Reset _active_limbo_lock, in case we forked while the lock was held # by another (non-forked) thread. http://bugs.python.org/issue874900 global _active_limbo_lock, _main_thread - global _shutdown_locks_lock, _shutdown_locks _active_limbo_lock = RLock() # fork() only copied the current thread; clear references to others. @@ -1729,10 +1570,6 @@ def _after_fork(): _main_thread = current - # reset _shutdown() locks: threads re-register their _tstate_lock below - _shutdown_locks_lock = _allocate_lock() - _shutdown_locks = set() - with _active_limbo_lock: # Dangling thread instances must still have their locks reset, # because someone may join() them. @@ -1749,7 +1586,6 @@ def _after_fork(): else: # All the others are already stopped. thread._after_fork() - thread._stop() _limbo.clear() _active.clear() diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 175bfbd7d912d2e..fd7b48e35199900 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -3166,11 +3166,16 @@ def __init__(self, master=None, cnf={}, **kw): Widget.__init__(self, master, 'checkbutton', cnf, kw) def _setup(self, master, cnf): + # Because Checkbutton defaults to a variable with the same name as + # the widget, Checkbutton default names must be globally unique, + # not just unique within the parent widget. if not cnf.get('name'): global _checkbutton_count name = self.__class__.__name__.lower() _checkbutton_count += 1 - cnf['name'] = f'!{name}{_checkbutton_count}' + # To avoid collisions with ttk.Checkbutton, use the different + # name template. + cnf['name'] = f'!{name}-{_checkbutton_count}' super()._setup(master, cnf) def deselect(self): diff --git a/Lib/typing.py b/Lib/typing.py index 914ddeaf504cd0e..533b64062834d28 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -144,6 +144,7 @@ 'override', 'ParamSpecArgs', 'ParamSpecKwargs', + 'ReadOnly', 'Required', 'reveal_type', 'runtime_checkable', @@ -308,19 +309,33 @@ def _unpack_args(args): newargs.append(arg) return newargs -def _deduplicate(params): +def _deduplicate(params, *, unhashable_fallback=False): # Weed out strict duplicates, preserving the first of each occurrence. - all_params = set(params) - if len(all_params) < len(params): - new_params = [] - for t in params: - if t in all_params: - new_params.append(t) - all_params.remove(t) - params = new_params - assert not all_params, all_params - return params - + try: + return dict.fromkeys(params) + except TypeError: + if not unhashable_fallback: + raise + # Happens for cases like `Annotated[dict, {'x': IntValidator()}]` + return _deduplicate_unhashable(params) + +def _deduplicate_unhashable(unhashable_params): + new_unhashable = [] + for t in unhashable_params: + if t not in new_unhashable: + new_unhashable.append(t) + return new_unhashable + +def _compare_args_orderless(first_args, second_args): + first_unhashable = _deduplicate_unhashable(first_args) + second_unhashable = _deduplicate_unhashable(second_args) + t = list(second_unhashable) + try: + for elem in first_unhashable: + t.remove(elem) + except ValueError: + return False + return not t def _remove_dups_flatten(parameters): """Internal helper for Union creation and substitution. @@ -335,7 +350,7 @@ def _remove_dups_flatten(parameters): else: params.append(p) - return tuple(_deduplicate(params)) + return tuple(_deduplicate(params, unhashable_fallback=True)) def _flatten_literal_params(parameters): @@ -639,7 +654,7 @@ class Starship: Note that ClassVar is not a class itself, and should not be used with isinstance() or issubclass(). """ - item = _type_check(parameters, f'{self} accepts only single type.') + item = _type_check(parameters, f'{self} accepts only single type.', allow_special_forms=True) return _GenericAlias(self, (item,)) @_SpecialForm @@ -661,7 +676,7 @@ class FastConnector(Connection): There is no runtime checking of these properties. """ - item = _type_check(parameters, f'{self} accepts only single type.') + item = _type_check(parameters, f'{self} accepts only single type.', allow_special_forms=True) return _GenericAlias(self, (item,)) @_SpecialForm @@ -866,7 +881,7 @@ def __init__(self, arg, is_argument=True, module=None, *, is_class=False): # If we do `def f(*args: *Ts)`, then we'll have `arg = '*Ts'`. # Unfortunately, this isn't a valid expression on its own, so we # do the unpacking manually. - if arg[0] == '*': + if arg.startswith('*'): arg_to_compile = f'({arg},)[0]' # E.g. (*Ts,)[0] or (*tuple[int, int],)[0] else: arg_to_compile = arg @@ -1555,7 +1570,10 @@ def copy_with(self, params): def __eq__(self, other): if not isinstance(other, (_UnionGenericAlias, types.UnionType)): return NotImplemented - return set(self.__args__) == set(other.__args__) + try: # fast path + return set(self.__args__) == set(other.__args__) + except TypeError: # not hashable, slow path + return _compare_args_orderless(self.__args__, other.__args__) def __hash__(self): return hash(frozenset(self.__args__)) @@ -2284,7 +2302,7 @@ def _strip_annotations(t): """Strip the annotations from a given type.""" if isinstance(t, _AnnotatedAlias): return _strip_annotations(t.__origin__) - if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired): + if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired, ReadOnly): return _strip_annotations(t.__args__[0]) if isinstance(t, _GenericAlias): stripped_args = tuple(_strip_annotations(a) for a in t.__args__) @@ -2905,6 +2923,28 @@ def _namedtuple_mro_entries(bases): NamedTuple.__mro_entries__ = _namedtuple_mro_entries +def _get_typeddict_qualifiers(annotation_type): + while True: + annotation_origin = get_origin(annotation_type) + if annotation_origin is Annotated: + annotation_args = get_args(annotation_type) + if annotation_args: + annotation_type = annotation_args[0] + else: + break + elif annotation_origin is Required: + yield Required + (annotation_type,) = get_args(annotation_type) + elif annotation_origin is NotRequired: + yield NotRequired + (annotation_type,) = get_args(annotation_type) + elif annotation_origin is ReadOnly: + yield ReadOnly + (annotation_type,) = get_args(annotation_type) + else: + break + + class _TypedDictMeta(type): def __new__(cls, name, bases, ns, total=True): """Create a new typed dict class object. @@ -2938,6 +2978,8 @@ def __new__(cls, name, bases, ns, total=True): } required_keys = set() optional_keys = set() + readonly_keys = set() + mutable_keys = set() for base in bases: annotations.update(base.__dict__.get('__annotations__', {})) @@ -2950,18 +2992,15 @@ def __new__(cls, name, bases, ns, total=True): required_keys -= base_optional optional_keys |= base_optional + readonly_keys.update(base.__dict__.get('__readonly_keys__', ())) + mutable_keys.update(base.__dict__.get('__mutable_keys__', ())) + annotations.update(own_annotations) for annotation_key, annotation_type in own_annotations.items(): - annotation_origin = get_origin(annotation_type) - if annotation_origin is Annotated: - annotation_args = get_args(annotation_type) - if annotation_args: - annotation_type = annotation_args[0] - annotation_origin = get_origin(annotation_type) - - if annotation_origin is Required: + qualifiers = set(_get_typeddict_qualifiers(annotation_type)) + if Required in qualifiers: is_required = True - elif annotation_origin is NotRequired: + elif NotRequired in qualifiers: is_required = False else: is_required = total @@ -2973,6 +3012,17 @@ def __new__(cls, name, bases, ns, total=True): optional_keys.add(annotation_key) required_keys.discard(annotation_key) + if ReadOnly in qualifiers: + if annotation_key in mutable_keys: + raise TypeError( + f"Cannot override mutable key {annotation_key!r}" + " with read-only key" + ) + readonly_keys.add(annotation_key) + else: + mutable_keys.add(annotation_key) + readonly_keys.discard(annotation_key) + assert required_keys.isdisjoint(optional_keys), ( f"Required keys overlap with optional keys in {name}:" f" {required_keys=}, {optional_keys=}" @@ -2980,6 +3030,8 @@ def __new__(cls, name, bases, ns, total=True): tp_dict.__annotations__ = annotations tp_dict.__required_keys__ = frozenset(required_keys) tp_dict.__optional_keys__ = frozenset(optional_keys) + tp_dict.__readonly_keys__ = frozenset(readonly_keys) + tp_dict.__mutable_keys__ = frozenset(mutable_keys) tp_dict.__total__ = total return tp_dict @@ -3038,6 +3090,14 @@ class Point2D(TypedDict): y: NotRequired[int] # the "y" key can be omitted See PEP 655 for more details on Required and NotRequired. + + The ReadOnly special form can be used + to mark individual keys as immutable for type checkers:: + + class DatabaseUser(TypedDict): + id: ReadOnly[int] # the "id" key must not be modified + username: str # the "username" key can be changed + """ if fields is _sentinel or fields is None: import warnings @@ -3114,6 +3174,26 @@ class Movie(TypedDict): return _GenericAlias(self, (item,)) +@_SpecialForm +def ReadOnly(self, parameters): + """A special typing construct to mark an item of a TypedDict as read-only. + + For example:: + + class Movie(TypedDict): + title: ReadOnly[str] + year: int + + def mutate_movie(m: Movie) -> None: + m["year"] = 1992 # allowed + m["title"] = "The Matrix" # typechecker error + + There is no runtime checking for this property. + """ + item = _type_check(parameters, f'{self._name} accepts only a single type.') + return _GenericAlias(self, (item,)) + + class NewType: """NewType creates simple unique types with almost zero runtime overhead. diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 93f4d9743ed2fa9..1799e9bbf58592f 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -573,7 +573,7 @@ def __get_return_value(self): if self._mock_delegate is not None: ret = self._mock_delegate.return_value - if ret is DEFAULT: + if ret is DEFAULT and self._mock_wraps is None: ret = self._get_child_mock( _new_parent=self, _new_name='()' ) @@ -1234,6 +1234,9 @@ def _execute_mock_call(self, /, *args, **kwargs): if self._mock_return_value is not DEFAULT: return self.return_value + if self._mock_delegate and self._mock_delegate.return_value is not DEFAULT: + return self.return_value + if self._mock_wraps is not None: return self._mock_wraps(*args, **kwargs) @@ -2785,9 +2788,12 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, if _parent is not None and not instance: _parent._mock_children[_name] = mock + wrapped = kwargs.get('wraps') + if is_type and not instance and 'return_value' not in kwargs: mock.return_value = create_autospec(spec, spec_set, instance=True, - _name='()', _parent=mock) + _name='()', _parent=mock, + wraps=wrapped) for entry in dir(spec): if _is_magic(entry): @@ -2809,6 +2815,9 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, continue kwargs = {'spec': original} + # Wrap child attributes also. + if wrapped and hasattr(wrapped, entry): + kwargs.update(wraps=original) if spec_set: kwargs = {'spec_set': original} diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py index c129b0d7971d71d..fc9e7c99f283bed 100644 --- a/Lib/urllib/parse.py +++ b/Lib/urllib/parse.py @@ -763,42 +763,48 @@ def parse_qsl(qs, keep_blank_values=False, strict_parsing=False, Returns a list, as G-d intended. """ - qs, _coerce_result = _coerce_args(qs) - separator, _ = _coerce_args(separator) - if not separator or (not isinstance(separator, (str, bytes))): + if not separator or not isinstance(separator, (str, bytes)): raise ValueError("Separator must be of type string or bytes.") + if isinstance(qs, str): + if not isinstance(separator, str): + separator = str(separator, 'ascii') + eq = '=' + def _unquote(s): + return unquote_plus(s, encoding=encoding, errors=errors) + else: + if not qs: + return [] + # Use memoryview() to reject integers and iterables, + # acceptable by the bytes constructor. + qs = bytes(memoryview(qs)) + if isinstance(separator, str): + separator = bytes(separator, 'ascii') + eq = b'=' + def _unquote(s): + return unquote_to_bytes(s.replace(b'+', b' ')) + + if not qs: + return [] # If max_num_fields is defined then check that the number of fields # is less than max_num_fields. This prevents a memory exhaustion DOS # attack via post bodies with many fields. if max_num_fields is not None: - num_fields = 1 + qs.count(separator) if qs else 0 + num_fields = 1 + qs.count(separator) if max_num_fields < num_fields: raise ValueError('Max number of fields exceeded') r = [] - query_args = qs.split(separator) if qs else [] - for name_value in query_args: - if not name_value and not strict_parsing: - continue - nv = name_value.split('=', 1) - if len(nv) != 2: - if strict_parsing: + for name_value in qs.split(separator): + if name_value or strict_parsing: + name, has_eq, value = name_value.partition(eq) + if not has_eq and strict_parsing: raise ValueError("bad query field: %r" % (name_value,)) - # Handle case of a control-name with no equal sign - if keep_blank_values: - nv.append('') - else: - continue - if len(nv[1]) or keep_blank_values: - name = nv[0].replace('+', ' ') - name = unquote(name, encoding=encoding, errors=errors) - name = _coerce_result(name) - value = nv[1].replace('+', ' ') - value = unquote(value, encoding=encoding, errors=errors) - value = _coerce_result(value) - r.append((name, value)) + if value or keep_blank_values: + name = _unquote(name) + value = _unquote(value) + r.append((name, value)) return r def unquote_plus(string, encoding='utf-8', errors='replace'): diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py index bca594420f6d9d2..d22af6618d80f16 100644 --- a/Lib/urllib/request.py +++ b/Lib/urllib/request.py @@ -2563,6 +2563,7 @@ def _proxy_bypass_macosx_sysconf(host, proxy_settings): } """ from fnmatch import fnmatch + from ipaddress import AddressValueError, IPv4Address hostonly, port = _splitport(host) @@ -2579,20 +2580,17 @@ def ip2num(ipAddr): return True hostIP = None + try: + hostIP = int(IPv4Address(hostonly)) + except AddressValueError: + pass for value in proxy_settings.get('exceptions', ()): # Items in the list are strings like these: *.local, 169.254/16 if not value: continue m = re.match(r"(\d+(?:\.\d+)*)(/\d+)?", value) - if m is not None: - if hostIP is None: - try: - hostIP = socket.gethostbyname(hostonly) - hostIP = ip2num(hostIP) - except OSError: - continue - + if m is not None and hostIP is not None: base = ip2num(m.group(1)) mask = m.group(2) if mask is None: @@ -2615,6 +2613,31 @@ def ip2num(ipAddr): return False +# Same as _proxy_bypass_macosx_sysconf, testable on all platforms +def _proxy_bypass_winreg_override(host, override): + """Return True if the host should bypass the proxy server. + + The proxy override list is obtained from the Windows + Internet settings proxy override registry value. + + An example of a proxy override value is: + "www.example.com;*.example.net; 192.168.0.1" + """ + from fnmatch import fnmatch + + host, _ = _splitport(host) + proxy_override = override.split(';') + for test in proxy_override: + test = test.strip() + # "" should bypass the proxy server for all intranet addresses + if test == '': + if '.' not in host: + return True + elif fnmatch(host, test): + return True + return False + + if sys.platform == 'darwin': from _scproxy import _get_proxy_settings, _get_proxies @@ -2713,7 +2736,7 @@ def proxy_bypass_registry(host): import winreg except ImportError: # Std modules, so should be around - but you never know! - return 0 + return False try: internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Internet Settings') @@ -2723,40 +2746,10 @@ def proxy_bypass_registry(host): 'ProxyOverride')[0]) # ^^^^ Returned as Unicode but problems if not converted to ASCII except OSError: - return 0 + return False if not proxyEnable or not proxyOverride: - return 0 - # try to make a host list from name and IP address. - rawHost, port = _splitport(host) - host = [rawHost] - try: - addr = socket.gethostbyname(rawHost) - if addr != rawHost: - host.append(addr) - except OSError: - pass - try: - fqdn = socket.getfqdn(rawHost) - if fqdn != rawHost: - host.append(fqdn) - except OSError: - pass - # make a check value list from the registry entry: replace the - # '' string by the localhost entry and the corresponding - # canonical entry. - proxyOverride = proxyOverride.split(';') - # now check if we match one of the registry values. - for test in proxyOverride: - if test == '': - if '.' not in rawHost: - return 1 - test = test.replace(".", r"\.") # mask dots - test = test.replace("*", r".*") # change glob sequence - test = test.replace("?", r".") # change glob char - for val in host: - if re.match(test, val, re.I): - return 1 - return 0 + return False + return _proxy_bypass_winreg_override(host, proxyOverride) def proxy_bypass(host): """Return True, if host should be bypassed. diff --git a/Lib/uuid.py b/Lib/uuid.py index 470bc0d68597ab7..c286eac38e1ef45 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -53,13 +53,16 @@ __author__ = 'Ka-Ping Yee ' # The recognized platforms - known behaviors -if sys.platform in ('win32', 'darwin', 'emscripten', 'wasi'): +if sys.platform in {'win32', 'darwin', 'emscripten', 'wasi'}: _AIX = _LINUX = False +elif sys.platform == 'linux': + _LINUX = True + _AIX = False else: import platform _platform_system = platform.system() _AIX = _platform_system == 'AIX' - _LINUX = _platform_system == 'Linux' + _LINUX = _platform_system in ('Linux', 'Android') _MAC_DELIM = b':' _MAC_OMITS_LEADING_ZEROES = False @@ -564,32 +567,16 @@ def _netstat_getnode(): # This works on AIX and might work on Tru64 UNIX. return _find_mac_under_heading('netstat', '-ian', b'Address') -def _ipconfig_getnode(): - """[DEPRECATED] Get the hardware address on Windows.""" - # bpo-40501: UuidCreateSequential() is now the only supported approach - return _windll_getnode() - -def _netbios_getnode(): - """[DEPRECATED] Get the hardware address on Windows.""" - # bpo-40501: UuidCreateSequential() is now the only supported approach - return _windll_getnode() - # Import optional C extension at toplevel, to help disabling it when testing try: import _uuid _generate_time_safe = getattr(_uuid, "generate_time_safe", None) _UuidCreate = getattr(_uuid, "UuidCreate", None) - _has_uuid_generate_time_safe = _uuid.has_uuid_generate_time_safe except ImportError: _uuid = None _generate_time_safe = None _UuidCreate = None - _has_uuid_generate_time_safe = None - - -def _load_system_functions(): - """[DEPRECATED] Platform-specific functions loaded at import time""" def _unix_getnode(): diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 636e8ca459d1091..0424c53b7ccaf9c 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -418,12 +418,18 @@ def register_X_browsers(): if shutil.which("gio"): register("gio", None, BackgroundBrowser(["gio", "open", "--", "%s"])) - # Equivalent of gio open before 2015 - if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gvfs-open"): + xdg_desktop = os.getenv("XDG_CURRENT_DESKTOP", "").split(":") + + # The default GNOME3 browser + if (("GNOME" in xdg_desktop or + "GNOME_DESKTOP_SESSION_ID" in os.environ) and + shutil.which("gvfs-open")): register("gvfs-open", None, BackgroundBrowser("gvfs-open")) # The default KDE browser - if "KDE_FULL_SESSION" in os.environ and shutil.which("kfmclient"): + if (("KDE" in xdg_desktop or + "KDE_FULL_SESSION" in os.environ) and + shutil.which("kfmclient")): register("kfmclient", Konqueror, Konqueror("kfmclient")) # Common symbolic link for the default X11 browser diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index a37fead41b750e4..9e15d34d22aa6c4 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -1320,6 +1320,11 @@ def read_events(self): else: yield event + def flush(self): + if self._parser is None: + raise ValueError("flush() called after end of stream") + self._parser.flush() + def XML(text, parser=None): """Parse XML document from string constant. @@ -1726,6 +1731,15 @@ def close(self): del self.parser, self._parser del self.target, self._target + def flush(self): + was_enabled = self.parser.GetReparseDeferralEnabled() + try: + self.parser.SetReparseDeferralEnabled(False) + self.parser.Parse(b"", False) + except self._error as v: + self._raiseerror(v) + finally: + self.parser.SetReparseDeferralEnabled(was_enabled) # -------------------------------------------------------------------- # C14N 2.0 diff --git a/Lib/xml/sax/expatreader.py b/Lib/xml/sax/expatreader.py index b9ad52692db8dd6..ba3c1e98517429b 100644 --- a/Lib/xml/sax/expatreader.py +++ b/Lib/xml/sax/expatreader.py @@ -214,6 +214,20 @@ def feed(self, data, isFinal=False): # FIXME: when to invoke error()? self._err_handler.fatalError(exc) + def flush(self): + if self._parser is None: + return + + was_enabled = self._parser.GetReparseDeferralEnabled() + try: + self._parser.SetReparseDeferralEnabled(False) + self._parser.Parse(b"", False) + except expat.error as e: + exc = SAXParseException(expat.ErrorString(e.code), e, self) + self._err_handler.fatalError(exc) + finally: + self._parser.SetReparseDeferralEnabled(was_enabled) + def _close_source(self): source = self._source try: diff --git a/Lib/zipfile/_path/__init__.py b/Lib/zipfile/_path/__init__.py index 78c413563bb2b1b..4c167563b6b7621 100644 --- a/Lib/zipfile/_path/__init__.py +++ b/Lib/zipfile/_path/__init__.py @@ -5,8 +5,9 @@ import contextlib import pathlib import re +import sys -from .glob import translate +from .glob import Translator __all__ = ['Path'] @@ -147,6 +148,16 @@ def make(cls, source): source.__class__ = cls return source + @classmethod + def inject(cls, zf: zipfile.ZipFile) -> zipfile.ZipFile: + """ + Given a writable zip file zf, inject directory entries for + any directories implied by the presence of children. + """ + for name in cls._implied_dirs(zf.namelist()): + zf.writestr(name, b"") + return zf + class FastLookup(CompleteDirs): """ @@ -168,8 +179,10 @@ def _name_set(self): def _extract_text_encoding(encoding=None, *args, **kwargs): - # stacklevel=3 so that the caller of the caller see any warning. - return io.text_encoding(encoding, 3), args, kwargs + # compute stack level so that the caller of the caller sees any warning. + is_pypy = sys.implementation.name == 'pypy' + stack_level = 3 + is_pypy + return io.text_encoding(encoding, stack_level), args, kwargs class Path: @@ -194,13 +207,13 @@ class Path: Path accepts the zipfile object itself or a filename - >>> root = Path(zf) + >>> path = Path(zf) From there, several path operations are available. Directory iteration (including the zip file itself): - >>> a, b = root.iterdir() + >>> a, b = path.iterdir() >>> a Path('mem/abcde.zip', 'a.txt') >>> b @@ -238,16 +251,38 @@ class Path: 'mem/abcde.zip/b/c.txt' At the root, ``name``, ``filename``, and ``parent`` - resolve to the zipfile. Note these attributes are not - valid and will raise a ``ValueError`` if the zipfile - has no filename. + resolve to the zipfile. - >>> root.name + >>> str(path) + 'mem/abcde.zip/' + >>> path.name 'abcde.zip' - >>> str(root.filename).replace(os.sep, posixpath.sep) - 'mem/abcde.zip' - >>> str(root.parent) + >>> path.filename == pathlib.Path('mem/abcde.zip') + True + >>> str(path.parent) 'mem' + + If the zipfile has no filename, such attribtues are not + valid and accessing them will raise an Exception. + + >>> zf.filename = None + >>> path.name + Traceback (most recent call last): + ... + TypeError: ... + + >>> path.filename + Traceback (most recent call last): + ... + TypeError: ... + + >>> path.parent + Traceback (most recent call last): + ... + TypeError: ... + + # workaround python/cpython#106763 + >>> pass """ __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})" @@ -364,8 +399,10 @@ def glob(self, pattern): raise ValueError(f"Unacceptable pattern: {pattern!r}") prefix = re.escape(self.at) - matches = re.compile(prefix + translate(pattern)).fullmatch - return map(self._next, filter(matches, self.root.namelist())) + tr = Translator(seps='/') + matches = re.compile(prefix + tr.translate(pattern)).fullmatch + names = (data.filename for data in self.root.filelist) + return map(self._next, filter(matches, names)) def rglob(self, pattern): return self.glob(f'**/{pattern}') diff --git a/Lib/zipfile/_path/glob.py b/Lib/zipfile/_path/glob.py index 4a2e665e27078aa..69c41d77c3f6549 100644 --- a/Lib/zipfile/_path/glob.py +++ b/Lib/zipfile/_path/glob.py @@ -1,18 +1,97 @@ +import os import re -def translate(pattern): - r""" - Given a glob pattern, produce a regex that matches it. +_default_seps = os.sep + str(os.altsep) * bool(os.altsep) - >>> translate('*.txt') - '[^/]*\\.txt' - >>> translate('a?txt') - 'a.txt' - >>> translate('**/*') - '.*/[^/]*' + +class Translator: + """ + >>> Translator('xyz') + Traceback (most recent call last): + ... + AssertionError: Invalid separators + + >>> Translator('') + Traceback (most recent call last): + ... + AssertionError: Invalid separators """ - return ''.join(map(replace, separate(pattern))) + + seps: str + + def __init__(self, seps: str = _default_seps): + assert seps and set(seps) <= set(_default_seps), "Invalid separators" + self.seps = seps + + def translate(self, pattern): + """ + Given a glob pattern, produce a regex that matches it. + """ + return self.extend(self.translate_core(pattern)) + + def extend(self, pattern): + r""" + Extend regex for pattern-wide concerns. + + Apply '(?s:)' to create a non-matching group that + matches newlines (valid on Unix). + + Append '\Z' to imply fullmatch even when match is used. + """ + return rf'(?s:{pattern})\Z' + + def translate_core(self, pattern): + r""" + Given a glob pattern, produce a regex that matches it. + + >>> t = Translator() + >>> t.translate_core('*.txt').replace('\\\\', '') + '[^/]*\\.txt' + >>> t.translate_core('a?txt') + 'a[^/]txt' + >>> t.translate_core('**/*').replace('\\\\', '') + '.*/[^/][^/]*' + """ + self.restrict_rglob(pattern) + return ''.join(map(self.replace, separate(self.star_not_empty(pattern)))) + + def replace(self, match): + """ + Perform the replacements for a match from :func:`separate`. + """ + return match.group('set') or ( + re.escape(match.group(0)) + .replace('\\*\\*', r'.*') + .replace('\\*', rf'[^{re.escape(self.seps)}]*') + .replace('\\?', r'[^/]') + ) + + def restrict_rglob(self, pattern): + """ + Raise ValueError if ** appears in anything but a full path segment. + + >>> Translator().translate('**foo') + Traceback (most recent call last): + ... + ValueError: ** must appear alone in a path segment + """ + seps_pattern = rf'[{re.escape(self.seps)}]+' + segments = re.split(seps_pattern, pattern) + if any('**' in segment and segment != '**' for segment in segments): + raise ValueError("** must appear alone in a path segment") + + def star_not_empty(self, pattern): + """ + Ensure that * will not match an empty segment. + """ + + def handle_segment(match): + segment = match.group(0) + return '?*' if segment == '*' else segment + + not_seps_pattern = rf'[^{re.escape(self.seps)}]+' + return re.sub(not_seps_pattern, handle_segment, pattern) def separate(pattern): @@ -25,16 +104,3 @@ def separate(pattern): ['a', '[?]', 'txt'] """ return re.finditer(r'([^\[]+)|(?P[\[].*?[\]])|([\[][^\]]*$)', pattern) - - -def replace(match): - """ - Perform the replacements for a match from :func:`separate`. - """ - - return match.group('set') or ( - re.escape(match.group(0)) - .replace('\\*\\*', r'.*') - .replace('\\*', r'[^/]*') - .replace('\\?', r'.') - ) diff --git a/Mac/BuildScript/backport_gh92603_fix.patch b/Mac/BuildScript/backport_gh92603_fix.patch deleted file mode 100644 index 9a37b0296503409..000000000000000 --- a/Mac/BuildScript/backport_gh92603_fix.patch +++ /dev/null @@ -1,82 +0,0 @@ -Accepted upstream for release in Tk 8.6.14: -https://core.tcl-lang.org/tk/info/cf3830280b - ---- tk8.6.13/macosx/tkMacOSXWindowEvent.c.orig -+++ tk8.6.13-patched/macosx/tkMacOSXWindowEvent.c -@@ -239,8 +239,8 @@ extern NSString *NSWindowDidOrderOffScreenNotification; - if (winPtr) { - TKContentView *view = [window contentView]; - --#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 -- if (@available(macOS 10.15, *)) { -+#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 -+ if (@available(macOS 10.14, *)) { - [view viewDidChangeEffectiveAppearance]; - } - #endif -@@ -1237,29 +1237,8 @@ static const char *const accentNames[] = { - } else if (effectiveAppearanceName == NSAppearanceNameDarkAqua) { - TkSendVirtualEvent(tkwin, "DarkAqua", NULL); - } -- if ([NSApp macOSVersion] < 101500) { -- -- /* -- * Mojave cannot handle the KVO shenanigans that we need for the -- * highlight and accent color notifications. -- */ -- -- return; -- } - if (!defaultColor) { - defaultColor = [NSApp macOSVersion] < 110000 ? "Blue" : "Multicolor"; -- preferences = [[NSUserDefaults standardUserDefaults] retain]; -- -- /* -- * AppKit calls this method when the user changes the Accent Color -- * but not when the user changes the Highlight Color. So we register -- * to receive KVO notifications for Highlight Color as well. -- */ -- -- [preferences addObserver:self -- forKeyPath:@"AppleHighlightColor" -- options:NSKeyValueObservingOptionNew -- context:NULL]; - } - NSString *accent = [preferences stringForKey:@"AppleAccentColor"]; - NSArray *words = [[preferences stringForKey:@"AppleHighlightColor"] ---- tk8.6.13/macosx/tkMacOSXWm.c.orig -+++ tk8.6.13-patched/macosx/tkMacOSXWm.c -@@ -1289,6 +1289,11 @@ TkWmDeadWindow( - [NSApp _setMainWindow:nil]; - } - [deadNSWindow close]; -+#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 -+ NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; -+ [preferences removeObserver:deadNSWindow.contentView -+ forKeyPath:@"AppleHighlightColor"]; -+#endif - [deadNSWindow release]; - - #if DEBUG_ZOMBIES > 1 -@@ -6763,6 +6768,21 @@ TkMacOSXMakeRealWindowExist( - } - TKContentView *contentView = [[TKContentView alloc] - initWithFrame:NSZeroRect]; -+#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 -+ NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; -+ -+ /* -+ * AppKit calls the viewDidChangeEffectiveAppearance method when the -+ * user changes the Accent Color but not when the user changes the -+ * Highlight Color. So we register to receive KVO notifications for -+ * Highlight Color as well. -+ */ -+ -+ [preferences addObserver:contentView -+ forKeyPath:@"AppleHighlightColor" -+ options:NSKeyValueObservingOptionNew -+ context:NULL]; -+#endif - [window setContentView:contentView]; - [contentView release]; - [window setDelegate:NSApp]; diff --git a/Mac/BuildScript/build-installer.py b/Mac/BuildScript/build-installer.py index 0af90563cbbb2b7..b2f8c77e0c1a73b 100755 --- a/Mac/BuildScript/build-installer.py +++ b/Mac/BuildScript/build-installer.py @@ -264,11 +264,11 @@ def library_recipes(): tk_patches = ['backport_gh71383_fix.patch', 'tk868_on_10_8_10_9.patch', 'backport_gh110950_fix.patch'] else: - tcl_tk_ver='8.6.13' - tcl_checksum='43a1fae7412f61ff11de2cfd05d28cfc3a73762f354a417c62370a54e2caf066' + tcl_tk_ver='8.6.14' + tcl_checksum='5880225babf7954c58d4fb0f5cf6279104ce1cd6aa9b71e9a6322540e1c4de66' - tk_checksum='2e65fa069a23365440a3c56c556b8673b5e32a283800d8d9b257e3f584ce0675' - tk_patches = ['backport_gh92603_fix.patch', 'backport_gh71383_fix.patch', 'backport_gh110950_fix.patch'] + tk_checksum='8ffdb720f47a6ca6107eac2dd877e30b0ef7fac14f3a84ebbd0b3612cee41a94' + tk_patches = [] base_url = "https://prdownloads.sourceforge.net/tcl/{what}{version}-src.tar.gz" diff --git a/Makefile.pre.in b/Makefile.pre.in index 4c1a18602b2d0bb..3cf4de08a0c8427 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -446,6 +446,7 @@ PYTHON_OBJS= \ Python/object_stack.o \ Python/optimizer.o \ Python/optimizer_analysis.o \ + Python/optimizer_symbols.o \ Python/parking_lot.o \ Python/pathconfig.o \ Python/preconfig.o \ @@ -652,6 +653,30 @@ all: @DEF_MAKE_ALL_RULE@ # all. .PHONY: all +# Provide quick help for common Makefile targets. +.PHONY: help +help: + @echo "Run 'make' to build the Python executable and extension modules" + @echo "" + @echo "or 'make ' where is one of:" + @echo " test run the test suite" + @echo " install install built files" + @echo " regen-all regenerate a number of generated source files" + @echo " clinic run Argument Clinic over source files" + @echo "" + @echo " clean to remove build files" + @echo " distclean 'clean' + remove other generated files (patch, exe, etc)" + @echo "" + @echo " recheck rerun configure with last cmdline options" + @echo " reindent reindent .py files in Lib directory" + @echo " tags build a tags file (useful for Emacs and other editors)" + @echo " list-targets list all targets in the Makefile" + +# Display a full list of Makefile targets +.PHONY: list-targets +list-targets: + @grep -E '^[A-Za-z][-A-Za-z0-9]+:' Makefile | awk -F : '{print $$1}' + .PHONY: build_all build_all: check-clean-src $(BUILDPYTHON) platform sharedmods \ gdbhooks Programs/_testembed scripts checksharedmods rundsymutil @@ -858,11 +883,9 @@ $(LIBRARY): $(LIBRARY_OBJS) $(AR) $(ARFLAGS) $@ $(LIBRARY_OBJS) libpython$(LDVERSION).so: $(LIBRARY_OBJS) $(DTRACE_OBJS) - if test $(INSTSONAME) != $(LDLIBRARY); then \ - $(BLDSHARED) -Wl,-h$(INSTSONAME) -o $(INSTSONAME) $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM); \ + $(BLDSHARED) -Wl,-h$(INSTSONAME) -o $(INSTSONAME) $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM) + if test $(INSTSONAME) != $@; then \ $(LN) -f $(INSTSONAME) $@; \ - else \ - $(BLDSHARED) -o $@ $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM); \ fi libpython3.so: libpython$(LDVERSION).so @@ -912,6 +935,21 @@ $(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK): \ $(LN) -fsn Versions/Current/$(PYTHONFRAMEWORK) $(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK) $(LN) -fsn Versions/Current/Resources $(PYTHONFRAMEWORKDIR)/Resources +# This rule is for iOS, which requires an annoyingly just slighly different +# format for frameworks to macOS. It *doesn't* use a versioned framework, and +# the Info.plist must be in the root of the framework. +$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK): \ + $(LIBRARY) \ + $(RESSRCDIR)/Info.plist + $(INSTALL) -d -m $(DIRMODE) $(PYTHONFRAMEWORKDIR) + $(CC) -o $(LDLIBRARY) $(PY_CORE_LDFLAGS) -dynamiclib \ + -all_load $(LIBRARY) \ + -install_name $(PYTHONFRAMEWORKINSTALLNAMEPREFIX)/$(PYTHONFRAMEWORK) \ + -compatibility_version $(VERSION) \ + -current_version $(VERSION) \ + -framework CoreFoundation $(LIBS); + $(INSTALL_DATA) $(RESSRCDIR)/Info.plist $(PYTHONFRAMEWORKDIR)/Info.plist + # This rule builds the Cygwin Python DLL and import library if configured # for a shared core library; otherwise, this rule is a noop. $(DLLLIBRARY) libpython$(LDVERSION).dll.a: $(LIBRARY_OBJS) @@ -1858,39 +1896,72 @@ Objects/obmalloc.o: $(srcdir)/Objects/mimalloc/alloc.c \ Objects/mimalloc/page.o: $(srcdir)/Objects/mimalloc/page-queue.c + +# Regenerate various files from Python/bytecodes.c +# Pass CASESFLAG=-l to insert #line directives in the output + .PHONY: regen-cases -regen-cases: - # Regenerate various files from Python/bytecodes.c - # Pass CASESFLAG=-l to insert #line directives in the output +regen-cases: \ + regen-opcode-ids regen-opcode-targets regen-uop-ids regen-opcode-metadata-py \ + regen-generated-cases regen-executor-cases regen-optimizer-cases \ + regen-opcode-metadata regen-uop-metadata + +.PHONY: regen-opcode-ids +regen-opcode-ids: $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/opcode_id_generator.py \ -o $(srcdir)/Include/opcode_ids.h.new $(srcdir)/Python/bytecodes.c + $(UPDATE_FILE) $(srcdir)/Include/opcode_ids.h $(srcdir)/Include/opcode_ids.h.new + +.PHONY: regen-opcode-targets +regen-opcode-targets: $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/target_generator.py \ -o $(srcdir)/Python/opcode_targets.h.new $(srcdir)/Python/bytecodes.c + $(UPDATE_FILE) $(srcdir)/Python/opcode_targets.h $(srcdir)/Python/opcode_targets.h.new + +.PHONY: regen-uop-ids +regen-uop-ids: $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/uop_id_generator.py \ -o $(srcdir)/Include/internal/pycore_uop_ids.h.new $(srcdir)/Python/bytecodes.c + $(UPDATE_FILE) $(srcdir)/Include/internal/pycore_uop_ids.h $(srcdir)/Include/internal/pycore_uop_ids.h.new + +.PHONY: regen-opcode-metadata-py +regen-opcode-metadata-py: $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/py_metadata_generator.py \ -o $(srcdir)/Lib/_opcode_metadata.py.new $(srcdir)/Python/bytecodes.c + $(UPDATE_FILE) $(srcdir)/Lib/_opcode_metadata.py $(srcdir)/Lib/_opcode_metadata.py.new + +.PHONY: regen-generated-cases +regen-generated-cases: $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/tier1_generator.py \ -o $(srcdir)/Python/generated_cases.c.h.new $(srcdir)/Python/bytecodes.c + $(UPDATE_FILE) $(srcdir)/Python/generated_cases.c.h $(srcdir)/Python/generated_cases.c.h.new + +.PHONY: regen-executor-cases +regen-executor-cases: $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/tier2_generator.py \ -o $(srcdir)/Python/executor_cases.c.h.new $(srcdir)/Python/bytecodes.c - $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/tier2_abstract_generator.py \ - -o $(srcdir)/Python/tier2_redundancy_eliminator_cases.c.h.new \ - $(srcdir)/Python/tier2_redundancy_eliminator_bytecodes.c \ + $(UPDATE_FILE) $(srcdir)/Python/executor_cases.c.h $(srcdir)/Python/executor_cases.c.h.new + +.PHONY: regen-optimizer-cases +regen-optimizer-cases: + $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/optimizer_generator.py \ + -o $(srcdir)/Python/optimizer_cases.c.h.new \ + $(srcdir)/Python/optimizer_bytecodes.c \ $(srcdir)/Python/bytecodes.c + $(UPDATE_FILE) $(srcdir)/Python/optimizer_cases.c.h $(srcdir)/Python/optimizer_cases.c.h.new + +.PHONY: regen-opcode-metadata +regen-opcode-metadata: $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/opcode_metadata_generator.py \ -o $(srcdir)/Include/internal/pycore_opcode_metadata.h.new $(srcdir)/Python/bytecodes.c + $(UPDATE_FILE) $(srcdir)/Include/internal/pycore_opcode_metadata.h $(srcdir)/Include/internal/pycore_opcode_metadata.h.new + +.PHONY: regen-uop-metadata +regen-uop-metadata: $(PYTHON_FOR_REGEN) $(srcdir)/Tools/cases_generator/uop_metadata_generator.py -o \ $(srcdir)/Include/internal/pycore_uop_metadata.h.new $(srcdir)/Python/bytecodes.c - $(UPDATE_FILE) $(srcdir)/Python/generated_cases.c.h $(srcdir)/Python/generated_cases.c.h.new - $(UPDATE_FILE) $(srcdir)/Include/opcode_ids.h $(srcdir)/Include/opcode_ids.h.new - $(UPDATE_FILE) $(srcdir)/Include/internal/pycore_uop_ids.h $(srcdir)/Include/internal/pycore_uop_ids.h.new - $(UPDATE_FILE) $(srcdir)/Python/opcode_targets.h $(srcdir)/Python/opcode_targets.h.new - $(UPDATE_FILE) $(srcdir)/Include/internal/pycore_opcode_metadata.h $(srcdir)/Include/internal/pycore_opcode_metadata.h.new $(UPDATE_FILE) $(srcdir)/Include/internal/pycore_uop_metadata.h $(srcdir)/Include/internal/pycore_uop_metadata.h.new - $(UPDATE_FILE) $(srcdir)/Python/executor_cases.c.h $(srcdir)/Python/executor_cases.c.h.new - $(UPDATE_FILE) $(srcdir)/Python/tier2_redundancy_eliminator_cases.c.h $(srcdir)/Python/tier2_redundancy_eliminator_cases.c.h.new - $(UPDATE_FILE) $(srcdir)/Lib/_opcode_metadata.py $(srcdir)/Lib/_opcode_metadata.py.new + Python/compile.o: $(srcdir)/Include/internal/pycore_opcode_metadata.h @@ -1912,7 +1983,7 @@ Python/optimizer.o: \ Python/optimizer_analysis.o: \ $(srcdir)/Include/internal/pycore_opcode_metadata.h \ $(srcdir)/Include/internal/pycore_optimizer.h \ - $(srcdir)/Python/tier2_redundancy_eliminator_cases.c.h + $(srcdir)/Python/optimizer_cases.c.h Python/frozen.o: $(FROZEN_FILES_OUT) @@ -1979,6 +2050,42 @@ testuniversal: all $(RUNSHARED) /usr/libexec/oah/translate \ ./$(BUILDPYTHON) -E -m test -j 0 -u all $(TESTOPTS) +# Run the test suite on the iOS simulator. Must be run on a macOS machine with +# a full Xcode install that has an iPhone SE (3rd edition) simulator available. +# This must be run *after* a `make install` has completed the build. The +# `--with-framework-name` argument *cannot* be used when configuring the build. +XCFOLDER=iOSTestbed.$(MULTIARCH).$(shell date +%s) +XCRESULT=$(XCFOLDER)/$(MULTIARCH).xcresult +.PHONY: testios +testios: + @if test "$(MACHDEP)" != "ios"; then \ + echo "Cannot run the iOS testbed for a non-iOS build."; \ + exit 1;\ + fi + @if test "$(findstring -iphonesimulator,$(MULTIARCH))" != "-iphonesimulator"; then \ + echo "Cannot run the iOS testbed for non-simulator builds."; \ + exit 1;\ + fi + @if test $(PYTHONFRAMEWORK) != "Python"; then \ + echo "Cannot run the iOS testbed with a non-default framework name."; \ + exit 1;\ + fi + @if ! test -d $(PYTHONFRAMEWORKPREFIX); then \ + echo "Cannot find a finalized iOS Python.framework. Have you run 'make install' to finalize the framework build?"; \ + exit 1;\ + fi + # Copy the testbed project into the build folder + cp -r $(srcdir)/iOS/testbed $(XCFOLDER) + # Copy the framework from the install location to the testbed project. + cp -r $(PYTHONFRAMEWORKPREFIX)/* $(XCFOLDER)/Python.xcframework/ios-arm64_x86_64-simulator + # Run the test suite for the Xcode project, targeting the iOS simulator. + # If the suite fails, extract and print the console output, then re-raise the failure + if ! xcodebuild test -project $(XCFOLDER)/iOSTestbed.xcodeproj -scheme "iOSTestbed" -destination "platform=iOS Simulator,name=iPhone SE (3rd Generation)" -resultBundlePath $(XCRESULT) ; then \ + xcrun xcresulttool get --path $(XCRESULT) --id $$(xcrun xcresulttool get --path $(XCRESULT) --format json | $(PYTHON_FOR_BUILD) -c "import sys, json; result = json.load(sys.stdin); print(result['actions']['_values'][0]['actionResult']['logRef']['id']['_value'])"); \ + echo ; \ + exit 1; \ + fi + # Like test, but using --slow-ci which enables all test resources and use # longer timeout. Run an optional pybuildbot.identify script to include # information about the build environment. @@ -2607,10 +2714,11 @@ frameworkinstall: install # only have to cater for the structural bits of the framework. .PHONY: frameworkinstallframework -frameworkinstallframework: frameworkinstallstructure install frameworkinstallmaclib +frameworkinstallframework: @FRAMEWORKINSTALLFIRST@ install frameworkinstallmaclib -.PHONY: frameworkinstallstructure -frameworkinstallstructure: $(LDLIBRARY) +# macOS uses a versioned frameworks structure that includes a full install +.PHONY: frameworkinstallversionedstructure +frameworkinstallversionedstructure: $(LDLIBRARY) @if test "$(PYTHONFRAMEWORKDIR)" = no-framework; then \ echo Not configured with --enable-framework; \ exit 1; \ @@ -2631,6 +2739,27 @@ frameworkinstallstructure: $(LDLIBRARY) $(LN) -fsn Versions/Current/Resources $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Resources $(INSTALL_SHARED) $(LDLIBRARY) $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/$(LDLIBRARY) +# iOS/tvOS/watchOS uses a non-versioned framework with Info.plist in the +# framework root, no .lproj data, and only stub compilation assistance binaries +.PHONY: frameworkinstallunversionedstructure +frameworkinstallunversionedstructure: $(LDLIBRARY) + @if test "$(PYTHONFRAMEWORKDIR)" = no-framework; then \ + echo Not configured with --enable-framework; \ + exit 1; \ + else true; \ + fi + if test -d $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include; then \ + echo "Clearing stale header symlink directory"; \ + rm -rf $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include; \ + fi + $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR) + sed 's/%VERSION%/'"`$(RUNSHARED) $(PYTHON_FOR_BUILD) -c 'import platform; print(platform.python_version())'`"'/g' < $(RESSRCDIR)/Info.plist > $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Info.plist + $(INSTALL_SHARED) $(LDLIBRARY) $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/$(LDLIBRARY) + $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(BINDIR) + for file in $(srcdir)/$(RESSRCDIR)/bin/* ; do \ + $(INSTALL) -m $(EXEMODE) $$file $(DESTDIR)$(BINDIR); \ + done + # This installs Mac/Lib into the framework # Install a number of symlinks to keep software that expects a normal unix # install (which includes python-config) happy. @@ -2671,6 +2800,19 @@ frameworkaltinstallunixtools: frameworkinstallextras: cd Mac && $(MAKE) installextras DESTDIR="$(DESTDIR)" +# On iOS, bin/lib can't live inside the framework; include needs to be called +# "Headers", but *must* be in the framework, and *not* include the `python3.X` +# subdirectory. The install has put these folders in the same folder as +# Python.framework; Move the headers to their final framework-compatible home. +.PHONY: frameworkinstallmobileheaders +frameworkinstallmobileheaders: frameworkinstallunversionedstructure inclinstall + if test -d $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers; then \ + echo "Removing old framework headers"; \ + rm -rf $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers; \ + fi + mv "$(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include/python$(VERSION)" "$(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Headers" + $(LN) -fs "../$(PYTHONFRAMEWORKDIR)/Headers" "$(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/include/python$(VERSION)" + # Build the toplevel Makefile Makefile.pre: $(srcdir)/Makefile.pre.in config.status CONFIG_FILES=Makefile.pre CONFIG_HEADERS= ./config.status @@ -2798,6 +2940,10 @@ clean-retain-profile: pycremoval -find build -type f -a ! -name '*.gc??' -exec rm -f {} ';' -rm -f Include/pydtrace_probes.h -rm -f profile-gen-stamp + -rm -rf iOS/testbed/Python.xcframework/ios-*/bin + -rm -rf iOS/testbed/Python.xcframework/ios-*/lib + -rm -rf iOS/testbed/Python.xcframework/ios-*/include + -rm -rf iOS/testbed/Python.xcframework/ios-*/Python.framework .PHONY: profile-removal profile-removal: @@ -2823,6 +2969,8 @@ clobber: clean config.cache config.log pyconfig.h Modules/config.c -rm -rf build platform -rm -rf $(PYTHONFRAMEWORKDIR) + -rm -rf iOS/Frameworks + -rm -rf iOSTestbed.* -rm -f python-config.py python-config -rm -rf cross-build diff --git a/Misc/ACKS b/Misc/ACKS index f01c7a70a65dc52..03e458d170d2b83 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -470,6 +470,7 @@ Allen Downey Cesar Douady Dean Draayer Fred L. Drake, Jr. +Mehdi Drissi Derk Drukker John DuBois Paul Dubois diff --git a/Misc/NEWS.d/3.10.0a6.rst b/Misc/NEWS.d/3.10.0a6.rst index c379b968c9885b9..bad3528084897b5 100644 --- a/Misc/NEWS.d/3.10.0a6.rst +++ b/Misc/NEWS.d/3.10.0a6.rst @@ -158,7 +158,7 @@ tests that are unrelated to :class:`ProcessPoolExecutor` on those platforms. .. nonce: hsCNgX .. section: Core and Builtins -If :func:`object.__ipow__` returns :const:`NotImplemented`, the operator +If :func:`object.__ipow__` returns :data:`NotImplemented`, the operator will correctly fall back to :func:`object.__pow__` and :func:`object.__rpow__` as expected. diff --git a/Misc/NEWS.d/3.11.0a1.rst b/Misc/NEWS.d/3.11.0a1.rst index e8d4a02a11e0f94..754e782dfe661b9 100644 --- a/Misc/NEWS.d/3.11.0a1.rst +++ b/Misc/NEWS.d/3.11.0a1.rst @@ -660,7 +660,7 @@ Karabas. .. section: Core and Builtins Parameter substitution of the union type with wrong types now raises -``TypeError`` instead of returning ``NotImplemented``. +``TypeError`` instead of returning :data:`NotImplemented`. .. diff --git a/Misc/NEWS.d/3.13.0a5.rst b/Misc/NEWS.d/3.13.0a5.rst new file mode 100644 index 000000000000000..fb0163eed67aeba --- /dev/null +++ b/Misc/NEWS.d/3.13.0a5.rst @@ -0,0 +1,1163 @@ +.. date: 2024-02-18-03-14-40 +.. gh-issue: 115398 +.. nonce: tzvxH8 +.. release date: 2024-03-12 +.. section: Security + +Allow controlling Expat >=2.6.0 reparse deferral (CVE-2023-52425) by adding +five new methods: + +* :meth:`xml.etree.ElementTree.XMLParser.flush` +* :meth:`xml.etree.ElementTree.XMLPullParser.flush` +* :meth:`xml.parsers.expat.xmlparser.GetReparseDeferralEnabled` +* :meth:`xml.parsers.expat.xmlparser.SetReparseDeferralEnabled` +* :meth:`xml.sax.expatreader.ExpatParser.flush` + +.. + +.. date: 2024-01-26-22-14-09 +.. gh-issue: 114572 +.. nonce: t1QMQD +.. section: Security + +:meth:`ssl.SSLContext.cert_store_stats` and +:meth:`ssl.SSLContext.get_ca_certs` now correctly lock access to the +certificate store, when the :class:`ssl.SSLContext` is shared across +multiple threads. + +.. + +.. date: 2024-03-11-22-24-59 +.. gh-issue: 116604 +.. nonce: LCEzAT +.. section: Core and Builtins + +Respect the status of the garbage collector when indirect calls are made via +:c:func:`PyErr_CheckSignals` and the evaluation breaker. Patch by Pablo +Galindo + +.. + +.. date: 2024-03-09-11-10-53 +.. gh-issue: 112087 +.. nonce: nbI0Pw +.. section: Core and Builtins + +:class:`list` is now compatible with the implementation of :pep:`703`. + +.. + +.. date: 2024-03-05-22-00-58 +.. gh-issue: 116381 +.. nonce: 0Nq9iO +.. section: Core and Builtins + +Add specialization for ``CONTAINS_OP``. + +.. + +.. date: 2024-03-04-10-19-51 +.. gh-issue: 116296 +.. nonce: gvtxyU +.. section: Core and Builtins + +Fix possible refleak in :meth:`!object.__reduce__` internal error handling. + +.. + +.. date: 2024-02-22-16-17-53 +.. gh-issue: 115823 +.. nonce: c1TreJ +.. section: Core and Builtins + +Properly calculate error ranges in the parser when raising +:exc:`SyntaxError` exceptions caused by invalid byte sequences. Patch by +Pablo Galindo + +.. + +.. date: 2024-02-22-11-33-20 +.. gh-issue: 115778 +.. nonce: jksd1D +.. section: Core and Builtins + +Add ``tierN`` annotation for instruction definition in interpreter DSL. + +.. + +.. date: 2024-02-20-18-49-02 +.. gh-issue: 115733 +.. nonce: 51Zb85 +.. section: Core and Builtins + +Fix crash when calling ``next()`` on exhausted list iterators. + +.. + +.. date: 2024-02-20-12-46-20 +.. gh-issue: 115700 +.. nonce: KLJ5r4 +.. section: Core and Builtins + +The regen-cases build stage now works on Windows. + +.. + +.. date: 2024-02-14-23-50-43 +.. gh-issue: 115347 +.. nonce: VkHvQC +.. section: Core and Builtins + +Fix bug where docstring was replaced by a redundant NOP when Python is run +with ``-OO``. + +.. + +.. date: 2024-02-12-23-29-17 +.. gh-issue: 115323 +.. nonce: 3t6687 +.. section: Core and Builtins + +Make error message more meaningful for when :meth:`bytearray.extend` is +called with a :class:`str` object. + +.. + +.. date: 2024-02-09-18-59-22 +.. gh-issue: 112175 +.. nonce: qglugr +.. section: Core and Builtins + +Every ``PyThreadState`` now has its own ``eval_breaker``, allowing specific +threads to be interrupted. + +.. + +.. date: 2024-02-08-16-01-18 +.. gh-issue: 115154 +.. nonce: ji96FV +.. section: Core and Builtins + +Fix a bug that was causing the :func:`tokenize.untokenize` function to +handle unicode named literals incorrectly. Patch by Pablo Galindo + +.. + +.. date: 2024-01-28-02-46-12 +.. gh-issue: 112433 +.. nonce: FUX-nT +.. section: Core and Builtins + +Add ability to force alignment of :mod:`ctypes.Structure` by way of the new +``_align_`` attribute on the class. + +.. + +.. date: 2023-07-16-15-02-47 +.. gh-issue: 104090 +.. nonce: oMjNa9 +.. section: Core and Builtins + +The multiprocessing resource tracker now exits with non-zero status code if +a resource leak was detected. It still exits with status code 0 otherwise. + +.. + +.. date: 2023-06-16-21-29-06 +.. gh-issue: 105858 +.. nonce: Q7h0EV +.. section: Core and Builtins + +Improve the constructors for :mod:`ast` nodes. Arguments of list types now +default to an empty list if omitted, and optional fields default to +``None``. AST nodes now have an ``__annotations__`` attribute with the +expected types of their attributes. Passing unrecognized extra arguments to +AST nodes is deprecated and will become an error in Python 3.15. Omitting a +required argument to an AST node is deprecated and will become an error in +Python 3.15. Patch by Jelle Zijlstra. + +.. + +.. date: 2023-02-13-11-36-50 +.. gh-issue: 101860 +.. nonce: CKCMbC +.. section: Core and Builtins + +Expose ``__name__`` attribute on property. + +.. + +.. date: 2022-09-04-16-51-56 +.. gh-issue: 96497 +.. nonce: HTBuIL +.. section: Core and Builtins + +Fix incorrect resolution of mangled class variables used in assignment +expressions in comprehensions. + +.. + +.. date: 2024-03-11-12-11-10 +.. gh-issue: 116600 +.. nonce: FcNBy_ +.. section: Library + +Fix :func:`repr` for global :class:`~enum.Flag` members. + +.. + +.. date: 2024-03-07-21-57-50 +.. gh-issue: 116349 +.. nonce: fD2pbP +.. section: Library + +:func:`platform.java_ver` is deprecated and will be removed in 3.15. It was +largely untested, had a confusing API, and was only useful for Jython +support. + +.. + +.. date: 2024-03-05-20-53-34 +.. gh-issue: 116143 +.. nonce: sww6Zl +.. section: Library + +Fix a race in pydoc ``_start_server``, eliminating a window in which +``_start_server`` can return a thread that is "serving" but without a +``docserver`` set. + +.. + +.. date: 2024-03-05-14-34-22 +.. gh-issue: 116127 +.. nonce: 5uktu3 +.. section: Library + +:mod:`typing`: implement :pep:`705` which adds :data:`typing.ReadOnly` +support to :class:`typing.TypedDict`. + +.. + +.. date: 2024-03-05-02-09-18 +.. gh-issue: 116325 +.. nonce: FmlBYv +.. section: Library + +:mod:`typing`: raise :exc:`SyntaxError` instead of :exc:`AttributeError` on +forward references as empty strings. + +.. + +.. date: 2024-03-02-11-31-49 +.. gh-issue: 115957 +.. nonce: C-3Z_U +.. section: Library + +When ``asyncio.TaskGroup.create_task`` is called on an inactive +``asyncio.TaskGroup``, the given coroutine will be closed (which prevents a +``RuntimeWarning``). + +.. + +.. date: 2024-03-01-14-22-08 +.. gh-issue: 115978 +.. nonce: r2ePTo +.. section: Library + +Disable preadv(), readv(), pwritev(), and writev() on WASI. + +Under wasmtime for WASI 0.2, these functions don't pass test_posix +(https://github.com/bytecodealliance/wasmtime/issues/7830). + +.. + +.. date: 2024-03-01-11-57-32 +.. gh-issue: 88352 +.. nonce: bZ68rw +.. section: Library + +Fix the computation of the next rollover time in the +:class:`logging.TimedRotatingFileHandler` handler. :meth:`!computeRollover` +now always returns a timestamp larger than the specified time and works +correctly during the DST change. :meth:`!doRollover` no longer overwrite the +already rolled over file, saving from data loss when run at midnight or +during repeated time at the DST change. + +.. + +.. date: 2024-02-29-20-06-06 +.. gh-issue: 87115 +.. nonce: FVMiOR +.. section: Library + +Set ``__main__.__spec__`` to ``None`` when running a script with :mod:`pdb` + +.. + +.. date: 2024-02-29-17-06-54 +.. gh-issue: 76511 +.. nonce: WqjRLP +.. section: Library + +Fix UnicodeEncodeError in :meth:`email.Message.as_string` that results when +a message that claims to be in the ascii character set actually has +non-ascii characters. Non-ascii characters are now replaced with the U+FFFD +replacement character, like in the ``replace`` error handler. + +.. + +.. date: 2024-02-28-17-50-42 +.. gh-issue: 89547 +.. nonce: GetF38 +.. section: Library + +Add support for nested typing special forms like Final[ClassVar[int]]. + +.. + +.. date: 2024-02-28-17-04-28 +.. gh-issue: 65824 +.. nonce: gG8KR1 +.. section: Library + +Improve the ``less`` prompt in :mod:`pydoc`. + +.. + +.. date: 2024-02-28-13-10-17 +.. gh-issue: 116040 +.. nonce: wDidHd +.. section: Library + +[Enum] fix by-value calls when second value is falsey; e.g. Cardinal(1, 0) + +.. + +.. date: 2024-02-28-12-14-31 +.. gh-issue: 115821 +.. nonce: YO2vKA +.. section: Library + +[Enum] Improve error message when calling super().__new__() in custom +__new__. + +.. + +.. date: 2024-02-27-20-11-29 +.. gh-issue: 85644 +.. nonce: 3rgcBm +.. section: Library + +Use the ``XDG_CURRENT_DESKTOP`` environment variable in :mod:`webbrowser` to +check desktop. Prefer it to the deprecated ``GNOME_DESKTOP_SESSION_ID`` for +GNOME detection. + +.. + +.. date: 2024-02-27-13-05-51 +.. gh-issue: 75988 +.. nonce: In6LlB +.. section: Library + +Fixed :func:`unittest.mock.create_autospec` to pass the call through to the +wrapped object to return the real result. + +.. + +.. date: 2024-02-25-19-20-05 +.. gh-issue: 115881 +.. nonce: ro_Kuw +.. section: Library + +Fix issue where :func:`ast.parse` would incorrectly flag conditional context +managers (such as ``with (x() if y else z()): ...``) as invalid syntax if +``feature_version=(3, 8)`` was passed. This reverts changes to the grammar +made as part of gh-94949. + +.. + +.. date: 2024-02-24-18-48-14 +.. gh-issue: 115886 +.. nonce: rgM6AF +.. section: Library + +Fix silent truncation of the name with an embedded null character in +:class:`multiprocessing.shared_memory.SharedMemory`. + +.. + +.. date: 2024-02-23-11-08-31 +.. gh-issue: 115532 +.. nonce: zVd3gK +.. section: Library + +Add kernel density estimation to the statistics module. + +.. + +.. date: 2024-02-22-12-10-18 +.. gh-issue: 115714 +.. nonce: P2JsU1 +.. section: Library + +On WASI, the :mod:`time` module no longer get process time using ``times()`` +or ``CLOCK_PROCESS_CPUTIME_ID``, system API is that is unreliable and is +likely to be removed from WASI. The affected clock functions fall back to +calling ``clock()``. + +.. + +.. date: 2024-02-22-11-29-27 +.. gh-issue: 115809 +.. nonce: 9H1DhB +.. section: Library + +Improve algorithm for computing which rolled-over log files to delete in +:class:`logging.TimedRotatingFileHandler`. It is now reliable for handlers +without ``namer`` and with arbitrary deterministic ``namer`` that leaves the +datetime part in the file name unmodified. + +.. + +.. date: 2024-02-21-17-54-59 +.. gh-issue: 74668 +.. nonce: JT-Q8W +.. section: Library + +:mod:`urllib.parse` functions :func:`~urllib.parse.parse_qs` and +:func:`~urllib.parse.parse_qsl` now support bytes arguments containing raw +and percent-encoded non-ASCII data. + +.. + +.. date: 2024-02-20-22-02-34 +.. gh-issue: 67044 +.. nonce: QF9_Ru +.. section: Library + +:func:`csv.writer` now always quotes or escapes ``'\r'`` and ``'\n'``, +regardless of *lineterminator* value. + +.. + +.. date: 2024-02-20-16-42-54 +.. gh-issue: 115712 +.. nonce: EXVMXw +.. section: Library + +Restore support of space delimiter with ``skipinitialspace=True`` in +:mod:`csv`. :func:`csv.writer()` now quotes empty fields if delimiter is a +space and skipinitialspace is true and raises exception if quoting is not +possible. + +.. + +.. date: 2024-02-20-07-38-15 +.. gh-issue: 112364 +.. nonce: EX7uGI +.. section: Library + +Fixed :func:`ast.unparse` to handle format_spec with ``"``, ``'`` or ``\\``. +Patched by Frank Hoffmann. + +.. + +.. date: 2024-02-19-16-53-48 +.. gh-issue: 112997 +.. nonce: sYBXRZ +.. section: Library + +Stop logging potentially sensitive callback arguments in :mod:`asyncio` +unless debug mode is active. + +.. + +.. date: 2024-02-19-15-52-30 +.. gh-issue: 114914 +.. nonce: M5-1d8 +.. section: Library + +Fix an issue where an abandoned :class:`StreamWriter` would not be garbage +collected. + +.. + +.. date: 2024-02-18-12-18-12 +.. gh-issue: 111358 +.. nonce: 9yJUMD +.. section: Library + +Fix a bug in :meth:`asyncio.BaseEventLoop.shutdown_default_executor` to +ensure the timeout passed to the coroutine behaves as expected. + +.. + +.. date: 2024-02-17-18-47-12 +.. gh-issue: 115618 +.. nonce: napiNp +.. section: Library + +Fix improper decreasing the reference count for ``None`` argument in +:class:`property` methods :meth:`~property.getter`, :meth:`~property.setter` +and :meth:`~property.deleter`. + +.. + +.. date: 2024-02-16-16-40-10 +.. gh-issue: 112720 +.. nonce: io6_Ac +.. section: Library + +Refactor :class:`dis.ArgResolver` to make it possible to subclass and change +the way jump args are interpreted. + +.. + +.. date: 2024-02-15-23-42-54 +.. gh-issue: 112006 +.. nonce: 4wxcK- +.. section: Library + +Fix :func:`inspect.unwrap` for types with the ``__wrapper__`` data +descriptor. Fix :meth:`inspect.Signature.from_callable` for builtins +:func:`classmethod` and :func:`staticmethod`. + +.. + +.. date: 2024-02-15-19-11-49 +.. gh-issue: 101293 +.. nonce: 898b8l +.. section: Library + +Support callables with the ``__call__()`` method and types with +``__new__()`` and ``__init__()`` methods set to class methods, static +methods, bound methods, partial functions, and other types of methods and +descriptors in :meth:`inspect.Signature.from_callable`. + +.. + +.. date: 2024-02-12-11-42-48 +.. gh-issue: 103092 +.. nonce: sGMKr0 +.. section: Library + +Isolate :mod:`_lsprof` (apply :pep:`687`). + +.. + +.. date: 2024-02-11-20-12-39 +.. gh-issue: 113942 +.. nonce: i72sMJ +.. section: Library + +:mod:`pydoc` no longer skips global functions implemented as builtin +methods, such as :class:`~type.MethodDescriptorType` and +:class:`~type.WrapperDescriptorType`. + +.. + +.. date: 2024-02-10-17-18-49 +.. gh-issue: 115256 +.. nonce: 41Fy9P +.. section: Library + +Added DeprecationWarning when accessing the tarfile attribute of TarInfo +objects. The attribute is never used internally and is only attached to +TarInfos when the tarfile is opened in write-mode, not read-mode. The +attribute creates an unnecessary reference cycle which may cause corruption +when not closing the handle after writing a tarfile. + +.. + +.. date: 2024-02-09-19-41-48 +.. gh-issue: 115197 +.. nonce: 20wkWH +.. section: Library + +``urllib.request`` no longer resolves the hostname before checking it +against the system's proxy bypass list on macOS and Windows. + +.. + +.. date: 2024-02-09-12-22-47 +.. gh-issue: 113812 +.. nonce: wOraaG +.. section: Library + +:meth:`DatagramTransport.sendto` will now send zero-length datagrams if +called with an empty bytes object. The transport flow control also now +accounts for the datagram header when calculating the buffer size. + +.. + +.. date: 2024-01-30-23-28-29 +.. gh-issue: 114763 +.. nonce: BRjKkg +.. section: Library + +Protect modules loaded with :class:`importlib.util.LazyLoader` from race +conditions when multiple threads try to access attributes before the loading +is complete. + +.. + +.. date: 2024-01-29-13-46-41 +.. gh-issue: 114709 +.. nonce: SQ998l +.. section: Library + +:func:`posixpath.commonpath()` now raises a :exc:`ValueError` exception when +passed an empty iterable. Previously, :exc:`IndexError` was raised. + +:func:`posixpath.commonpath()` now raises a :exc:`TypeError` exception when +passed ``None``. Previously, :exc:`ValueError` was raised. + +.. + +.. date: 2024-01-26-16-42-31 +.. gh-issue: 114610 +.. nonce: S18Vuz +.. section: Library + +Fix bug where :meth:`pathlib.PurePath.with_stem` converted a non-empty path +suffix to a stem when given an empty *stem* argument. It now raises +:exc:`ValueError`, just like :meth:`pathlib.PurePath.with_suffix` does when +called on a path with an empty stem, given a non-empty *suffix* argument. + +.. + +.. date: 2023-11-24-23-40-00 +.. gh-issue: 107361 +.. nonce: v54gh46 +.. section: Library + +Add :data:`ssl.VERIFY_X509_PARTIAL_CHAIN` and :data:`VERIFY_X509_STRICT` to +the default SSL context created with :func:`ssl.create_default_context`. + +.. + +.. date: 2023-11-20-16-15-44 +.. gh-issue: 112281 +.. nonce: gH4EVk +.. section: Library + +Allow creating :ref:`union of types` for +:class:`typing.Annotated` with unhashable metadata. + +.. + +.. date: 2023-11-07-10-22-06 +.. gh-issue: 111775 +.. nonce: IoVxfX +.. section: Library + +Fix :meth:`importlib.resources.simple.ResourceHandle.open` for text mode, +added missed ``stream`` argument. + +.. + +.. date: 2023-10-07-06-15-13 +.. gh-issue: 90095 +.. nonce: gWn1ka +.. section: Library + +Make .pdbrc and -c work with any valid pdb commands. + +.. + +.. date: 2023-08-05-08-41-58 +.. gh-issue: 107625 +.. nonce: cVSHCT +.. section: Library + +Raise :exc:`configparser.ParsingError` from +:meth:`~configparser.ConfigParser.read` and +:meth:`~configparser.ConfigParser.read_file` methods of +:class:`configparser.ConfigParser` if a key without a corresponding value is +continued (that is, followed by an indented line). + +.. + +.. date: 2023-08-02-01-17-32 +.. gh-issue: 107155 +.. nonce: Mj1K9L +.. section: Library + +Fix incorrect output of ``help(x)`` where ``x`` is a :keyword:`lambda` +function, which has an ``__annotations__`` dictionary attribute with a +``"return"`` key. + +.. + +.. date: 2023-07-12-14-52-04 +.. gh-issue: 57141 +.. nonce: L2k8Xb +.. section: Library + +Add option for *non-shallow* comparisons to :class:`filecmp.dircmp` like +:func:`filecmp.cmp`. Original patch by Steven Ward. Enhanced by Tobias +Rautenkranz + +.. + +.. date: 2023-05-17-21-33-21 +.. gh-issue: 69990 +.. nonce: Blvz9G +.. section: Library + +:meth:`Profile.print_stats` has been improved to accept multiple sort +arguments. Patched by Chiu-Hsiang Hsu and Furkan Onder. + +.. + +.. date: 2023-05-01-22-28-57 +.. gh-issue: 104061 +.. nonce: vxfBXf +.. section: Library + +Add :data:`socket.SO_BINDTOIFINDEX` constant. + +.. + +.. date: 2023-04-02-21-20-35 +.. gh-issue: 60346 +.. nonce: 7mjgua +.. section: Library + +Fix ArgumentParser inconsistent with parse_known_args. + +.. + +.. date: 2023-03-03-09-05-42 +.. gh-issue: 102389 +.. nonce: ucmo0_ +.. section: Library + +Add ``windows_31j`` to aliases for ``cp932`` codec + +.. + +.. date: 2023-02-14-17-19-59 +.. gh-issue: 72249 +.. nonce: fv35wU +.. section: Library + +:func:`functools.partial`s of :func:`repr` has been improved to include the +:term:`module` name. Patched by Furkan Onder and Anilyka Barry. + +.. + +.. date: 2023-01-12-14-16-01 +.. gh-issue: 100985 +.. nonce: GT5Fvd +.. section: Library + +Update HTTPSConnection to consistently wrap IPv6 Addresses when using a +proxy. + +.. + +.. date: 2023-01-09-14-08-02 +.. gh-issue: 100884 +.. nonce: DcmdLl +.. section: Library + +email: fix misfolding of comma in address-lists over multiple lines in +combination with unicode encoding. + +.. + +.. date: 2022-11-22-23-17-43 +.. gh-issue: 95782 +.. nonce: an_and +.. section: Library + +Fix :func:`io.BufferedReader.tell`, :func:`io.BufferedReader.seek`, +:func:`_pyio.BufferedReader.tell`, :func:`io.BufferedRandom.tell`, +:func:`io.BufferedRandom.seek` and :func:`_pyio.BufferedRandom.tell` being +able to return negative offsets. + +.. + +.. date: 2022-08-26-15-50-53 +.. gh-issue: 96310 +.. nonce: 0NssDh +.. section: Library + +Fix a traceback in :mod:`argparse` when all options in a mutually exclusive +group are suppressed. + +.. + +.. date: 2022-05-25-17-49-04 +.. gh-issue: 93205 +.. nonce: DjhFVR +.. section: Library + +Fixed a bug in :class:`logging.handlers.TimedRotatingFileHandler` where +multiple rotating handler instances pointing to files with the same name but +different extensions would conflict and not delete the correct files. + +.. + +.. bpo: 31116 +.. date: 2022-01-14-10-50-17 +.. nonce: 0bduV9 +.. section: Library + +Add Z85 encoding to ``base64``. + +.. + +.. bpo: 44865 +.. date: 2021-08-24-20-47-37 +.. nonce: c3BhZS +.. section: Library + +Add missing call to localization function in :mod:`argparse`. + +.. + +.. bpo: 43952 +.. date: 2021-05-03-11-04-12 +.. nonce: Me7fJe +.. section: Library + +Fix :meth:`multiprocessing.connection.Listener.accept()` to accept empty +bytes as authkey. Not accepting empty bytes as key causes it to hang +indefinitely. + +.. + +.. bpo: 42125 +.. date: 2020-12-15-22-30-49 +.. nonce: UGyseY +.. section: Library + +linecache: get module name from ``__spec__`` if available. This allows +getting source code for the ``__main__`` module when a custom loader is +used. + +.. + +.. bpo: 41122 +.. date: 2020-07-13-23-59-42 +.. nonce: 8P_Brh +.. section: Library + +Failing to pass arguments properly to :func:`functools.singledispatchmethod` +now throws a TypeError instead of hitting an index out of bounds internally. + +.. + +.. bpo: 40818 +.. date: 2020-05-29-18-08-54 +.. nonce: Ij8ffq +.. section: Library + +The asyncio REPL now runs :data:`sys.__interactivehook__` on startup. The +default implementation of :data:`sys.__interactivehook__` provides +auto-completion to the asyncio REPL. Patch contributed by Rémi Lapeyre. + +.. + +.. bpo: 33775 +.. date: 2019-04-06-23-50-59 +.. nonce: 0yhMDc +.. section: Library + +Add 'default' and 'version' help text for localization in argparse. + +.. + +.. date: 2024-02-14-20-17-04 +.. gh-issue: 115399 +.. nonce: fb9a0R +.. section: Documentation + +Document CVE-2023-52425 of Expat <2.6.0 under "XML vulnerabilities". + +.. + +.. date: 2024-02-08-08-51-37 +.. gh-issue: 109653 +.. nonce: QHLW4w +.. section: Documentation + +Improve import time of :mod:`uuid` on Linux. + +.. + +.. date: 2024-02-25-16-28-26 +.. gh-issue: 71052 +.. nonce: lSb9EC +.. section: Tests + +Add test exclusions to support running the test suite on Android. + +.. + +.. date: 2024-02-25-15-58-28 +.. gh-issue: 71052 +.. nonce: lxBjqY +.. section: Tests + +Enable ``test_concurrent_futures`` on platforms that support threading but +not multiprocessing. + +.. + +.. date: 2024-02-22-00-17-06 +.. gh-issue: 115796 +.. nonce: d4hpKy +.. section: Tests + +Make '_testinternalcapi.assemble_code_object' construct the exception table +for the code object. + +.. + +.. date: 2024-02-20-15-47-41 +.. gh-issue: 115720 +.. nonce: w8i8UG +.. section: Tests + +Leak tests (``-R``, ``--huntrleaks``) now show a summary of the number of +leaks found in each iteration. + +.. + +.. date: 2024-02-18-14-20-52 +.. gh-issue: 115122 +.. nonce: 3rGNo9 +.. section: Tests + +Add ``--bisect`` option to regrtest test runner: run failed tests with +``test.bisect_cmd`` to identify failing tests. Patch by Victor Stinner. + +.. + +.. date: 2024-02-17-08-25-01 +.. gh-issue: 115596 +.. nonce: RGPCrR +.. section: Tests + +Fix ``ProgramPriorityTests`` in ``test_os`` permanently changing the process +priority. + +.. + +.. date: 2024-02-16-13-04-28 +.. gh-issue: 115556 +.. nonce: rjaQ9w +.. section: Tests + +On Windows, commas passed in arguments to ``Tools\buildbot\test.bat`` and +``PCbuild\\rt.bat`` are now properly handled. + +.. + +.. date: 2024-02-13-18-24-04 +.. gh-issue: 115420 +.. nonce: -dlzfI +.. section: Tests + +Fix translation of exception hander targets by +``_testinternalcapi.optimize_cfg``. + +.. + +.. date: 2024-02-12-22-35-01 +.. gh-issue: 115376 +.. nonce: n9vubZ +.. section: Tests + +Fix segfault in ``_testinternalcapi.compiler_codegen`` on bad input. + +.. + +.. date: 2024-03-04-12-43-42 +.. gh-issue: 116313 +.. nonce: cLLb8S +.. section: Build + +Get WASI builds to work under wasmtime 18 w/ WASI 0.2/preview2 primitives. + +.. + +.. date: 2024-03-01-16-44-19 +.. gh-issue: 71052 +.. nonce: Hs-9EP +.. section: Build + +Change Android's :data:`sys.platform` from ``"linux"`` to ``"android"``. + +.. + +.. date: 2024-02-29-15-12-31 +.. gh-issue: 116117 +.. nonce: eENkQK +.. section: Build + +Backport ``libb2``'s PR #42 to fix compiling CPython on 32-bit Windows with +``clang-cl``. + +.. + +.. date: 2024-02-26-14-54-58 +.. gh-issue: 71052 +.. nonce: XvFay1 +.. section: Build + +Fix several Android build issues + +.. + +.. date: 2024-02-26-13-13-53 +.. gh-issue: 114099 +.. nonce: 8lpX-7 +.. section: Build + +A testbed project was added to run the test suite on iOS. + +.. + +.. date: 2024-02-24-12-50-43 +.. gh-issue: 115350 +.. nonce: naQA6y +.. section: Build + +Fix building ctypes module with -DWIN32_LEAN_AND_MEAN defined + +.. + +.. date: 2024-02-21-18-22-49 +.. gh-issue: 111225 +.. nonce: Z8C3av +.. section: Build + +Link extension modules against libpython on Android. + +.. + +.. date: 2024-02-21-11-58-30 +.. gh-issue: 115737 +.. nonce: dpNl2T +.. section: Build + +The install name for libPython is now correctly set for non-framework macOS +builds. + +.. + +.. date: 2024-02-13-14-52-59 +.. gh-issue: 114099 +.. nonce: zjXsQr +.. section: Build + +Makefile targets were added to support compiling an iOS-compatible framework +build. + +.. + +.. date: 2024-02-27-23-21-55 +.. gh-issue: 116012 +.. nonce: B9_IwM +.. section: Windows + +Ensure the value of ``GetLastError()`` is preserved across GIL operations. + +.. + +.. date: 2024-02-23-11-43-43 +.. gh-issue: 115582 +.. nonce: sk1XPi +.. section: Windows + +Building extensions intended for free-threaded builds of CPython now require +compiling with ``/DPy_GIL_DISABLED`` manually when using a regular install. +This is expected to change in future releases. + +.. + +.. date: 2024-02-21-23-48-59 +.. gh-issue: 115554 +.. nonce: 02mpQC +.. section: Windows + +The installer now has more strict rules about updating the :ref:`launcher`. +In general, most users only have a single launcher installed and will see no +difference. When multiple launchers have been installed, the option to +install the launcher is disabled until all but one have been removed. +Downgrading the launcher (which was never allowed) is now more obviously +blocked. + +.. + +.. date: 2024-02-15-23-16-31 +.. gh-issue: 115543 +.. nonce: otrWnw +.. section: Windows + +:ref:`launcher` can now detect Python 3.13 when installed from the Microsoft +Store, and will install Python 3.12 by default when +:envvar:`PYLAUNCHER_ALLOW_INSTALL` is set. + +.. + +.. date: 2024-02-29-20-52-23 +.. gh-issue: 116145 +.. nonce: ygafim +.. section: macOS + +Update macOS installer to Tcl/Tk 8.6.14. + +.. + +.. date: 2023-12-09-11-04-26 +.. gh-issue: 88516 +.. nonce: SIIvfs +.. section: IDLE + +On macOS show a proxy icon in the title bar of editor windows to match +platform behaviour. + +.. + +.. date: 2023-02-12-19-28-08 +.. gh-issue: 100176 +.. nonce: Kzs4Zw +.. section: Tools/Demos + +Remove outdated Tools/{io,cc,string}bench + +.. + +.. bpo: 45101 +.. date: 2021-09-05-02-47-48 +.. nonce: 60Zqmt +.. section: Tools/Demos + +Add consistency in usage message IO between 2 versions of python-config. + +.. + +.. date: 2024-02-16-15-56-53 +.. gh-issue: 114626 +.. nonce: ie2esA +.. section: C API + +Add again ``_PyCFunctionFastWithKeywords`` name, removed in Python 3.13 +alpha 4 by mistake. Keep the old private ``_PyCFunctionFastWithKeywords`` +name (Python 3.7) as an alias to the new public name +``PyCFunctionFastWithKeywords`` (Python 3.13a4). Patch by Victor Stinner. + +.. + +.. date: 2023-11-15-09-24-51 +.. gh-issue: 111418 +.. nonce: FYYetY +.. section: C API + +Add :c:macro:`PyHASH_MODULUS`, :c:macro:`PyHASH_BITS`, :c:macro:`PyHASH_INF` +and :c:macro:`PyHASH_IMAG` C macros. Patch by Sergey B Kirpichev. diff --git a/Misc/NEWS.d/next/Build/2024-02-21-11-58-30.gh-issue-115737.dpNl2T.rst b/Misc/NEWS.d/next/Build/2024-02-21-11-58-30.gh-issue-115737.dpNl2T.rst deleted file mode 100644 index 112f65258dd84b0..000000000000000 --- a/Misc/NEWS.d/next/Build/2024-02-21-11-58-30.gh-issue-115737.dpNl2T.rst +++ /dev/null @@ -1,2 +0,0 @@ -The install name for libPython is now correctly set for non-framework macOS -builds. diff --git a/Misc/NEWS.d/next/Build/2024-02-21-18-22-49.gh-issue-111225.Z8C3av.rst b/Misc/NEWS.d/next/Build/2024-02-21-18-22-49.gh-issue-111225.Z8C3av.rst deleted file mode 100644 index 8cdeba46ba23136..000000000000000 --- a/Misc/NEWS.d/next/Build/2024-02-21-18-22-49.gh-issue-111225.Z8C3av.rst +++ /dev/null @@ -1 +0,0 @@ -Link extension modules against libpython on Android. diff --git a/Misc/NEWS.d/next/Build/2024-03-08-17-05-15.gh-issue-115983.ZQqk0Q.rst b/Misc/NEWS.d/next/Build/2024-03-08-17-05-15.gh-issue-115983.ZQqk0Q.rst new file mode 100644 index 000000000000000..a8d39921d590927 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-03-08-17-05-15.gh-issue-115983.ZQqk0Q.rst @@ -0,0 +1 @@ +Skip building test modules that must be built as shared under WASI. diff --git a/Misc/NEWS.d/next/Build/2024-03-13-16-16-43.gh-issue-114736.ZhmauG.rst b/Misc/NEWS.d/next/Build/2024-03-13-16-16-43.gh-issue-114736.ZhmauG.rst new file mode 100644 index 000000000000000..cc863c3a3ceb48b --- /dev/null +++ b/Misc/NEWS.d/next/Build/2024-03-13-16-16-43.gh-issue-114736.ZhmauG.rst @@ -0,0 +1 @@ +Have WASI builds use WASI SDK 21. diff --git a/Misc/NEWS.d/next/C API/2024-02-16-15-56-53.gh-issue-114626.ie2esA.rst b/Misc/NEWS.d/next/C API/2024-02-16-15-56-53.gh-issue-114626.ie2esA.rst deleted file mode 100644 index 763f4cee6d3f0b1..000000000000000 --- a/Misc/NEWS.d/next/C API/2024-02-16-15-56-53.gh-issue-114626.ie2esA.rst +++ /dev/null @@ -1,4 +0,0 @@ -Add again ``_PyCFunctionFastWithKeywords`` name, removed in Python 3.13 -alpha 4 by mistake. Keep the old private ``_PyCFunctionFastWithKeywords`` -name (Python 3.7) as an alias to the new public name -``PyCFunctionFastWithKeywords`` (Python 3.13a4). Patch by Victor Stinner. diff --git a/Misc/NEWS.d/next/C API/2024-03-14-10-33-58.gh-issue-85283.LOgmdU.rst b/Misc/NEWS.d/next/C API/2024-03-14-10-33-58.gh-issue-85283.LOgmdU.rst new file mode 100644 index 000000000000000..ef8a934b435a88c --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-03-14-10-33-58.gh-issue-85283.LOgmdU.rst @@ -0,0 +1,2 @@ +The ``fcntl``, ``grp``, ``pwd``, ``termios`` and ``_statistics`` C extensions are now +built with the :ref:`limited C API `. Patch by Victor Stinner. diff --git a/Misc/NEWS.d/next/C API/2024-03-14-15-17-11.gh-issue-111696.YmnvAi.rst b/Misc/NEWS.d/next/C API/2024-03-14-15-17-11.gh-issue-111696.YmnvAi.rst new file mode 100644 index 000000000000000..3d87c56bf2493a6 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-03-14-15-17-11.gh-issue-111696.YmnvAi.rst @@ -0,0 +1,4 @@ +Add :c:func:`PyType_GetFullyQualifiedName` function to get the type's fully +qualified name. Equivalent to ``f"{type.__module__}.{type.__qualname__}"``, or +``type.__qualname__`` if ``type.__module__`` is not a string or is equal to +``"builtins"``. Patch by Victor Stinner. diff --git a/Misc/NEWS.d/next/C API/2024-03-14-18-00-32.gh-issue-111696.L6oIPq.rst b/Misc/NEWS.d/next/C API/2024-03-14-18-00-32.gh-issue-111696.L6oIPq.rst new file mode 100644 index 000000000000000..7973d7b16e5826b --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-03-14-18-00-32.gh-issue-111696.L6oIPq.rst @@ -0,0 +1,3 @@ +Add :c:func:`PyType_GetModuleName` function to get the type's module name. +Equivalent to getting the ``type.__module__`` attribute. Patch by Eric Snow +and Victor Stinner. diff --git a/Misc/NEWS.d/next/C API/2024-03-14-22-30-07.gh-issue-111696.76UMKi.rst b/Misc/NEWS.d/next/C API/2024-03-14-22-30-07.gh-issue-111696.76UMKi.rst new file mode 100644 index 000000000000000..44c15e4e6a82567 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-03-14-22-30-07.gh-issue-111696.76UMKi.rst @@ -0,0 +1,4 @@ +Add support for ``%T``, ``%T#``, ``%N`` and ``%N#`` formats to +:c:func:`PyUnicode_FromFormat`: format the fully qualified name of an object +type and of a type: call :c:func:`PyType_GetModuleName`. See :pep:`737` for +more information. Patch by Victor Stinner. diff --git a/Misc/NEWS.d/next/C API/2024-03-16-12-21-00.gh-issue-116809.JL786L.rst b/Misc/NEWS.d/next/C API/2024-03-16-12-21-00.gh-issue-116809.JL786L.rst new file mode 100644 index 000000000000000..a122e1b45b959a9 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-03-16-12-21-00.gh-issue-116809.JL786L.rst @@ -0,0 +1,2 @@ +Restore removed private ``_PyErr_ChainExceptions1()`` function. Patch by +Victor Stinner. diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-09-04-16-51-56.gh-issue-96497.HTBuIL.rst b/Misc/NEWS.d/next/Core and Builtins/2022-09-04-16-51-56.gh-issue-96497.HTBuIL.rst deleted file mode 100644 index 6881dde2e6cf442..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2022-09-04-16-51-56.gh-issue-96497.HTBuIL.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix incorrect resolution of mangled class variables used in assignment -expressions in comprehensions. diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-02-13-11-36-50.gh-issue-101860.CKCMbC.rst b/Misc/NEWS.d/next/Core and Builtins/2023-02-13-11-36-50.gh-issue-101860.CKCMbC.rst deleted file mode 100644 index 5a2743534669737..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2023-02-13-11-36-50.gh-issue-101860.CKCMbC.rst +++ /dev/null @@ -1 +0,0 @@ -Expose ``__name__`` attribute on property. diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-07-16-15-02-47.gh-issue-104090.oMjNa9.rst b/Misc/NEWS.d/next/Core and Builtins/2023-07-16-15-02-47.gh-issue-104090.oMjNa9.rst deleted file mode 100644 index e581d291d047aee..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2023-07-16-15-02-47.gh-issue-104090.oMjNa9.rst +++ /dev/null @@ -1,2 +0,0 @@ -The multiprocessing resource tracker now exits with non-zero status code if a resource -leak was detected. It still exits with status code 0 otherwise. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-28-02-46-12.gh-issue-112433.FUX-nT.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-28-02-46-12.gh-issue-112433.FUX-nT.rst deleted file mode 100644 index fdd11bdf4241b95..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-28-02-46-12.gh-issue-112433.FUX-nT.rst +++ /dev/null @@ -1 +0,0 @@ -Add ability to force alignment of :mod:`ctypes.Structure` by way of the new ``_align_`` attribute on the class. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-08-16-01-18.gh-issue-115154.ji96FV.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-08-16-01-18.gh-issue-115154.ji96FV.rst deleted file mode 100644 index 045596bfcdca431..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-02-08-16-01-18.gh-issue-115154.ji96FV.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix a bug that was causing the :func:`tokenize.untokenize` function to -handle unicode named literals incorrectly. Patch by Pablo Galindo diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-09-18-59-22.gh-issue-112175.qglugr.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-09-18-59-22.gh-issue-112175.qglugr.rst deleted file mode 100644 index 6d919134bf4d9c5..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-02-09-18-59-22.gh-issue-112175.qglugr.rst +++ /dev/null @@ -1 +0,0 @@ -Every ``PyThreadState`` now has its own ``eval_breaker``, allowing specific threads to be interrupted. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-14-23-50-43.gh-issue-115347.VkHvQC.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-14-23-50-43.gh-issue-115347.VkHvQC.rst deleted file mode 100644 index 16f357a944ed7a5..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-02-14-23-50-43.gh-issue-115347.VkHvQC.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix bug where docstring was replaced by a redundant NOP when Python is run -with ``-OO``. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-20-12-46-20.gh-issue-115700.KLJ5r4.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-20-12-46-20.gh-issue-115700.KLJ5r4.rst deleted file mode 100644 index 5b7b8e410b50632..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-02-20-12-46-20.gh-issue-115700.KLJ5r4.rst +++ /dev/null @@ -1 +0,0 @@ -The regen-cases build stage now works on Windows. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-20-18-49-02.gh-issue-115733.51Zb85.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-20-18-49-02.gh-issue-115733.51Zb85.rst deleted file mode 100644 index 5cbb292065b5daa..000000000000000 --- a/Misc/NEWS.d/next/Core and Builtins/2024-02-20-18-49-02.gh-issue-115733.51Zb85.rst +++ /dev/null @@ -1 +0,0 @@ -Fix crash when calling ``next()`` on exhausted list iterators. diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-03-11-00-45-39.gh-issue-116554.gYumG5.rst b/Misc/NEWS.d/next/Core and Builtins/2024-03-11-00-45-39.gh-issue-116554.gYumG5.rst new file mode 100644 index 000000000000000..82f92789de0a392 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-03-11-00-45-39.gh-issue-116554.gYumG5.rst @@ -0,0 +1 @@ +``list.sort()`` now exploits more cases of partial ordering, particularly those with long descending runs with sub-runs of equal values. Those are recognized as single runs now (previously, each block of repeated values caused a new run to be created). diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-03-11-22-05-56.gh-issue-116626.GsyczB.rst b/Misc/NEWS.d/next/Core and Builtins/2024-03-11-22-05-56.gh-issue-116626.GsyczB.rst new file mode 100644 index 000000000000000..5b18d04cca64b55 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-03-11-22-05-56.gh-issue-116626.GsyczB.rst @@ -0,0 +1 @@ +Ensure ``INSTRUMENTED_CALL_FUNCTION_EX`` always emits :monitoring-event:`CALL` diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-03-13-16-55-25.gh-issue-116735.o3w6y8.rst b/Misc/NEWS.d/next/Core and Builtins/2024-03-13-16-55-25.gh-issue-116735.o3w6y8.rst new file mode 100644 index 000000000000000..ca15d484e345dbd --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-03-13-16-55-25.gh-issue-116735.o3w6y8.rst @@ -0,0 +1 @@ +For ``INSTRUMENTED_CALL_FUNCTION_EX``, set ``arg0`` to ``sys.monitoring.MISSING`` instead of ``None`` for :monitoring-event:`CALL` event. diff --git a/Misc/NEWS.d/next/Documentation/2024-02-14-20-17-04.gh-issue-115399.fb9a0R.rst b/Misc/NEWS.d/next/Documentation/2024-02-14-20-17-04.gh-issue-115399.fb9a0R.rst deleted file mode 100644 index 587aea802168bd6..000000000000000 --- a/Misc/NEWS.d/next/Documentation/2024-02-14-20-17-04.gh-issue-115399.fb9a0R.rst +++ /dev/null @@ -1 +0,0 @@ -Document CVE-2023-52425 of Expat <2.6.0 under "XML vulnerabilities". diff --git a/Misc/NEWS.d/next/IDLE/2023-12-09-11-04-26.gh-issue-88516.SIIvfs.rst b/Misc/NEWS.d/next/IDLE/2023-12-09-11-04-26.gh-issue-88516.SIIvfs.rst deleted file mode 100644 index b6dea5029bf3531..000000000000000 --- a/Misc/NEWS.d/next/IDLE/2023-12-09-11-04-26.gh-issue-88516.SIIvfs.rst +++ /dev/null @@ -1,2 +0,0 @@ -On macOS show a proxy icon in the title bar of editor windows to match -platform behaviour. diff --git a/Misc/NEWS.d/next/Library/2020-07-13-23-59-42.bpo-41122.8P_Brh.rst b/Misc/NEWS.d/next/Library/2020-07-13-23-59-42.bpo-41122.8P_Brh.rst deleted file mode 100644 index 76568d407449f53..000000000000000 --- a/Misc/NEWS.d/next/Library/2020-07-13-23-59-42.bpo-41122.8P_Brh.rst +++ /dev/null @@ -1,3 +0,0 @@ -Failing to pass arguments properly to :func:`functools.singledispatchmethod` -now throws a TypeError instead of hitting an index out of bounds -internally. diff --git a/Misc/NEWS.d/next/Library/2020-12-15-22-30-49.bpo-42125.UGyseY.rst b/Misc/NEWS.d/next/Library/2020-12-15-22-30-49.bpo-42125.UGyseY.rst deleted file mode 100644 index 49d4462e2577022..000000000000000 --- a/Misc/NEWS.d/next/Library/2020-12-15-22-30-49.bpo-42125.UGyseY.rst +++ /dev/null @@ -1,2 +0,0 @@ -linecache: get module name from ``__spec__`` if available. This allows getting -source code for the ``__main__`` module when a custom loader is used. diff --git a/Misc/NEWS.d/next/Library/2022-05-25-17-49-04.gh-issue-93205.DjhFVR.rst b/Misc/NEWS.d/next/Library/2022-05-25-17-49-04.gh-issue-93205.DjhFVR.rst deleted file mode 100644 index 4a280b93d933478..000000000000000 --- a/Misc/NEWS.d/next/Library/2022-05-25-17-49-04.gh-issue-93205.DjhFVR.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed a bug in :class:`logging.handlers.TimedRotatingFileHandler` where multiple rotating handler instances pointing to files with the same name but different extensions would conflict and not delete the correct files. diff --git a/Misc/NEWS.d/next/Library/2022-08-26-15-50-53.gh-issue-96310.0NssDh.rst b/Misc/NEWS.d/next/Library/2022-08-26-15-50-53.gh-issue-96310.0NssDh.rst deleted file mode 100644 index f8efb0002e104a4..000000000000000 --- a/Misc/NEWS.d/next/Library/2022-08-26-15-50-53.gh-issue-96310.0NssDh.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix a traceback in :mod:`argparse` when all options in a mutually exclusive -group are suppressed. diff --git a/Misc/NEWS.d/next/Library/2022-11-22-23-17-43.gh-issue-95782.an_and.rst b/Misc/NEWS.d/next/Library/2022-11-22-23-17-43.gh-issue-95782.an_and.rst deleted file mode 100644 index 123c3944aa3a3a0..000000000000000 --- a/Misc/NEWS.d/next/Library/2022-11-22-23-17-43.gh-issue-95782.an_and.rst +++ /dev/null @@ -1,4 +0,0 @@ -Fix :func:`io.BufferedReader.tell`, :func:`io.BufferedReader.seek`, -:func:`_pyio.BufferedReader.tell`, :func:`io.BufferedRandom.tell`, -:func:`io.BufferedRandom.seek` and :func:`_pyio.BufferedRandom.tell` -being able to return negative offsets. diff --git a/Misc/NEWS.d/next/Library/2023-01-09-14-08-02.gh-issue-100884.DcmdLl.rst b/Misc/NEWS.d/next/Library/2023-01-09-14-08-02.gh-issue-100884.DcmdLl.rst deleted file mode 100644 index 2a3881788108350..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-01-09-14-08-02.gh-issue-100884.DcmdLl.rst +++ /dev/null @@ -1,2 +0,0 @@ -email: fix misfolding of comma in address-lists over multiple lines in -combination with unicode encoding. diff --git a/Misc/NEWS.d/next/Library/2023-01-12-14-16-01.gh-issue-100985.GT5Fvd.rst b/Misc/NEWS.d/next/Library/2023-01-12-14-16-01.gh-issue-100985.GT5Fvd.rst deleted file mode 100644 index 8d8693a5edb3d4f..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-01-12-14-16-01.gh-issue-100985.GT5Fvd.rst +++ /dev/null @@ -1,2 +0,0 @@ -Update HTTPSConnection to consistently wrap IPv6 Addresses when using a -proxy. diff --git a/Misc/NEWS.d/next/Library/2023-03-03-09-05-42.gh-issue-102389.ucmo0_.rst b/Misc/NEWS.d/next/Library/2023-03-03-09-05-42.gh-issue-102389.ucmo0_.rst deleted file mode 100644 index 8c11567d79ba7b9..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-03-03-09-05-42.gh-issue-102389.ucmo0_.rst +++ /dev/null @@ -1 +0,0 @@ -Add ``windows_31j`` to aliases for ``cp932`` codec diff --git a/Misc/NEWS.d/next/Library/2023-04-02-21-20-35.gh-issue-60346.7mjgua.rst b/Misc/NEWS.d/next/Library/2023-04-02-21-20-35.gh-issue-60346.7mjgua.rst deleted file mode 100644 index c15bd6ed11d17f9..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-04-02-21-20-35.gh-issue-60346.7mjgua.rst +++ /dev/null @@ -1 +0,0 @@ -Fix ArgumentParser inconsistent with parse_known_args. diff --git a/Misc/NEWS.d/next/Library/2023-05-01-22-28-57.gh-issue-104061.vxfBXf.rst b/Misc/NEWS.d/next/Library/2023-05-01-22-28-57.gh-issue-104061.vxfBXf.rst deleted file mode 100644 index e15a811f9043520..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-05-01-22-28-57.gh-issue-104061.vxfBXf.rst +++ /dev/null @@ -1 +0,0 @@ -Add :data:`socket.SO_BINDTOIFINDEX` constant. diff --git a/Misc/NEWS.d/next/Library/2023-05-17-21-33-21.gh-issue-69990.Blvz9G.rst b/Misc/NEWS.d/next/Library/2023-05-17-21-33-21.gh-issue-69990.Blvz9G.rst deleted file mode 100644 index b0cdf44f7b9e391..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-05-17-21-33-21.gh-issue-69990.Blvz9G.rst +++ /dev/null @@ -1 +0,0 @@ -:meth:`Profile.print_stats` has been improved to accept multiple sort arguments. Patched by Chiu-Hsiang Hsu and Furkan Onder. diff --git a/Misc/NEWS.d/next/Library/2023-08-02-01-17-32.gh-issue-107155.Mj1K9L.rst b/Misc/NEWS.d/next/Library/2023-08-02-01-17-32.gh-issue-107155.Mj1K9L.rst deleted file mode 100644 index 8362dc0fcfaa748..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-08-02-01-17-32.gh-issue-107155.Mj1K9L.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fix incorrect output of ``help(x)`` where ``x`` is a :keyword:`lambda` -function, which has an ``__annotations__`` dictionary attribute with a -``"return"`` key. diff --git a/Misc/NEWS.d/next/Library/2023-11-07-10-22-06.gh-issue-111775.IoVxfX.rst b/Misc/NEWS.d/next/Library/2023-11-07-10-22-06.gh-issue-111775.IoVxfX.rst deleted file mode 100644 index 2a3bdd640ea67de..000000000000000 --- a/Misc/NEWS.d/next/Library/2023-11-07-10-22-06.gh-issue-111775.IoVxfX.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix :meth:`importlib.resources.simple.ResourceHandle.open` for text mode, -added missed ``stream`` argument. diff --git a/Misc/NEWS.d/next/Library/2024-01-02-22-47-12.gh-issue-85287.ZC5DLj.rst b/Misc/NEWS.d/next/Library/2024-01-02-22-47-12.gh-issue-85287.ZC5DLj.rst new file mode 100644 index 000000000000000..e6d031fbc93e833 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-02-22-47-12.gh-issue-85287.ZC5DLj.rst @@ -0,0 +1,2 @@ +Changes Unicode codecs to return UnicodeEncodeError or UnicodeDecodeError, +rather than just UnicodeError. diff --git a/Misc/NEWS.d/next/Library/2024-01-29-13-46-41.gh-issue-114709.SQ998l.rst b/Misc/NEWS.d/next/Library/2024-01-29-13-46-41.gh-issue-114709.SQ998l.rst deleted file mode 100644 index ca0d7902c73d1c0..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-01-29-13-46-41.gh-issue-114709.SQ998l.rst +++ /dev/null @@ -1,5 +0,0 @@ -:func:`posixpath.commonpath()` now raises a :exc:`ValueError` exception when -passed an empty iterable. Previously, :exc:`IndexError` was raised. - -:func:`posixpath.commonpath()` now raises a :exc:`TypeError` exception when -passed ``None``. Previously, :exc:`ValueError` was raised. diff --git a/Misc/NEWS.d/next/Library/2024-02-01-03-09-38.gh-issue-114271.raCkt5.rst b/Misc/NEWS.d/next/Library/2024-02-01-03-09-38.gh-issue-114271.raCkt5.rst new file mode 100644 index 000000000000000..2cd35eeda830b93 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-01-03-09-38.gh-issue-114271.raCkt5.rst @@ -0,0 +1,7 @@ +Fix a race in ``threading.Thread.join()``. + +``threading._MainThread`` now always represents the main thread of the main +interpreter. + +``PyThreadState.on_delete`` and ``PyThreadState.on_delete_data`` have been +removed. diff --git a/Misc/NEWS.d/next/Library/2024-02-09-12-22-47.gh-issue-113812.wOraaG.rst b/Misc/NEWS.d/next/Library/2024-02-09-12-22-47.gh-issue-113812.wOraaG.rst deleted file mode 100644 index 7ef7bc891cd885d..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-09-12-22-47.gh-issue-113812.wOraaG.rst +++ /dev/null @@ -1,3 +0,0 @@ -:meth:`DatagramTransport.sendto` will now send zero-length datagrams if -called with an empty bytes object. The transport flow control also now -accounts for the datagram header when calculating the buffer size. diff --git a/Misc/NEWS.d/next/Library/2024-02-16-16-40-10.gh-issue-112720.io6_Ac.rst b/Misc/NEWS.d/next/Library/2024-02-16-16-40-10.gh-issue-112720.io6_Ac.rst deleted file mode 100644 index 32916ede4dee357..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-16-16-40-10.gh-issue-112720.io6_Ac.rst +++ /dev/null @@ -1,2 +0,0 @@ -Refactor :class:`dis.ArgResolver` to make it possible to subclass and change -the way jump args are interpreted. diff --git a/Misc/NEWS.d/next/Library/2024-02-17-18-47-12.gh-issue-115618.napiNp.rst b/Misc/NEWS.d/next/Library/2024-02-17-18-47-12.gh-issue-115618.napiNp.rst deleted file mode 100644 index cb4b147d5dc6639..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-17-18-47-12.gh-issue-115618.napiNp.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fix improper decreasing the reference count for ``None`` argument in -:class:`property` methods :meth:`~property.getter`, :meth:`~property.setter` -and :meth:`~property.deleter`. diff --git a/Misc/NEWS.d/next/Library/2024-02-18-12-18-12.gh-issue-111358.9yJUMD.rst b/Misc/NEWS.d/next/Library/2024-02-18-12-18-12.gh-issue-111358.9yJUMD.rst deleted file mode 100644 index 2e895f8f181ce7e..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-18-12-18-12.gh-issue-111358.9yJUMD.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix a bug in :meth:`asyncio.BaseEventLoop.shutdown_default_executor` to -ensure the timeout passed to the coroutine behaves as expected. diff --git a/Misc/NEWS.d/next/Library/2024-02-20-07-38-15.gh-issue-112364.EX7uGI.rst b/Misc/NEWS.d/next/Library/2024-02-20-07-38-15.gh-issue-112364.EX7uGI.rst deleted file mode 100644 index 6af71e60ec2a8ea..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-20-07-38-15.gh-issue-112364.EX7uGI.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed :func:`ast.unparse` to handle format_spec with ``"``, ``'`` or ``\\``. Patched by Frank Hoffmann. diff --git a/Misc/NEWS.d/next/Library/2024-02-20-16-42-54.gh-issue-115712.EXVMXw.rst b/Misc/NEWS.d/next/Library/2024-02-20-16-42-54.gh-issue-115712.EXVMXw.rst deleted file mode 100644 index 8b19064dba779d2..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-20-16-42-54.gh-issue-115712.EXVMXw.rst +++ /dev/null @@ -1,4 +0,0 @@ -Restore support of space delimiter with ``skipinitialspace=True`` in -:mod:`csv`. :func:`csv.writer()` now quotes empty fields if delimiter is a -space and skipinitialspace is true and raises exception if quoting is not -possible. diff --git a/Misc/NEWS.d/next/Library/2024-02-22-12-10-18.gh-issue-115714.P2JsU1.rst b/Misc/NEWS.d/next/Library/2024-02-22-12-10-18.gh-issue-115714.P2JsU1.rst deleted file mode 100644 index fb626344c87fdb3..000000000000000 --- a/Misc/NEWS.d/next/Library/2024-02-22-12-10-18.gh-issue-115714.P2JsU1.rst +++ /dev/null @@ -1,4 +0,0 @@ -On WASI, the :mod:`time` module no longer get process time using ``times()`` -or ``CLOCK_PROCESS_CPUTIME_ID``, system API is that is unreliable and is -likely to be removed from WASI. The affected clock functions fall back to -calling ``clock()``. diff --git a/Misc/NEWS.d/next/Library/2024-02-26-10-06-50.gh-issue-113308.MbvOFt.rst b/Misc/NEWS.d/next/Library/2024-02-26-10-06-50.gh-issue-113308.MbvOFt.rst new file mode 100644 index 000000000000000..c4c242fe3d578ff --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-26-10-06-50.gh-issue-113308.MbvOFt.rst @@ -0,0 +1,4 @@ +Remove some internal protected parts from :mod:`uuid`: +``_has_uuid_generate_time_safe``, ``_netbios_getnode``, +``_ipconfig_getnode``, and ``_load_system_functions``. +They were unused. diff --git a/Misc/NEWS.d/next/Library/2024-03-01-20-23-57.gh-issue-90535.wXm-jC.rst b/Misc/NEWS.d/next/Library/2024-03-01-20-23-57.gh-issue-90535.wXm-jC.rst new file mode 100644 index 000000000000000..9af4efabb6b5b2d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-03-01-20-23-57.gh-issue-90535.wXm-jC.rst @@ -0,0 +1,3 @@ +Fix support of *interval* values > 1 in +:class:`logging.TimedRotatingFileHandler` for ``when='MIDNIGHT'`` and +``when='Wx'``. diff --git a/Misc/NEWS.d/next/Library/2024-03-06-18-30-37.gh-issue-116401.3Wcda2.rst b/Misc/NEWS.d/next/Library/2024-03-06-18-30-37.gh-issue-116401.3Wcda2.rst new file mode 100644 index 000000000000000..121f0065ecca952 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-03-06-18-30-37.gh-issue-116401.3Wcda2.rst @@ -0,0 +1,2 @@ +Fix blocking :func:`os.fwalk` and :func:`shutil.rmtree` on opening named +pipe. diff --git a/Misc/NEWS.d/next/Library/2024-03-08-11-31-49.gh-issue-116484.VMAsU7.rst b/Misc/NEWS.d/next/Library/2024-03-08-11-31-49.gh-issue-116484.VMAsU7.rst new file mode 100644 index 000000000000000..265c3810466d393 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-03-08-11-31-49.gh-issue-116484.VMAsU7.rst @@ -0,0 +1,3 @@ +Change automatically generated :class:`tkinter.Checkbutton` widget names to +avoid collisions with automatically generated +:class:`tkinter.ttk.Checkbutton` widget names within the same parent widget. diff --git a/Misc/NEWS.d/next/Library/2024-03-12-17-53-14.gh-issue-73468.z4ZzvJ.rst b/Misc/NEWS.d/next/Library/2024-03-12-17-53-14.gh-issue-73468.z4ZzvJ.rst new file mode 100644 index 000000000000000..c91f4eb97e06bc4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-03-12-17-53-14.gh-issue-73468.z4ZzvJ.rst @@ -0,0 +1,2 @@ +Added new :func:`math.fma` function, wrapping C99's ``fma()`` operation: +fused multiply-add function. Patch by Mark Dickinson and Victor Stinner. diff --git a/Misc/NEWS.d/next/Library/2024-03-13-15-45-54.gh-issue-63283.OToJnG.rst b/Misc/NEWS.d/next/Library/2024-03-13-15-45-54.gh-issue-63283.OToJnG.rst new file mode 100644 index 000000000000000..bb4c3a4a8d741b7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-03-13-15-45-54.gh-issue-63283.OToJnG.rst @@ -0,0 +1,2 @@ +In :mod:`encodings.idna`, any capitalization of the the ACE prefix +(``xn--``) is now acceptable. Patch by Pepijn de Vos and Zackery Spytz. diff --git a/Misc/NEWS.d/next/Library/2024-03-14-10-01-23.gh-issue-116811._h5iKP.rst b/Misc/NEWS.d/next/Library/2024-03-14-10-01-23.gh-issue-116811._h5iKP.rst new file mode 100644 index 000000000000000..001686324299969 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-03-14-10-01-23.gh-issue-116811._h5iKP.rst @@ -0,0 +1,2 @@ +In ``PathFinder.invalidate_caches``, delegate to +``MetadataPathFinder.invalidate_caches``. diff --git a/Misc/NEWS.d/next/Library/2024-03-14-14-01-46.gh-issue-116764.moB3Lc.rst b/Misc/NEWS.d/next/Library/2024-03-14-14-01-46.gh-issue-116764.moB3Lc.rst new file mode 100644 index 000000000000000..e92034b0e8b1572 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-03-14-14-01-46.gh-issue-116764.moB3Lc.rst @@ -0,0 +1,4 @@ +Restore support of ``None`` and other false values in :mod:`urllib.parse` +functions :func:`~urllib.parse.parse_qs` and +:func:`~urllib.parse.parse_qsl`. Also, they now raise a TypeError for +non-zero integers and non-empty sequences. diff --git a/Misc/NEWS.d/next/Library/2024-03-14-17-24-59.gh-issue-106531.9ehywi.rst b/Misc/NEWS.d/next/Library/2024-03-14-17-24-59.gh-issue-106531.9ehywi.rst new file mode 100644 index 000000000000000..e2720d333783c0c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-03-14-17-24-59.gh-issue-106531.9ehywi.rst @@ -0,0 +1,5 @@ +Refreshed zipfile._path from `zipp 3.18 +`_, providing +better compatibility for PyPy, better glob performance for deeply nested +zipfiles, and providing internal access to ``CompleteDirs.inject`` for use +in other tests (like importlib.resources). diff --git a/Misc/NEWS.d/next/Library/2024-03-14-20-59-28.gh-issue-90095.7UaJ1U.rst b/Misc/NEWS.d/next/Library/2024-03-14-20-59-28.gh-issue-90095.7UaJ1U.rst new file mode 100644 index 000000000000000..b7024c74f7aa7de --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-03-14-20-59-28.gh-issue-90095.7UaJ1U.rst @@ -0,0 +1 @@ +Ignore empty lines and comments in ``.pdbrc`` diff --git a/Misc/NEWS.d/next/Library/2024-03-17-18-12-39.gh-issue-115538.PBiRQB.rst b/Misc/NEWS.d/next/Library/2024-03-17-18-12-39.gh-issue-115538.PBiRQB.rst new file mode 100644 index 000000000000000..fda2ebf7593ed5b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-03-17-18-12-39.gh-issue-115538.PBiRQB.rst @@ -0,0 +1,2 @@ +:class:`_io.WindowsConsoleIO` now emit a warning if a boolean value is +passed as a filedescriptor argument. diff --git a/Misc/NEWS.d/next/Security/2024-01-26-22-14-09.gh-issue-114572.t1QMQD.rst b/Misc/NEWS.d/next/Security/2024-01-26-22-14-09.gh-issue-114572.t1QMQD.rst deleted file mode 100644 index b4f9fe64db06157..000000000000000 --- a/Misc/NEWS.d/next/Security/2024-01-26-22-14-09.gh-issue-114572.t1QMQD.rst +++ /dev/null @@ -1,4 +0,0 @@ -:meth:`ssl.SSLContext.cert_store_stats` and -:meth:`ssl.SSLContext.get_ca_certs` now correctly lock access to the -certificate store, when the :class:`ssl.SSLContext` is shared across -multiple threads. diff --git a/Misc/NEWS.d/next/Tests/2024-02-12-22-35-01.gh-issue-115376.n9vubZ.rst b/Misc/NEWS.d/next/Tests/2024-02-12-22-35-01.gh-issue-115376.n9vubZ.rst deleted file mode 100644 index e09d78a9c4b189d..000000000000000 --- a/Misc/NEWS.d/next/Tests/2024-02-12-22-35-01.gh-issue-115376.n9vubZ.rst +++ /dev/null @@ -1 +0,0 @@ -Fix segfault in ``_testinternalcapi.compiler_codegen`` on bad input. diff --git a/Misc/NEWS.d/next/Tests/2024-02-13-18-24-04.gh-issue-115420.-dlzfI.rst b/Misc/NEWS.d/next/Tests/2024-02-13-18-24-04.gh-issue-115420.-dlzfI.rst deleted file mode 100644 index 1442ada3490fa09..000000000000000 --- a/Misc/NEWS.d/next/Tests/2024-02-13-18-24-04.gh-issue-115420.-dlzfI.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix translation of exception hander targets by -``_testinternalcapi.optimize_cfg``. diff --git a/Misc/NEWS.d/next/Tests/2024-02-16-13-04-28.gh-issue-115556.rjaQ9w.rst b/Misc/NEWS.d/next/Tests/2024-02-16-13-04-28.gh-issue-115556.rjaQ9w.rst deleted file mode 100644 index c2811b133d9314f..000000000000000 --- a/Misc/NEWS.d/next/Tests/2024-02-16-13-04-28.gh-issue-115556.rjaQ9w.rst +++ /dev/null @@ -1,2 +0,0 @@ -On Windows, commas passed in arguments to ``Tools\buildbot\test.bat`` and -``PCbuild\\rt.bat`` are now properly handled. diff --git a/Misc/NEWS.d/next/Tests/2024-02-17-08-25-01.gh-issue-115596.RGPCrR.rst b/Misc/NEWS.d/next/Tests/2024-02-17-08-25-01.gh-issue-115596.RGPCrR.rst deleted file mode 100644 index 2bcb8b9ac6bcd4d..000000000000000 --- a/Misc/NEWS.d/next/Tests/2024-02-17-08-25-01.gh-issue-115596.RGPCrR.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fix ``ProgramPriorityTests`` in ``test_os`` permanently changing the process -priority. diff --git a/Misc/NEWS.d/next/Tests/2024-02-18-14-20-52.gh-issue-115122.3rGNo9.rst b/Misc/NEWS.d/next/Tests/2024-02-18-14-20-52.gh-issue-115122.3rGNo9.rst deleted file mode 100644 index e187a40a40516b4..000000000000000 --- a/Misc/NEWS.d/next/Tests/2024-02-18-14-20-52.gh-issue-115122.3rGNo9.rst +++ /dev/null @@ -1,2 +0,0 @@ -Add ``--bisect`` option to regrtest test runner: run failed tests with -``test.bisect_cmd`` to identify failing tests. Patch by Victor Stinner. diff --git a/Misc/NEWS.d/next/Tests/2024-02-22-00-17-06.gh-issue-115796.d4hpKy.rst b/Misc/NEWS.d/next/Tests/2024-02-22-00-17-06.gh-issue-115796.d4hpKy.rst deleted file mode 100644 index a40be74f73908e5..000000000000000 --- a/Misc/NEWS.d/next/Tests/2024-02-22-00-17-06.gh-issue-115796.d4hpKy.rst +++ /dev/null @@ -1,2 +0,0 @@ -Make '_testinternalcapi.assemble_code_object' construct the exception table -for the code object. diff --git a/Misc/NEWS.d/next/Tests/2024-03-06-11-00-36.gh-issue-116307.Uij0t_.rst b/Misc/NEWS.d/next/Tests/2024-03-06-11-00-36.gh-issue-116307.Uij0t_.rst new file mode 100644 index 000000000000000..0bc4be94789f215 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-03-06-11-00-36.gh-issue-116307.Uij0t_.rst @@ -0,0 +1,3 @@ +Added import helper ``isolated_modules`` as ``CleanImport`` does not remove +modules imported during the context. Use it in importlib.resources tests to +avoid leaving ``mod`` around to impede importlib.metadata tests. diff --git a/Misc/NEWS.d/next/Tests/2024-03-11-23-20-28.gh-issue-112536.Qv1RrX.rst b/Misc/NEWS.d/next/Tests/2024-03-11-23-20-28.gh-issue-112536.Qv1RrX.rst new file mode 100644 index 000000000000000..de9e1c557b093c6 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-03-11-23-20-28.gh-issue-112536.Qv1RrX.rst @@ -0,0 +1,2 @@ +Add --tsan to test.regrtest for running TSAN tests in reasonable execution +times. Patch by Donghee Na. diff --git a/Misc/NEWS.d/next/Tests/2024-03-13-12-06-49.gh-issue-115979.zsNpQD.rst b/Misc/NEWS.d/next/Tests/2024-03-13-12-06-49.gh-issue-115979.zsNpQD.rst new file mode 100644 index 000000000000000..02bc2b88942e4fe --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2024-03-13-12-06-49.gh-issue-115979.zsNpQD.rst @@ -0,0 +1 @@ +Update test_importlib so that it passes under WASI SDK 21. diff --git a/Misc/NEWS.d/next/Tools-Demos/2023-02-12-19-28-08.gh-issue-100176.Kzs4Zw.rst b/Misc/NEWS.d/next/Tools-Demos/2023-02-12-19-28-08.gh-issue-100176.Kzs4Zw.rst deleted file mode 100644 index 1a9fc76d93f2976..000000000000000 --- a/Misc/NEWS.d/next/Tools-Demos/2023-02-12-19-28-08.gh-issue-100176.Kzs4Zw.rst +++ /dev/null @@ -1 +0,0 @@ -Remove outdated Tools/{io,cc,string}bench diff --git a/Misc/NEWS.d/next/Windows/2024-02-15-23-16-31.gh-issue-115543.otrWnw.rst b/Misc/NEWS.d/next/Windows/2024-02-15-23-16-31.gh-issue-115543.otrWnw.rst deleted file mode 100644 index ebd15c83b83491f..000000000000000 --- a/Misc/NEWS.d/next/Windows/2024-02-15-23-16-31.gh-issue-115543.otrWnw.rst +++ /dev/null @@ -1,3 +0,0 @@ -:ref:`launcher` can now detect Python 3.13 when installed from the Microsoft -Store, and will install Python 3.12 by default when -:envvar:`PYLAUNCHER_ALLOW_INSTALL` is set. diff --git a/Misc/NEWS.d/next/Windows/2024-03-14-09-14-21.gh-issue-88494.Bwfmp7.rst b/Misc/NEWS.d/next/Windows/2024-03-14-09-14-21.gh-issue-88494.Bwfmp7.rst new file mode 100644 index 000000000000000..5a96af0231918f8 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-03-14-09-14-21.gh-issue-88494.Bwfmp7.rst @@ -0,0 +1,4 @@ +On Windows, :func:`time.monotonic()` now uses the ``QueryPerformanceCounter()`` +clock to have a resolution better than 1 us, instead of the +``GetTickCount64()`` clock which has a resolution of 15.6 ms. Patch by Victor +Stinner. diff --git a/Misc/NEWS.d/next/Windows/2024-03-14-20-46-23.gh-issue-116195.Cu_rYs.rst b/Misc/NEWS.d/next/Windows/2024-03-14-20-46-23.gh-issue-116195.Cu_rYs.rst new file mode 100644 index 000000000000000..32122d764e870a0 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-03-14-20-46-23.gh-issue-116195.Cu_rYs.rst @@ -0,0 +1 @@ +Improves performance of :func:`os.getppid` by using an alternate system API when available. Contributed by vxiiduu. diff --git a/Misc/externals.spdx.json b/Misc/externals.spdx.json new file mode 100644 index 000000000000000..2acfccbb004d6b3 --- /dev/null +++ b/Misc/externals.spdx.json @@ -0,0 +1,174 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "packages": [ + { + "SPDXID": "SPDXRef-PACKAGE-bzip2", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "ab8d1b0cc087c20d4c32c0e4fcf7d0c733a95da12cedc6d63b3f0a9af07427e2" + } + ], + "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/bzip2-1.0.8.tar.gz", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:bzip:bzip2:1.0.8:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + } + ], + "licenseConcluded": "NOASSERTION", + "name": "bzip2", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "1.0.8" + }, + { + "SPDXID": "SPDXRef-PACKAGE-libffi", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "9d802681adfea27d84cae0487a785fb9caa925bdad44c401b364c59ab2b8edda" + } + ], + "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/libffi-3.4.4.tar.gz", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:libffi_project:libffi:3.4.4:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + } + ], + "licenseConcluded": "NOASSERTION", + "name": "libffi", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "3.4.4" + }, + { + "SPDXID": "SPDXRef-PACKAGE-openssl", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "e6a77c273ebb284fedd8ea19b081fce74a9455936ffd47215f7c24713e2614b2" + } + ], + "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/openssl-3.0.13.tar.gz", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:openssl:openssl:3.0.13:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + } + ], + "licenseConcluded": "NOASSERTION", + "name": "openssl", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "3.0.13" + }, + { + "SPDXID": "SPDXRef-PACKAGE-sqlite", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "6f0364a27375435a34137b138ca4fedef8d23eec6493ca1dfff33bfc0c34fda4" + } + ], + "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/sqlite-3.45.1.0.tar.gz", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:sqlite:sqlite:3.45.1.0:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + } + ], + "licenseConcluded": "NOASSERTION", + "name": "sqlite", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "3.45.1.0" + }, + { + "SPDXID": "SPDXRef-PACKAGE-tcl-core", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "1d3f2015e49e269cf681373d433cd54d88d5ef7443fe87f5f50f5fcfe9003e73" + } + ], + "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/tcl-core-8.6.13.1.tar.gz", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:tcl_tk:tcl_tk:8.6.13.1:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + } + ], + "licenseConcluded": "NOASSERTION", + "name": "tcl-core", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "8.6.13.1" + }, + { + "SPDXID": "SPDXRef-PACKAGE-tk", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "6056203b8a6aaf6ea89d90a7b55dc7f407e55c093f731a98fd830a712a3c81d3" + } + ], + "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/tk-8.6.13.1.tar.gz", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:tcl_tk:tcl_tk:8.6.13.1:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + } + ], + "licenseConcluded": "NOASSERTION", + "name": "tk", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "8.6.13.1" + }, + { + "SPDXID": "SPDXRef-PACKAGE-xz", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "a15c168e39e87d750c3dc766edc7f19bdda57dacf01e509678467eace91ad282" + } + ], + "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/xz-5.2.5.tar.gz", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:xz_project:xz:5.2.5:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + } + ], + "licenseConcluded": "NOASSERTION", + "name": "xz", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "5.2.5" + }, + { + "SPDXID": "SPDXRef-PACKAGE-zlib", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "e3f3fb32564952006eb18b091ca8464740e5eca29d328cfb0b2da22768e0b638" + } + ], + "downloadLocation": "https://github.com/python/cpython-source-deps/archive/refs/tags/zlib-1.3.1.tar.gz", + "externalRefs": [ + { + "referenceCategory": "SECURITY", + "referenceLocator": "cpe:2.3:a:zlib:zlib:1.3.1:*:*:*:*:*:*:*", + "referenceType": "cpe23Type" + } + ], + "licenseConcluded": "NOASSERTION", + "name": "zlib", + "primaryPackagePurpose": "SOURCE", + "versionInfo": "1.3.1" + } + ], + "spdxVersion": "SPDX-2.3" +} \ No newline at end of file diff --git a/Misc/platform_triplet.c b/Misc/platform_triplet.c index 3307260544e8a6d..06b03bfa9a266a3 100644 --- a/Misc/platform_triplet.c +++ b/Misc/platform_triplet.c @@ -12,8 +12,20 @@ #undef powerpc #undef sparc #undef unix + #if defined(__ANDROID__) - # Android is not a multiarch system. +# if defined(__x86_64__) +PLATFORM_TRIPLET=x86_64-linux-android +# elif defined(__i386__) +PLATFORM_TRIPLET=i686-linux-android +# elif defined(__aarch64__) +PLATFORM_TRIPLET=aarch64-linux-android +# elif defined(__arm__) +PLATFORM_TRIPLET=arm-linux-androideabi +# else +# error unknown Android platform +# endif + #elif defined(__linux__) /* * BEGIN of Linux block @@ -233,7 +245,22 @@ PLATFORM_TRIPLET=i386-gnu # error unknown platform triplet # endif #elif defined(__APPLE__) +# include "TargetConditionals.h" +# if TARGET_OS_IOS +# if TARGET_OS_SIMULATOR +# if __x86_64__ +PLATFORM_TRIPLET=x86_64-iphonesimulator +# else +PLATFORM_TRIPLET=arm64-iphonesimulator +# endif +# else +PLATFORM_TRIPLET=arm64-iphoneos +# endif +# elif TARGET_OS_OSX PLATFORM_TRIPLET=darwin +# else +# error unknown Apple platform +# endif #elif defined(__VXWORKS__) PLATFORM_TRIPLET=vxworks #elif defined(__wasm32__) diff --git a/Misc/python-config.in b/Misc/python-config.in index 81c3316e334a485..dd5d161ab2286f1 100644 --- a/Misc/python-config.in +++ b/Misc/python-config.in @@ -13,7 +13,8 @@ valid_opts = ['prefix', 'exec-prefix', 'includes', 'libs', 'cflags', def exit_with_usage(code=1): print("Usage: {0} [{1}]".format( - sys.argv[0], '|'.join('--'+opt for opt in valid_opts)), file=sys.stderr) + sys.argv[0], '|'.join('--'+opt for opt in valid_opts)), + file=sys.stdout if code == 0 else sys.stderr) sys.exit(code) try: diff --git a/Misc/python-config.sh.in b/Misc/python-config.sh.in index 2602fe24c0402e1..eb02223ddcd2c30 100644 --- a/Misc/python-config.sh.in +++ b/Misc/python-config.sh.in @@ -4,7 +4,12 @@ exit_with_usage () { - echo "Usage: $0 --prefix|--exec-prefix|--includes|--libs|--cflags|--ldflags|--extension-suffix|--help|--abiflags|--configdir|--embed" + local USAGE="Usage: $0 --prefix|--exec-prefix|--includes|--libs|--cflags|--ldflags|--extension-suffix|--help|--abiflags|--configdir|--embed" + if [[ "$1" -eq 0 ]]; then + echo "$USAGE" + else + echo "$USAGE" >&2 + fi exit $1 } diff --git a/Misc/python.man b/Misc/python.man index 0f5dfa2e2289f71..4c90c0e2a998ba2 100644 --- a/Misc/python.man +++ b/Misc/python.man @@ -607,6 +607,10 @@ output. Setting it to 0 deactivates this behavior. .IP PYTHON_HISTORY This environment variable can be used to set the location of a history file (on Unix, it is \fI~/.python_history\fP by default). +.IP PYTHON_GIL +If this variable is set to 1, the global interpreter lock (GIL) will be forced +on. Setting it to 0 forces the GIL off. Only available in builds configured +with \fB--disable-gil\fP. .SS Debug-mode variables Setting these variables only has an effect in a debug build of Python, that is, if Python was configured with the diff --git a/Misc/sbom.spdx.json b/Misc/sbom.spdx.json index e28eaea81d6aae1..7e9aa6dd82e619a 100644 --- a/Misc/sbom.spdx.json +++ b/Misc/sbom.spdx.json @@ -132,11 +132,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "baa44fe4581895d42e8d5e83d8ce6a69b1c34dbe" + "checksumValue": "f50c899172acd93fc539007bfb43315b83d407e4" }, { "algorithm": "SHA256", - "checksumValue": "33a7b9ac8bf4571e23272cdf644c6f9808bd44c66b149e3c41ab3870d1888609" + "checksumValue": "d571b8258cfaa067a20adef553e5fcedd6671ca4a8841483496de031bd904567" } ], "fileName": "Modules/expat/pyexpatns.h" @@ -678,11 +678,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "6fa074693aa7305018dfa8db48010a8ef1050ad4" + "checksumValue": "f935d64cc633c38e09fc2d89281c95edfbc1fb05" }, { "algorithm": "SHA256", - "checksumValue": "c8c6dd861ac193d4a0e836242ff44900f83423f86d2c2940c8c4c1e41fbd5812" + "checksumValue": "b932aa273b2504606a48895a50ff08c883f7a68a7e4aced5daa909c43348605a" } ], "fileName": "Modules/_blake2/impl/blake2b.c" @@ -762,11 +762,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "d2691353fa54ac6ffcd7c0a294984dc9d7968ef7" + "checksumValue": "13ac5bb93578a7ee8f815b4e247e82c849992bbe" }, { "algorithm": "SHA256", - "checksumValue": "cfd7948c9fd50e9f9c62f8a93b20a254d1d510a862d1092af4f187b7c1a859a3" + "checksumValue": "25ec5dd5c79f916307358059fe9f633781f27df1c0e0962c4fcccdda1feb93a7" } ], "fileName": "Modules/_blake2/impl/blake2s.c" @@ -1224,11 +1224,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "12402bcf7f0161adb83f78163f41cc10a5e5de5f" + "checksumValue": "9dcb50e3f9c3245972731be5da0b28e7583198d9" }, { "algorithm": "SHA256", - "checksumValue": "cba044c76b6bc3ae6cfa49df1121cad7552140157b9e61e11cbb6580cc5d74cf" + "checksumValue": "7cac49fef5e9d952ec9390bf81c54d83f1b5da32fdf76091c2f0770ed943b7fe" } ], "fileName": "Modules/_decimal/libmpdec/io.c" diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index ca7cf02961571ee..c68adf8db079f99 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2496,3 +2496,7 @@ [typedef.PyCFunctionFastWithKeywords] added = '3.13' # "abi-only" since 3.10. (Same story as PyCFunctionFast.) +[function.PyType_GetFullyQualifiedName] + added = '3.13' +[function.PyType_GetModuleName] + added = '3.13' diff --git a/Modules/Setup b/Modules/Setup index 8ad9a5aebbfcaad..cd1cf24c25d4064 100644 --- a/Modules/Setup +++ b/Modules/Setup @@ -285,6 +285,7 @@ PYTHONPATH=$(COREPYTHONPATH) #_testcapi _testcapimodule.c #_testimportmultiple _testimportmultiple.c #_testmultiphase _testmultiphase.c +#_testexternalinspection _testexternalinspection.c #_testsinglephase _testsinglephase.c # --- diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index e98775a48087653..deada66cf1a8072 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -162,7 +162,8 @@ @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c -@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/bytearray.c _testcapi/bytes.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/pyos.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/heaptype_relative.c _testcapi/gc.c _testcapi/sys.c _testcapi/hash.c _testcapi/time.c +@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c +@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/pyos.c _testlimitedcapi/sys.c _testlimitedcapi/vectorcall_limited.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c @@ -170,7 +171,8 @@ *shared* @MODULE__TESTIMPORTMULTIPLE_TRUE@_testimportmultiple _testimportmultiple.c @MODULE__TESTMULTIPHASE_TRUE@_testmultiphase _testmultiphase.c -@MODULE__TESTMULTIPHASE_TRUE@_testsinglephase _testsinglephase.c +@MODULE__TESTSINGLEPHASE_TRUE@_testsinglephase _testsinglephase.c +@MODULE__TESTEXTERNALINSPECTION_TRUE@_testexternalinspection _testexternalinspection.c @MODULE__CTYPES_TEST_TRUE@_ctypes_test _ctypes/_ctypes_test.c # Limited API template modules; must be built as shared modules. diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index c1aa849ecf1aad2..29246cfa6afd00c 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -2045,12 +2045,22 @@ static PyObject * swap_current_task(asyncio_state *state, PyObject *loop, PyObject *task) { PyObject *prev_task; + + if (task == Py_None) { + if (PyDict_Pop(state->current_tasks, loop, &prev_task) < 0) { + return NULL; + } + if (prev_task == NULL) { + Py_RETURN_NONE; + } + return prev_task; + } + Py_hash_t hash; hash = PyObject_Hash(loop); if (hash == -1) { return NULL; } - prev_task = _PyDict_GetItem_KnownHash(state->current_tasks, loop, hash); if (prev_task == NULL) { if (PyErr_Occurred()) { @@ -2059,22 +2069,12 @@ swap_current_task(asyncio_state *state, PyObject *loop, PyObject *task) prev_task = Py_None; } Py_INCREF(prev_task); - - if (task == Py_None) { - if (_PyDict_DelItem_KnownHash(state->current_tasks, loop, hash) == -1) { - goto error; - } - } else { - if (_PyDict_SetItem_KnownHash(state->current_tasks, loop, task, hash) == -1) { - goto error; - } + if (_PyDict_SetItem_KnownHash(state->current_tasks, loop, task, hash) == -1) { + Py_DECREF(prev_task); + return NULL; } return prev_task; - -error: - Py_DECREF(prev_task); - return NULL; } /* ----- Task */ diff --git a/Modules/_blake2/impl/blake2b.c b/Modules/_blake2/impl/blake2b.c index c1068e8640546a1..cef22838917d9d6 100644 --- a/Modules/_blake2/impl/blake2b.c +++ b/Modules/_blake2/impl/blake2b.c @@ -27,7 +27,7 @@ #if defined(HAVE_SSE2) #include // MSVC only defines _mm_set_epi64x for x86_64... -#if defined(_MSC_VER) && !defined(_M_X64) +#if defined(_MSC_VER) && !defined(_M_X64) && !defined(__clang__) static inline __m128i _mm_set_epi64x( const uint64_t u1, const uint64_t u0 ) { return _mm_set_epi32( u1 >> 32, u1, u0 >> 32, u0 ); diff --git a/Modules/_blake2/impl/blake2s.c b/Modules/_blake2/impl/blake2s.c index 47514685b8f30b8..e7f63fd274f2129 100644 --- a/Modules/_blake2/impl/blake2s.c +++ b/Modules/_blake2/impl/blake2s.c @@ -27,7 +27,7 @@ #if defined(HAVE_SSE2) #include // MSVC only defines _mm_set_epi64x for x86_64... -#if defined(_MSC_VER) && !defined(_M_X64) +#if defined(_MSC_VER) && !defined(_M_X64) && !defined(__clang__) static inline __m128i _mm_set_epi64x( const uint64_t u1, const uint64_t u0 ) { return _mm_set_epi32( u1 >> 32, u1, u0 >> 32, u0 ); diff --git a/Modules/_csv.c b/Modules/_csv.c index 8d0472885afd96d..ac948f417cebf51 100644 --- a/Modules/_csv.c +++ b/Modules/_csv.c @@ -1152,6 +1152,8 @@ join_append_data(WriterObj *self, int field_kind, const void *field_data, if (c == dialect->delimiter || c == dialect->escapechar || c == dialect->quotechar || + c == '\n' || + c == '\r' || PyUnicode_FindChar( dialect->lineterminator, c, 0, PyUnicode_GET_LENGTH(dialect->lineterminator), 1) >= 0) { @@ -1604,10 +1606,12 @@ _csv_unregister_dialect_impl(PyObject *module, PyObject *name) /*[clinic end generated code: output=0813ebca6c058df4 input=6b5c1557bf60c7e7]*/ { _csvstate *module_state = get_csv_state(module); - if (PyDict_DelItem(module_state->dialects, name) < 0) { - if (PyErr_ExceptionMatches(PyExc_KeyError)) { - PyErr_Format(module_state->error_obj, "unknown dialect"); - } + int rc = PyDict_Pop(module_state->dialects, name, NULL); + if (rc < 0) { + return NULL; + } + if (rc == 0) { + PyErr_Format(module_state->error_obj, "unknown dialect"); return NULL; } Py_RETURN_NONE; diff --git a/Modules/_ctypes/_ctypes_test.c b/Modules/_ctypes/_ctypes_test.c index ecc60417790417c..1dd3ef190524701 100644 --- a/Modules/_ctypes/_ctypes_test.c +++ b/Modules/_ctypes/_ctypes_test.c @@ -1,8 +1,7 @@ +// Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED #include "pyconfig.h" // Py_GIL_DISABLED - #ifndef Py_GIL_DISABLED -// Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED -#define Py_LIMITED_API 0x030c0000 +# define Py_LIMITED_API 0x030c0000 #endif // gh-85283: On Windows, Py_LIMITED_API requires Py_BUILD_CORE to not attempt diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h index 1989723f6f3dbb1..02f48a9ed55843c 100644 --- a/Modules/_ctypes/ctypes.h +++ b/Modules/_ctypes/ctypes.h @@ -32,6 +32,10 @@ #endif #endif +#ifdef MS_WIN32 +#include // for IUnknown interface +#endif + typedef struct { PyTypeObject *DictRemover_Type; PyTypeObject *PyCArg_Type; diff --git a/Modules/_decimal/libmpdec/io.c b/Modules/_decimal/libmpdec/io.c index e7bd6aee170056c..4e95b8964c8e5d3 100644 --- a/Modules/_decimal/libmpdec/io.c +++ b/Modules/_decimal/libmpdec/io.c @@ -48,6 +48,7 @@ #if defined(__GNUC__) && !defined(__INTEL_COMPILER) && __GNUC__ >= 7 #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" #pragma GCC diagnostic ignored "-Wmisleading-indentation" + #pragma GCC diagnostic ignored "-Warray-bounds" #endif diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 544510812116541..aaa0cad76ae5c4b 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -372,33 +372,27 @@ element_new(PyTypeObject *type, PyObject *args, PyObject *kwds) static PyObject* get_attrib_from_keywords(PyObject *kwds) { - PyObject *attrib_str = PyUnicode_FromString("attrib"); - if (attrib_str == NULL) { + PyObject *attrib; + if (PyDict_PopString(kwds, "attrib", &attrib) < 0) { return NULL; } - PyObject *attrib = PyDict_GetItemWithError(kwds, attrib_str); if (attrib) { /* If attrib was found in kwds, copy its value and remove it from * kwds */ if (!PyDict_Check(attrib)) { - Py_DECREF(attrib_str); PyErr_Format(PyExc_TypeError, "attrib must be dict, not %.100s", Py_TYPE(attrib)->tp_name); + Py_DECREF(attrib); return NULL; } - attrib = PyDict_Copy(attrib); - if (attrib && PyDict_DelItem(kwds, attrib_str) < 0) { - Py_SETREF(attrib, NULL); - } + Py_SETREF(attrib, PyDict_Copy(attrib)); } - else if (!PyErr_Occurred()) { + else { attrib = PyDict_New(); } - Py_DECREF(attrib_str); - if (attrib != NULL && PyDict_Update(attrib, kwds) < 0) { Py_DECREF(attrib); return NULL; @@ -3894,6 +3888,40 @@ _elementtree_XMLParser_close_impl(XMLParserObject *self) } } +/*[clinic input] +_elementtree.XMLParser.flush + +[clinic start generated code]*/ + +static PyObject * +_elementtree_XMLParser_flush_impl(XMLParserObject *self) +/*[clinic end generated code: output=42fdb8795ca24509 input=effbecdb28715949]*/ +{ + if (!_check_xmlparser(self)) { + return NULL; + } + + elementtreestate *st = self->state; + + if (EXPAT(st, SetReparseDeferralEnabled) == NULL) { + Py_RETURN_NONE; + } + + // NOTE: The Expat parser in the C implementation of ElementTree is not + // exposed to the outside; as a result we known that reparse deferral + // is currently enabled, or we would not even have access to function + // XML_SetReparseDeferralEnabled in the first place (which we checked + // for, a few lines up). + + EXPAT(st, SetReparseDeferralEnabled)(self->parser, XML_FALSE); + + PyObject *res = expat_parse(st, self, "", 0, XML_FALSE); + + EXPAT(st, SetReparseDeferralEnabled)(self->parser, XML_TRUE); + + return res; +} + /*[clinic input] _elementtree.XMLParser.feed @@ -4288,6 +4316,7 @@ static PyType_Spec treebuilder_spec = { static PyMethodDef xmlparser_methods[] = { _ELEMENTTREE_XMLPARSER_FEED_METHODDEF _ELEMENTTREE_XMLPARSER_CLOSE_METHODDEF + _ELEMENTTREE_XMLPARSER_FLUSH_METHODDEF _ELEMENTTREE_XMLPARSER__PARSE_WHOLE_METHODDEF _ELEMENTTREE_XMLPARSER__SETEVENTS_METHODDEF {NULL, NULL} diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 9ab847165dc0970..f23b6e0d62bfb1b 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -365,6 +365,8 @@ partial_repr(partialobject *pto) { PyObject *result = NULL; PyObject *arglist; + PyObject *mod; + PyObject *name; Py_ssize_t i, n; PyObject *key, *value; int status; @@ -399,13 +401,28 @@ partial_repr(partialobject *pto) if (arglist == NULL) goto done; } - result = PyUnicode_FromFormat("%s(%R%U)", Py_TYPE(pto)->tp_name, - pto->fn, arglist); + + mod = PyType_GetModuleName(Py_TYPE(pto)); + if (mod == NULL) { + goto error; + } + name = PyType_GetQualName(Py_TYPE(pto)); + if (name == NULL) { + Py_DECREF(mod); + goto error; + } + result = PyUnicode_FromFormat("%S.%S(%R%U)", mod, name, pto->fn, arglist); + Py_DECREF(mod); + Py_DECREF(name); Py_DECREF(arglist); done: Py_ReprLeave((PyObject *)pto); return result; + error: + Py_DECREF(arglist); + Py_ReprLeave((PyObject *)pto); + return NULL; } /* Pickle strategy: diff --git a/Modules/_interpreters_common.h b/Modules/_interpreters_common.h index 5661a26d8790d18..07120f6ccc7207d 100644 --- a/Modules/_interpreters_common.h +++ b/Modules/_interpreters_common.h @@ -11,3 +11,11 @@ ensure_xid_class(PyTypeObject *cls, crossinterpdatafunc getdata) //assert(cls->tp_flags & Py_TPFLAGS_HEAPTYPE); return _PyCrossInterpreterData_RegisterClass(cls, getdata); } + +#ifdef REGISTERS_HEAP_TYPES +static int +clear_xid_class(PyTypeObject *cls) +{ + return _PyCrossInterpreterData_UnregisterClass(cls); +} +#endif diff --git a/Modules/_io/winconsoleio.c b/Modules/_io/winconsoleio.c index 54e155554172874..ec5c298066a5874 100644 --- a/Modules/_io/winconsoleio.c +++ b/Modules/_io/winconsoleio.c @@ -298,6 +298,13 @@ _io__WindowsConsoleIO___init___impl(winconsoleio *self, PyObject *nameobj, self->fd = -1; } + if (PyBool_Check(nameobj)) { + if (PyErr_WarnEx(PyExc_RuntimeWarning, + "bool is used as a file descriptor", 1)) + { + return -1; + } + } fd = PyLong_AsInt(nameobj); if (fd < 0) { if (!PyErr_Occurred()) { diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c index f1cee7cb6f66bf8..a76c3dea5557839 100644 --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -1005,9 +1005,7 @@ _lsprof_exec(PyObject *module) static PyModuleDef_Slot _lsprofslots[] = { {Py_mod_exec, _lsprof_exec}, - // XXX gh-103092: fix isolation. - {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, - //{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, {0, NULL} }; diff --git a/Modules/_multiprocessing/multiprocessing.c b/Modules/_multiprocessing/multiprocessing.c index 2e6d8eb68c02437..1f6ab718a36984a 100644 --- a/Modules/_multiprocessing/multiprocessing.c +++ b/Modules/_multiprocessing/multiprocessing.c @@ -181,7 +181,7 @@ static PyMethodDef module_methods[] = { _MULTIPROCESSING_RECV_METHODDEF _MULTIPROCESSING_SEND_METHODDEF #endif -#if !defined(POSIX_SEMAPHORES_NOT_ENABLED) && !defined(__ANDROID__) +#if !defined(POSIX_SEMAPHORES_NOT_ENABLED) _MULTIPROCESSING_SEM_UNLINK_METHODDEF #endif {NULL} diff --git a/Modules/_multiprocessing/posixshmem.c b/Modules/_multiprocessing/posixshmem.c index 425ce10075c1565..d332a4e9d9ea0b8 100644 --- a/Modules/_multiprocessing/posixshmem.c +++ b/Modules/_multiprocessing/posixshmem.c @@ -2,15 +2,15 @@ posixshmem - A Python extension that provides shm_open() and shm_unlink() */ +// Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED #include "pyconfig.h" // Py_GIL_DISABLED - #ifndef Py_GIL_DISABLED -// Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED -#define Py_LIMITED_API 0x030c0000 +# define Py_LIMITED_API 0x030c0000 #endif #include +#include // strlen() #include // EINTR #ifdef HAVE_SYS_MMAN_H # include // shm_open(), shm_unlink() @@ -48,10 +48,15 @@ _posixshmem_shm_open_impl(PyObject *module, PyObject *path, int flags, { int fd; int async_err = 0; - const char *name = PyUnicode_AsUTF8AndSize(path, NULL); + Py_ssize_t name_size; + const char *name = PyUnicode_AsUTF8AndSize(path, &name_size); if (name == NULL) { return -1; } + if (strlen(name) != (size_t)name_size) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + return -1; + } do { Py_BEGIN_ALLOW_THREADS fd = shm_open(name, flags, mode); @@ -87,10 +92,15 @@ _posixshmem_shm_unlink_impl(PyObject *module, PyObject *path) { int rv; int async_err = 0; - const char *name = PyUnicode_AsUTF8AndSize(path, NULL); + Py_ssize_t name_size; + const char *name = PyUnicode_AsUTF8AndSize(path, &name_size); if (name == NULL) { return NULL; } + if (strlen(name) != (size_t)name_size) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + return NULL; + } do { Py_BEGIN_ALLOW_THREADS rv = shm_unlink(name); diff --git a/Modules/_scproxy.c b/Modules/_scproxy.c index fe82e918677f9a3..042738b4ab83a2e 100644 --- a/Modules/_scproxy.c +++ b/Modules/_scproxy.c @@ -3,11 +3,10 @@ * using the SystemConfiguration framework. */ +// Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED #include "pyconfig.h" // Py_GIL_DISABLED - #ifndef Py_GIL_DISABLED -// Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED -#define Py_LIMITED_API 0x030c0000 +# define Py_LIMITED_API 0x030c0000 #endif #include diff --git a/Modules/_stat.c b/Modules/_stat.c index b43e79453f5b2fd..8059ec2f1f066db 100644 --- a/Modules/_stat.c +++ b/Modules/_stat.c @@ -11,11 +11,10 @@ * */ +// Need limited C API version 3.13 for PyModule_Add() on Windows #include "pyconfig.h" // Py_GIL_DISABLED - #ifndef Py_GIL_DISABLED -// Need limited C API version 3.13 for PyModule_Add() on Windows -#define Py_LIMITED_API 0x030d0000 +# define Py_LIMITED_API 0x030d0000 #endif #include "Python.h" diff --git a/Modules/_statisticsmodule.c b/Modules/_statisticsmodule.c index a04a2a779a5d3d3..78a6552c4c9ec0c 100644 --- a/Modules/_statisticsmodule.c +++ b/Modules/_statisticsmodule.c @@ -1,8 +1,9 @@ /* statistics accelerator C extension: _statistics module. */ -// clinic/_statisticsmodule.c.h uses internal pycore_modsupport.h API -#ifndef Py_BUILD_CORE_BUILTIN -# define Py_BUILD_CORE_MODULE 1 +// Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED +#include "pyconfig.h" // Py_GIL_DISABLED +#ifndef Py_GIL_DISABLED +# define Py_LIMITED_API 0x030c0000 #endif #include "Python.h" diff --git a/Modules/_testcapi/clinic/vectorcall_limited.c.h b/Modules/_testcapi/clinic/vectorcall_limited.c.h deleted file mode 100644 index a233aefec79ecd5..000000000000000 --- a/Modules/_testcapi/clinic/vectorcall_limited.c.h +++ /dev/null @@ -1,20 +0,0 @@ -/*[clinic input] -preserve -[clinic start generated code]*/ - -PyDoc_STRVAR(_testcapi_call_vectorcall__doc__, -"call_vectorcall($module, callable, /)\n" -"--\n" -"\n"); - -#define _TESTCAPI_CALL_VECTORCALL_METHODDEF \ - {"call_vectorcall", (PyCFunction)_testcapi_call_vectorcall, METH_O, _testcapi_call_vectorcall__doc__}, - -PyDoc_STRVAR(_testcapi_call_vectorcall_method__doc__, -"call_vectorcall_method($module, callable, /)\n" -"--\n" -"\n"); - -#define _TESTCAPI_CALL_VECTORCALL_METHOD_METHODDEF \ - {"call_vectorcall_method", (PyCFunction)_testcapi_call_vectorcall_method, METH_O, _testcapi_call_vectorcall_method__doc__}, -/*[clinic end generated code: output=e980906a39602528 input=a9049054013a1b77]*/ diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h index e8cfb2423500d4b..f9bdd830775a756 100644 --- a/Modules/_testcapi/parts.h +++ b/Modules/_testcapi/parts.h @@ -31,8 +31,6 @@ int _PyTestCapi_Init_Vectorcall(PyObject *module); int _PyTestCapi_Init_Heaptype(PyObject *module); int _PyTestCapi_Init_Abstract(PyObject *module); -int _PyTestCapi_Init_ByteArray(PyObject *module); -int _PyTestCapi_Init_Bytes(PyObject *module); int _PyTestCapi_Init_Unicode(PyObject *module); int _PyTestCapi_Init_GetArgs(PyObject *module); int _PyTestCapi_Init_DateTime(PyObject *module); @@ -52,16 +50,11 @@ int _PyTestCapi_Init_Exceptions(PyObject *module); int _PyTestCapi_Init_Code(PyObject *module); int _PyTestCapi_Init_Buffer(PyObject *module); int _PyTestCapi_Init_PyAtomic(PyObject *module); -int _PyTestCapi_Init_PyOS(PyObject *module); int _PyTestCapi_Init_File(PyObject *module); int _PyTestCapi_Init_Codec(PyObject *module); int _PyTestCapi_Init_Immortal(PyObject *module); int _PyTestCapi_Init_GC(PyObject *module); -int _PyTestCapi_Init_Sys(PyObject *module); int _PyTestCapi_Init_Hash(PyObject *module); int _PyTestCapi_Init_Time(PyObject *module); -int _PyTestCapi_Init_VectorcallLimited(PyObject *module); -int _PyTestCapi_Init_HeaptypeRelative(PyObject *module); - #endif // Py_TESTCAPI_PARTS_H diff --git a/Modules/_testcapi/unicode.c b/Modules/_testcapi/unicode.c index a10183dddeca98c..d0954bbe36ff9df 100644 --- a/Modules/_testcapi/unicode.c +++ b/Modules/_testcapi/unicode.c @@ -3,8 +3,6 @@ #include "parts.h" #include "util.h" -static struct PyModuleDef *_testcapimodule = NULL; // set at initialization - static PyObject * codec_incrementalencoder(PyObject *self, PyObject *args) { @@ -2098,8 +2096,6 @@ static PyMethodDef TestMethods[] = { int _PyTestCapi_Init_Unicode(PyObject *m) { - _testcapimodule = PyModule_GetDef(m); - if (PyModule_AddFunctions(m, TestMethods) < 0) { return -1; } diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index b03f871b089c8a3..7928cd7d6fe1aeb 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -597,83 +597,39 @@ get_heaptype_for_name(PyObject *self, PyObject *Py_UNUSED(ignored)) return PyType_FromSpec(&HeapTypeNameType_Spec); } + static PyObject * -test_get_type_name(PyObject *self, PyObject *Py_UNUSED(ignored)) +get_type_name(PyObject *self, PyObject *type) { - PyObject *tp_name = PyType_GetName(&PyLong_Type); - assert(strcmp(PyUnicode_AsUTF8(tp_name), "int") == 0); - Py_DECREF(tp_name); - - tp_name = PyType_GetName(&PyModule_Type); - assert(strcmp(PyUnicode_AsUTF8(tp_name), "module") == 0); - Py_DECREF(tp_name); - - PyObject *HeapTypeNameType = PyType_FromSpec(&HeapTypeNameType_Spec); - if (HeapTypeNameType == NULL) { - Py_RETURN_NONE; - } - tp_name = PyType_GetName((PyTypeObject *)HeapTypeNameType); - assert(strcmp(PyUnicode_AsUTF8(tp_name), "HeapTypeNameType") == 0); - Py_DECREF(tp_name); - - PyObject *name = PyUnicode_FromString("test_name"); - if (name == NULL) { - goto done; - } - if (PyObject_SetAttrString(HeapTypeNameType, "__name__", name) < 0) { - Py_DECREF(name); - goto done; - } - tp_name = PyType_GetName((PyTypeObject *)HeapTypeNameType); - assert(strcmp(PyUnicode_AsUTF8(tp_name), "test_name") == 0); - Py_DECREF(name); - Py_DECREF(tp_name); - - done: - Py_DECREF(HeapTypeNameType); - Py_RETURN_NONE; + assert(PyType_Check(type)); + return PyType_GetName((PyTypeObject *)type); } static PyObject * -test_get_type_qualname(PyObject *self, PyObject *Py_UNUSED(ignored)) +get_type_qualname(PyObject *self, PyObject *type) { - PyObject *tp_qualname = PyType_GetQualName(&PyLong_Type); - assert(strcmp(PyUnicode_AsUTF8(tp_qualname), "int") == 0); - Py_DECREF(tp_qualname); + assert(PyType_Check(type)); + return PyType_GetQualName((PyTypeObject *)type); +} - tp_qualname = PyType_GetQualName(&PyODict_Type); - assert(strcmp(PyUnicode_AsUTF8(tp_qualname), "OrderedDict") == 0); - Py_DECREF(tp_qualname); - PyObject *HeapTypeNameType = PyType_FromSpec(&HeapTypeNameType_Spec); - if (HeapTypeNameType == NULL) { - Py_RETURN_NONE; - } - tp_qualname = PyType_GetQualName((PyTypeObject *)HeapTypeNameType); - assert(strcmp(PyUnicode_AsUTF8(tp_qualname), "HeapTypeNameType") == 0); - Py_DECREF(tp_qualname); +static PyObject * +get_type_fullyqualname(PyObject *self, PyObject *type) +{ + assert(PyType_Check(type)); + return PyType_GetFullyQualifiedName((PyTypeObject *)type); +} - PyObject *spec_name = PyUnicode_FromString(HeapTypeNameType_Spec.name); - if (spec_name == NULL) { - goto done; - } - if (PyObject_SetAttrString(HeapTypeNameType, - "__qualname__", spec_name) < 0) { - Py_DECREF(spec_name); - goto done; - } - tp_qualname = PyType_GetQualName((PyTypeObject *)HeapTypeNameType); - assert(strcmp(PyUnicode_AsUTF8(tp_qualname), - "_testcapi.HeapTypeNameType") == 0); - Py_DECREF(spec_name); - Py_DECREF(tp_qualname); - done: - Py_DECREF(HeapTypeNameType); - Py_RETURN_NONE; +static PyObject * +get_type_module_name(PyObject *self, PyObject *type) +{ + assert(PyType_Check(type)); + return PyType_GetModuleName((PyTypeObject *)type); } + static PyObject * test_get_type_dict(PyObject *self, PyObject *Py_UNUSED(ignored)) { @@ -3317,8 +3273,10 @@ static PyMethodDef TestMethods[] = { {"test_buildvalue_N", test_buildvalue_N, METH_NOARGS}, {"test_get_statictype_slots", test_get_statictype_slots, METH_NOARGS}, {"get_heaptype_for_name", get_heaptype_for_name, METH_NOARGS}, - {"test_get_type_name", test_get_type_name, METH_NOARGS}, - {"test_get_type_qualname", test_get_type_qualname, METH_NOARGS}, + {"get_type_name", get_type_name, METH_O}, + {"get_type_qualname", get_type_qualname, METH_O}, + {"get_type_fullyqualname", get_type_fullyqualname, METH_O}, + {"get_type_module_name", get_type_module_name, METH_O}, {"test_get_type_dict", test_get_type_dict, METH_NOARGS}, {"_test_thread_state", test_thread_state, METH_VARARGS}, #ifndef MS_WINDOWS @@ -4017,12 +3975,6 @@ PyInit__testcapi(void) if (_PyTestCapi_Init_Abstract(m) < 0) { return NULL; } - if (_PyTestCapi_Init_ByteArray(m) < 0) { - return NULL; - } - if (_PyTestCapi_Init_Bytes(m) < 0) { - return NULL; - } if (_PyTestCapi_Init_Unicode(m) < 0) { return NULL; } @@ -4077,18 +4029,12 @@ PyInit__testcapi(void) if (_PyTestCapi_Init_Buffer(m) < 0) { return NULL; } - if (_PyTestCapi_Init_PyOS(m) < 0) { - return NULL; - } if (_PyTestCapi_Init_File(m) < 0) { return NULL; } if (_PyTestCapi_Init_Codec(m) < 0) { return NULL; } - if (_PyTestCapi_Init_Sys(m) < 0) { - return NULL; - } if (_PyTestCapi_Init_Immortal(m) < 0) { return NULL; } @@ -4098,12 +4044,6 @@ PyInit__testcapi(void) if (_PyTestCapi_Init_PyAtomic(m) < 0) { return NULL; } - if (_PyTestCapi_Init_VectorcallLimited(m) < 0) { - return NULL; - } - if (_PyTestCapi_Init_HeaptypeRelative(m) < 0) { - return NULL; - } if (_PyTestCapi_Init_Hash(m) < 0) { return NULL; } diff --git a/Modules/_testclinic_limited.c b/Modules/_testclinic_limited.c index ef595be0b626db8..29f1b7c13e4c505 100644 --- a/Modules/_testclinic_limited.c +++ b/Modules/_testclinic_limited.c @@ -4,11 +4,10 @@ #undef Py_BUILD_CORE_MODULE #undef Py_BUILD_CORE_BUILTIN +// For now, AC only supports the limited C API version 3.13 #include "pyconfig.h" // Py_GIL_DISABLED - #ifndef Py_GIL_DISABLED -// For now, only limited C API 3.13 is supported -#define Py_LIMITED_API 0x030d0000 +# define Py_LIMITED_API 0x030d0000 #endif /* Always enable assertions */ @@ -72,10 +71,64 @@ my_int_sum_impl(PyObject *module, int x, int y) } +/*[clinic input] +my_float_sum -> float + + x: float + y: float + / + +[clinic start generated code]*/ + +static float +my_float_sum_impl(PyObject *module, float x, float y) +/*[clinic end generated code: output=634f59a5a419cad7 input=d4b5313bdf4dc377]*/ +{ + return x + y; +} + + +/*[clinic input] +my_double_sum -> double + + x: double + y: double + / + +[clinic start generated code]*/ + +static double +my_double_sum_impl(PyObject *module, double x, double y) +/*[clinic end generated code: output=a75576d9e4d8557f input=16b11c8aba172801]*/ +{ + return x + y; +} + + +/*[clinic input] +get_file_descriptor -> int + + file as fd: fildes + / + +Get a file descriptor. +[clinic start generated code]*/ + +static int +get_file_descriptor_impl(PyObject *module, int fd) +/*[clinic end generated code: output=80051ebad54db8a8 input=82e2a1418848cd5b]*/ +{ + return fd; +} + + static PyMethodDef tester_methods[] = { TEST_EMPTY_FUNCTION_METHODDEF MY_INT_FUNC_METHODDEF MY_INT_SUM_METHODDEF + MY_FLOAT_SUM_METHODDEF + MY_DOUBLE_SUM_METHODDEF + GET_FILE_DESCRIPTOR_METHODDEF {NULL, NULL} }; diff --git a/Modules/_testexternalinspection.c b/Modules/_testexternalinspection.c new file mode 100644 index 000000000000000..bd77f0cd0f1fc79 --- /dev/null +++ b/Modules/_testexternalinspection.c @@ -0,0 +1,632 @@ +#define _GNU_SOURCE + +#ifdef __linux__ +# include +# include +# if INTPTR_MAX == INT64_MAX +# define Elf_Ehdr Elf64_Ehdr +# define Elf_Shdr Elf64_Shdr +# define Elf_Phdr Elf64_Phdr +# else +# define Elf_Ehdr Elf32_Ehdr +# define Elf_Shdr Elf32_Shdr +# define Elf_Phdr Elf32_Phdr +# endif +# include +#endif + +#if defined(__APPLE__) +# include +# if TARGET_OS_OSX +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif +#include "Python.h" +#include + +#ifndef HAVE_PROCESS_VM_READV +# define HAVE_PROCESS_VM_READV 0 +#endif + +#if defined(__APPLE__) && TARGET_OS_OSX +static void* +analyze_macho64(mach_port_t proc_ref, void* base, void* map) +{ + struct mach_header_64* hdr = (struct mach_header_64*)map; + int ncmds = hdr->ncmds; + + int cmd_cnt = 0; + struct segment_command_64* cmd = map + sizeof(struct mach_header_64); + + mach_vm_size_t size = 0; + mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t); + mach_vm_address_t address = (mach_vm_address_t)base; + vm_region_basic_info_data_64_t region_info; + mach_port_t object_name; + + for (int i = 0; cmd_cnt < 2 && i < ncmds; i++) { + if (cmd->cmd == LC_SEGMENT_64 && strcmp(cmd->segname, "__DATA") == 0) { + while (cmd->filesize != size) { + address += size; + if (mach_vm_region( + proc_ref, + &address, + &size, + VM_REGION_BASIC_INFO_64, + (vm_region_info_t)®ion_info, // cppcheck-suppress [uninitvar] + &count, + &object_name) + != KERN_SUCCESS) + { + PyErr_SetString(PyExc_RuntimeError, "Cannot get any more VM maps.\n"); + return NULL; + } + } + base = (void*)address - cmd->vmaddr; + + int nsects = cmd->nsects; + struct section_64* sec = + (struct section_64*)((void*)cmd + sizeof(struct segment_command_64)); + for (int j = 0; j < nsects; j++) { + if (strcmp(sec[j].sectname, "PyRuntime") == 0) { + return base + sec[j].addr; + } + } + cmd_cnt++; + } + + cmd = (struct segment_command_64*)((void*)cmd + cmd->cmdsize); + } + return NULL; +} + +static void* +analyze_macho(char* path, void* base, mach_vm_size_t size, mach_port_t proc_ref) +{ + int fd = open(path, O_RDONLY); + if (fd == -1) { + PyErr_Format(PyExc_RuntimeError, "Cannot open binary %s\n", path); + return NULL; + } + + struct stat fs; + if (fstat(fd, &fs) == -1) { + PyErr_Format(PyExc_RuntimeError, "Cannot get size of binary %s\n", path); + close(fd); + return NULL; + } + + void* map = mmap(0, fs.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (map == MAP_FAILED) { + PyErr_Format(PyExc_RuntimeError, "Cannot map binary %s\n", path); + close(fd); + return NULL; + } + + void* result = NULL; + + struct mach_header_64* hdr = (struct mach_header_64*)map; + switch (hdr->magic) { + case MH_MAGIC: + case MH_CIGAM: + case FAT_MAGIC: + case FAT_CIGAM: + PyErr_SetString(PyExc_RuntimeError, "32-bit Mach-O binaries are not supported"); + break; + case MH_MAGIC_64: + case MH_CIGAM_64: + result = analyze_macho64(proc_ref, base, map); + break; + default: + PyErr_SetString(PyExc_RuntimeError, "Unknown Mach-O magic"); + break; + } + + munmap(map, fs.st_size); + if (close(fd) != 0) { + PyErr_SetFromErrno(PyExc_OSError); + } + return result; +} + +static mach_port_t +pid_to_task(pid_t pid) +{ + mach_port_t task; + kern_return_t result; + + result = task_for_pid(mach_task_self(), pid, &task); + if (result != KERN_SUCCESS) { + PyErr_Format(PyExc_PermissionError, "Cannot get task for PID %d", pid); + return 0; + } + return task; +} + +static void* +get_py_runtime_macos(pid_t pid) +{ + mach_vm_address_t address = 0; + mach_vm_size_t size = 0; + mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t); + vm_region_basic_info_data_64_t region_info; + mach_port_t object_name; + + mach_port_t proc_ref = pid_to_task(pid); + if (proc_ref == 0) { + PyErr_SetString(PyExc_PermissionError, "Cannot get task for PID"); + return NULL; + } + + int match_found = 0; + char map_filename[MAXPATHLEN + 1]; + void* result_address = NULL; + while (mach_vm_region( + proc_ref, + &address, + &size, + VM_REGION_BASIC_INFO_64, + (vm_region_info_t)®ion_info, + &count, + &object_name) + == KERN_SUCCESS) + { + int path_len = proc_regionfilename(pid, address, map_filename, MAXPATHLEN); + if (path_len == 0) { + address += size; + continue; + } + + char* filename = strrchr(map_filename, '/'); + if (filename != NULL) { + filename++; // Move past the '/' + } else { + filename = map_filename; // No path, use the whole string + } + + // Check if the filename starts with "python" or "libpython" + if (!match_found && strncmp(filename, "python", 6) == 0) { + match_found = 1; + result_address = analyze_macho(map_filename, (void*)address, size, proc_ref); + } + if (strncmp(filename, "libpython", 9) == 0) { + match_found = 1; + result_address = analyze_macho(map_filename, (void*)address, size, proc_ref); + break; + } + + address += size; + } + return result_address; +} +#endif + +#ifdef __linux__ +void* +find_python_map_start_address(pid_t pid, char* result_filename) +{ + char maps_file_path[64]; + sprintf(maps_file_path, "/proc/%d/maps", pid); + + FILE* maps_file = fopen(maps_file_path, "r"); + if (maps_file == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + int match_found = 0; + + char line[256]; + char map_filename[PATH_MAX]; + void* result_address = 0; + while (fgets(line, sizeof(line), maps_file) != NULL) { + unsigned long start_address = 0; + sscanf(line, "%lx-%*x %*s %*s %*s %*s %s", &start_address, map_filename); + char* filename = strrchr(map_filename, '/'); + if (filename != NULL) { + filename++; // Move past the '/' + } else { + filename = map_filename; // No path, use the whole string + } + + // Check if the filename starts with "python" or "libpython" + if (!match_found && strncmp(filename, "python", 6) == 0) { + match_found = 1; + result_address = (void*)start_address; + strcpy(result_filename, map_filename); + } + if (strncmp(filename, "libpython", 9) == 0) { + match_found = 1; + result_address = (void*)start_address; + strcpy(result_filename, map_filename); + break; + } + } + + fclose(maps_file); + + if (!match_found) { + map_filename[0] = '\0'; + } + + return result_address; +} + +void* +get_py_runtime_linux(pid_t pid) +{ + char elf_file[256]; + void* start_address = (void*)find_python_map_start_address(pid, elf_file); + + if (start_address == 0) { + PyErr_SetString(PyExc_RuntimeError, "No memory map associated with python or libpython found"); + return NULL; + } + + void* result = NULL; + void* file_memory = NULL; + + int fd = open(elf_file, O_RDONLY); + if (fd < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto exit; + } + + struct stat file_stats; + if (fstat(fd, &file_stats) != 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto exit; + } + + file_memory = mmap(NULL, file_stats.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (file_memory == MAP_FAILED) { + PyErr_SetFromErrno(PyExc_OSError); + goto exit; + } + + Elf_Ehdr* elf_header = (Elf_Ehdr*)file_memory; + + Elf_Shdr* section_header_table = (Elf_Shdr*)(file_memory + elf_header->e_shoff); + + Elf_Shdr* shstrtab_section = §ion_header_table[elf_header->e_shstrndx]; + char* shstrtab = (char*)(file_memory + shstrtab_section->sh_offset); + + Elf_Shdr* py_runtime_section = NULL; + for (int i = 0; i < elf_header->e_shnum; i++) { + if (strcmp(".PyRuntime", shstrtab + section_header_table[i].sh_name) == 0) { + py_runtime_section = §ion_header_table[i]; + break; + } + } + + Elf_Phdr* program_header_table = (Elf_Phdr*)(file_memory + elf_header->e_phoff); + // Find the first PT_LOAD segment + Elf_Phdr* first_load_segment = NULL; + for (int i = 0; i < elf_header->e_phnum; i++) { + if (program_header_table[i].p_type == PT_LOAD) { + first_load_segment = &program_header_table[i]; + break; + } + } + + if (py_runtime_section != NULL && first_load_segment != NULL) { + uintptr_t elf_load_addr = first_load_segment->p_vaddr + - (first_load_segment->p_vaddr % first_load_segment->p_align); + result = start_address + py_runtime_section->sh_addr - elf_load_addr; + } + +exit: + if (close(fd) != 0) { + PyErr_SetFromErrno(PyExc_OSError); + } + if (file_memory != NULL) { + munmap(file_memory, file_stats.st_size); + } + return result; +} +#endif + +ssize_t +read_memory(pid_t pid, void* remote_address, size_t len, void* dst) +{ + ssize_t total_bytes_read = 0; +#if defined(__linux__) && HAVE_PROCESS_VM_READV + struct iovec local[1]; + struct iovec remote[1]; + ssize_t result = 0; + ssize_t read = 0; + + do { + local[0].iov_base = dst + result; + local[0].iov_len = len - result; + remote[0].iov_base = (void*)(remote_address + result); + remote[0].iov_len = len - result; + + read = process_vm_readv(pid, local, 1, remote, 1, 0); + if (read < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + + result += read; + } while ((size_t)read != local[0].iov_len); + total_bytes_read = result; +#elif defined(__APPLE__) && TARGET_OS_OSX + ssize_t result = -1; + kern_return_t kr = mach_vm_read_overwrite( + pid_to_task(pid), + (mach_vm_address_t)remote_address, + len, + (mach_vm_address_t)dst, + (mach_vm_size_t*)&result); + + if (kr != KERN_SUCCESS) { + switch (kr) { + case KERN_PROTECTION_FAILURE: + PyErr_SetString(PyExc_PermissionError, "Not enough permissions to read memory"); + break; + case KERN_INVALID_ARGUMENT: + PyErr_SetString(PyExc_PermissionError, "Invalid argument to mach_vm_read_overwrite"); + break; + default: + PyErr_SetString(PyExc_RuntimeError, "Unknown error reading memory"); + } + return -1; + } + total_bytes_read = len; +#else + return -1; +#endif + return total_bytes_read; +} + +int +read_string(pid_t pid, _Py_DebugOffsets* debug_offsets, void* address, char* buffer, Py_ssize_t size) +{ + Py_ssize_t len; + ssize_t bytes_read = + read_memory(pid, address + debug_offsets->unicode_object.length, sizeof(Py_ssize_t), &len); + if (bytes_read == -1) { + return -1; + } + if (len >= size) { + PyErr_SetString(PyExc_RuntimeError, "Buffer too small"); + return -1; + } + size_t offset = debug_offsets->unicode_object.asciiobject_size; + bytes_read = read_memory(pid, address + offset, len, buffer); + if (bytes_read == -1) { + return -1; + } + buffer[len] = '\0'; + return 0; +} + +void* +get_py_runtime(pid_t pid) +{ +#if defined(__linux__) + return get_py_runtime_linux(pid); +#elif defined(__APPLE__) && TARGET_OS_OSX + return get_py_runtime_macos(pid); +#else + return NULL; +#endif +} + +static int +parse_code_object( + int pid, + PyObject* result, + struct _Py_DebugOffsets* offsets, + void* address, + void** previous_frame) +{ + void* address_of_function_name; + read_memory( + pid, + (void*)(address + offsets->code_object.name), + sizeof(void*), + &address_of_function_name); + + if (address_of_function_name == NULL) { + PyErr_SetString(PyExc_RuntimeError, "No function name found"); + return -1; + } + + char function_name[256]; + if (read_string(pid, offsets, address_of_function_name, function_name, sizeof(function_name)) != 0) { + return -1; + } + + PyObject* py_function_name = PyUnicode_FromString(function_name); + if (py_function_name == NULL) { + return -1; + } + + if (PyList_Append(result, py_function_name) == -1) { + Py_DECREF(py_function_name); + return -1; + } + Py_DECREF(py_function_name); + + return 0; +} + +static int +parse_frame_object( + int pid, + PyObject* result, + struct _Py_DebugOffsets* offsets, + void* address, + void** previous_frame) +{ + ssize_t bytes_read = read_memory( + pid, + (void*)(address + offsets->interpreter_frame.previous), + sizeof(void*), + previous_frame); + if (bytes_read == -1) { + return -1; + } + + char owner; + bytes_read = + read_memory(pid, (void*)(address + offsets->interpreter_frame.owner), sizeof(char), &owner); + if (bytes_read < 0) { + return -1; + } + + if (owner == FRAME_OWNED_BY_CSTACK) { + return 0; + } + + void* address_of_code_object; + bytes_read = read_memory( + pid, + (void*)(address + offsets->interpreter_frame.executable), + sizeof(void*), + &address_of_code_object); + if (bytes_read == -1) { + return -1; + } + + if (address_of_code_object == NULL) { + return 0; + } + return parse_code_object(pid, result, offsets, address_of_code_object, previous_frame); +} + +static PyObject* +get_stack_trace(PyObject* self, PyObject* args) +{ +#if (!defined(__linux__) && !defined(__APPLE__)) || (defined(__linux__) && !HAVE_PROCESS_VM_READV) + PyErr_SetString(PyExc_RuntimeError, "get_stack_trace is not supported on this platform"); + return NULL; +#endif + int pid; + + if (!PyArg_ParseTuple(args, "i", &pid)) { + return NULL; + } + + void* runtime_start_address = get_py_runtime(pid); + if (runtime_start_address == NULL) { + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_RuntimeError, "Failed to get .PyRuntime address"); + } + return NULL; + } + size_t size = sizeof(struct _Py_DebugOffsets); + struct _Py_DebugOffsets local_debug_offsets; + + ssize_t bytes_read = read_memory(pid, runtime_start_address, size, &local_debug_offsets); + if (bytes_read == -1) { + return NULL; + } + off_t thread_state_list_head = local_debug_offsets.runtime_state.interpreters_head; + + void* address_of_interpreter_state; + bytes_read = read_memory( + pid, + (void*)(runtime_start_address + thread_state_list_head), + sizeof(void*), + &address_of_interpreter_state); + if (bytes_read == -1) { + return NULL; + } + + if (address_of_interpreter_state == NULL) { + PyErr_SetString(PyExc_RuntimeError, "No interpreter state found"); + return NULL; + } + + void* address_of_thread; + bytes_read = read_memory( + pid, + (void*)(address_of_interpreter_state + local_debug_offsets.interpreter_state.threads_head), + sizeof(void*), + &address_of_thread); + if (bytes_read == -1) { + return NULL; + } + + PyObject* result = PyList_New(0); + if (result == NULL) { + return NULL; + } + + // No Python frames are available for us (can happen at tear-down). + if (address_of_thread != NULL) { + void* address_of_current_frame; + (void)read_memory( + pid, + (void*)(address_of_thread + local_debug_offsets.thread_state.current_frame), + sizeof(void*), + &address_of_current_frame); + while (address_of_current_frame != NULL) { + if (parse_frame_object( + pid, + result, + &local_debug_offsets, + address_of_current_frame, + &address_of_current_frame) + < 0) + { + Py_DECREF(result); + return NULL; + } + } + } + + return result; +} + +static PyMethodDef methods[] = { + {"get_stack_trace", get_stack_trace, METH_VARARGS, "Get the Python stack from a given PID"}, + {NULL, NULL, 0, NULL}, +}; + +static struct PyModuleDef module = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "_testexternalinspection", + .m_size = -1, + .m_methods = methods, +}; + +PyMODINIT_FUNC +PyInit__testexternalinspection(void) +{ + PyObject* mod = PyModule_Create(&module); + int rc = PyModule_AddIntConstant(mod, "PROCESS_VM_READV_SUPPORTED", HAVE_PROCESS_VM_READV); + if (rc < 0) { + Py_DECREF(mod); + return NULL; + } + return mod; +} diff --git a/Modules/_testimportmultiple.c b/Modules/_testimportmultiple.c index 7e6556ad400cdec..a65ca513a12516c 100644 --- a/Modules/_testimportmultiple.c +++ b/Modules/_testimportmultiple.c @@ -5,9 +5,8 @@ */ #include "pyconfig.h" // Py_GIL_DISABLED - #ifndef Py_GIL_DISABLED -#define Py_LIMITED_API 0x03020000 +# define Py_LIMITED_API 0x03020000 #endif #include diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 0d23b1899f22e44..1c10dd02138f3a4 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -24,10 +24,10 @@ #include "pycore_interp.h" // _PyInterpreterState_GetConfigCopy() #include "pycore_long.h" // _PyLong_Sign() #include "pycore_object.h" // _PyObject_IsFreed() +#include "pycore_optimizer.h" // _Py_UopsSymbol, etc. #include "pycore_pathconfig.h" // _PyPathConfig_ClearGlobal() #include "pycore_pyerrors.h" // _PyErr_ChainExceptions1() #include "pycore_pystate.h" // _PyThreadState_GET() -#include "pycore_typeobject.h" // _PyType_GetModuleName() #include "interpreteridobject.h" // PyInterpreterID_LookUp() @@ -1035,7 +1035,7 @@ static PyObject * invalidate_executors(PyObject *self, PyObject *obj) { PyInterpreterState *interp = PyInterpreterState_Get(); - _Py_Executors_InvalidateDependency(interp, obj); + _Py_Executors_InvalidateDependency(interp, obj, 1); Py_RETURN_NONE; } @@ -1287,8 +1287,8 @@ check_pyobject_forbidden_bytes_is_freed(PyObject *self, static PyObject * check_pyobject_freed_is_freed(PyObject *self, PyObject *Py_UNUSED(args)) { - /* This test would fail if run with the address sanitizer */ -#ifdef _Py_ADDRESS_SANITIZER + /* ASan or TSan would report an use-after-free error */ +#if defined(_Py_ADDRESS_SANITIZER) || defined(_Py_THREAD_SANITIZER) Py_RETURN_NONE; #else PyObject *op = PyObject_CallNoArgs((PyObject *)&PyBaseObject_Type); @@ -1630,13 +1630,6 @@ perf_trampoline_set_persist_after_fork(PyObject *self, PyObject *args) } -static PyObject * -get_type_module_name(PyObject *self, PyObject *type) -{ - assert(PyType_Check(type)); - return _PyType_GetModuleName((PyTypeObject *)type); -} - static PyObject * get_rare_event_counters(PyObject *self, PyObject *type) { @@ -1677,7 +1670,6 @@ get_py_thread_id(PyObject *self, PyObject *Py_UNUSED(ignored)) } #endif - static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, @@ -1741,12 +1733,12 @@ static PyMethodDef module_functions[] = { {"get_crossinterp_data", get_crossinterp_data, METH_VARARGS}, {"restore_crossinterp_data", restore_crossinterp_data, METH_VARARGS}, _TESTINTERNALCAPI_TEST_LONG_NUMBITS_METHODDEF - {"get_type_module_name", get_type_module_name, METH_O}, {"get_rare_event_counters", get_rare_event_counters, METH_NOARGS}, {"reset_rare_event_counters", reset_rare_event_counters, METH_NOARGS}, #ifdef Py_GIL_DISABLED {"py_thread_id", get_py_thread_id, METH_NOARGS}, #endif + {"uop_symbols_test", _Py_uop_symbols_test, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c new file mode 100644 index 000000000000000..49bf6a3ea39613f --- /dev/null +++ b/Modules/_testlimitedcapi.c @@ -0,0 +1,48 @@ +/* + * Test the limited C API. + * + * The 'test_*' functions exported by this module are run as part of the + * standard Python regression test, via Lib/test/test_capi.py. + */ + +#include "_testlimitedcapi/parts.h" + +static PyMethodDef TestMethods[] = { + {NULL, NULL} /* sentinel */ +}; + +static struct PyModuleDef _testlimitedcapimodule = { + PyModuleDef_HEAD_INIT, + .m_name = "_testlimitedcapi", + .m_size = 0, + .m_methods = TestMethods, +}; + +PyMODINIT_FUNC +PyInit__testlimitedcapi(void) +{ + PyObject *mod = PyModule_Create(&_testlimitedcapimodule); + if (mod == NULL) { + return NULL; + } + + if (_PyTestCapi_Init_ByteArray(mod) < 0) { + return NULL; + } + if (_PyTestCapi_Init_Bytes(mod) < 0) { + return NULL; + } + if (_PyTestCapi_Init_HeaptypeRelative(mod) < 0) { + return NULL; + } + if (_PyTestCapi_Init_PyOS(mod) < 0) { + return NULL; + } + if (_PyTestCapi_Init_Sys(mod) < 0) { + return NULL; + } + if (_PyTestCapi_Init_VectorcallLimited(mod) < 0) { + return NULL; + } + return mod; +} diff --git a/Modules/_testcapi/bytearray.c b/Modules/_testlimitedcapi/bytearray.c similarity index 100% rename from Modules/_testcapi/bytearray.c rename to Modules/_testlimitedcapi/bytearray.c diff --git a/Modules/_testcapi/bytes.c b/Modules/_testlimitedcapi/bytes.c similarity index 95% rename from Modules/_testcapi/bytes.c rename to Modules/_testlimitedcapi/bytes.c index da10503f6f68563..a14c4f9d4d30a8c 100644 --- a/Modules/_testcapi/bytes.c +++ b/Modules/_testlimitedcapi/bytes.c @@ -160,8 +160,8 @@ bytes_concat(PyObject *Py_UNUSED(module), PyObject *args) if (new) { assert(left != NULL); assert(PyBytes_CheckExact(left)); - left = PyBytes_FromStringAndSize(PyBytes_AS_STRING(left), - PyBytes_GET_SIZE(left)); + left = PyBytes_FromStringAndSize(PyBytes_AsString(left), + PyBytes_Size(left)); if (left == NULL) { return NULL; } @@ -191,8 +191,8 @@ bytes_concatanddel(PyObject *Py_UNUSED(module), PyObject *args) if (new) { assert(left != NULL); assert(PyBytes_CheckExact(left)); - left = PyBytes_FromStringAndSize(PyBytes_AS_STRING(left), - PyBytes_GET_SIZE(left)); + left = PyBytes_FromStringAndSize(PyBytes_AsString(left), + PyBytes_Size(left)); if (left == NULL) { return NULL; } diff --git a/Modules/_testlimitedcapi/clinic/vectorcall_limited.c.h b/Modules/_testlimitedcapi/clinic/vectorcall_limited.c.h new file mode 100644 index 000000000000000..6631a9c42deab09 --- /dev/null +++ b/Modules/_testlimitedcapi/clinic/vectorcall_limited.c.h @@ -0,0 +1,20 @@ +/*[clinic input] +preserve +[clinic start generated code]*/ + +PyDoc_STRVAR(_testlimitedcapi_call_vectorcall__doc__, +"call_vectorcall($module, callable, /)\n" +"--\n" +"\n"); + +#define _TESTLIMITEDCAPI_CALL_VECTORCALL_METHODDEF \ + {"call_vectorcall", (PyCFunction)_testlimitedcapi_call_vectorcall, METH_O, _testlimitedcapi_call_vectorcall__doc__}, + +PyDoc_STRVAR(_testlimitedcapi_call_vectorcall_method__doc__, +"call_vectorcall_method($module, callable, /)\n" +"--\n" +"\n"); + +#define _TESTLIMITEDCAPI_CALL_VECTORCALL_METHOD_METHODDEF \ + {"call_vectorcall_method", (PyCFunction)_testlimitedcapi_call_vectorcall_method, METH_O, _testlimitedcapi_call_vectorcall_method__doc__}, +/*[clinic end generated code: output=5976b9b360e1ff30 input=a9049054013a1b77]*/ diff --git a/Modules/_testcapi/heaptype_relative.c b/Modules/_testlimitedcapi/heaptype_relative.c similarity index 98% rename from Modules/_testcapi/heaptype_relative.c rename to Modules/_testlimitedcapi/heaptype_relative.c index 52bda75736b3167..d0316dd4fc63b4b 100644 --- a/Modules/_testcapi/heaptype_relative.c +++ b/Modules/_testlimitedcapi/heaptype_relative.c @@ -1,7 +1,7 @@ +// Need limited C API version 3.12 for PyType_FromMetaclass() #include "pyconfig.h" // Py_GIL_DISABLED - -#ifndef Py_GIL_DISABLED -#define Py_LIMITED_API 0x030c0000 // 3.12 +#if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API ) +# define Py_LIMITED_API 0x030c0000 #endif #include "parts.h" diff --git a/Modules/_testlimitedcapi/parts.h b/Modules/_testlimitedcapi/parts.h new file mode 100644 index 000000000000000..9bc52413382eb57 --- /dev/null +++ b/Modules/_testlimitedcapi/parts.h @@ -0,0 +1,32 @@ +#ifndef Py_TESTLIMITEDCAPI_PARTS_H +#define Py_TESTLIMITEDCAPI_PARTS_H + +// Always enable assertions +#undef NDEBUG + +#include "pyconfig.h" // Py_GIL_DISABLED + +// Use the limited C API +#if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API ) + // need limited C API version 3.5 for PyModule_AddFunctions() +# define Py_LIMITED_API 0x03050000 +#endif + +// Make sure that the internal C API cannot be used. +#undef Py_BUILD_CORE_MODULE +#undef Py_BUILD_CORE_BUILTIN + +#include "Python.h" + +#ifdef Py_BUILD_CORE +# error "Py_BUILD_CORE macro must not be defined" +#endif + +int _PyTestCapi_Init_ByteArray(PyObject *module); +int _PyTestCapi_Init_Bytes(PyObject *module); +int _PyTestCapi_Init_HeaptypeRelative(PyObject *module); +int _PyTestCapi_Init_PyOS(PyObject *module); +int _PyTestCapi_Init_Sys(PyObject *module); +int _PyTestCapi_Init_VectorcallLimited(PyObject *module); + +#endif // Py_TESTLIMITEDCAPI_PARTS_H diff --git a/Modules/_testcapi/pyos.c b/Modules/_testlimitedcapi/pyos.c similarity index 100% rename from Modules/_testcapi/pyos.c rename to Modules/_testlimitedcapi/pyos.c diff --git a/Modules/_testcapi/sys.c b/Modules/_testlimitedcapi/sys.c similarity index 100% rename from Modules/_testcapi/sys.c rename to Modules/_testlimitedcapi/sys.c diff --git a/Modules/_testlimitedcapi/util.h b/Modules/_testlimitedcapi/util.h new file mode 100644 index 000000000000000..f26d7656a10138f --- /dev/null +++ b/Modules/_testlimitedcapi/util.h @@ -0,0 +1,33 @@ +#define NULLABLE(x) do { \ + if (x == Py_None) { \ + x = NULL; \ + } \ + } while (0); + +#define RETURN_INT(value) do { \ + int _ret = (value); \ + if (_ret == -1) { \ + assert(PyErr_Occurred()); \ + return NULL; \ + } \ + assert(!PyErr_Occurred()); \ + return PyLong_FromLong(_ret); \ + } while (0) + +#define RETURN_SIZE(value) do { \ + Py_ssize_t _ret = (value); \ + if (_ret == -1) { \ + assert(PyErr_Occurred()); \ + return NULL; \ + } \ + assert(!PyErr_Occurred()); \ + return PyLong_FromSsize_t(_ret); \ + } while (0) + +/* Marker to check that pointer value was set. */ +static const char uninitialized[] = "uninitialized"; +#define UNINITIALIZED_PTR ((void *)uninitialized) +/* Marker to check that Py_ssize_t value was set. */ +#define UNINITIALIZED_SIZE ((Py_ssize_t)236892191) +/* Marker to check that integer value was set. */ +#define UNINITIALIZED_INT (63256717) diff --git a/Modules/_testcapi/vectorcall_limited.c b/Modules/_testlimitedcapi/vectorcall_limited.c similarity index 83% rename from Modules/_testcapi/vectorcall_limited.c rename to Modules/_testlimitedcapi/vectorcall_limited.c index d7070d37bb9e9bd..fc1a89c9098e1b3 100644 --- a/Modules/_testcapi/vectorcall_limited.c +++ b/Modules/_testlimitedcapi/vectorcall_limited.c @@ -1,18 +1,18 @@ /* Test Vectorcall in the limited API */ +// Need limited C API version 3.12 for PyObject_Vectorcall() #include "pyconfig.h" // Py_GIL_DISABLED - -#ifndef Py_GIL_DISABLED -#define Py_LIMITED_API 0x030c0000 // 3.12 +#if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API ) +# define Py_LIMITED_API 0x030c0000 #endif #include "parts.h" #include "clinic/vectorcall_limited.c.h" /*[clinic input] -module _testcapi +module _testlimitedcapi [clinic start generated code]*/ -/*[clinic end generated code: output=da39a3ee5e6b4b0d input=6361033e795369fc]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=2700057f9c1135ba]*/ static PyObject * LimitedVectorCallClass_tpcall(PyObject *self, PyObject *args, PyObject *kwargs) { @@ -40,15 +40,15 @@ LimitedVectorCallClass_new(PyTypeObject *tp, PyTypeObject *a, PyTypeObject *kw) } /*[clinic input] -_testcapi.call_vectorcall +_testlimitedcapi.call_vectorcall callable: object / [clinic start generated code]*/ static PyObject * -_testcapi_call_vectorcall(PyObject *module, PyObject *callable) -/*[clinic end generated code: output=bae81eec97fcaad7 input=55d88f92240957ee]*/ +_testlimitedcapi_call_vectorcall(PyObject *module, PyObject *callable) +/*[clinic end generated code: output=9cbb7832263a8eef input=0743636c12dccb28]*/ { PyObject *args[3] = { NULL, NULL, NULL }; PyObject *kwname = NULL, *kwnames = NULL, *result = NULL; @@ -93,15 +93,15 @@ _testcapi_call_vectorcall(PyObject *module, PyObject *callable) } /*[clinic input] -_testcapi.call_vectorcall_method +_testlimitedcapi.call_vectorcall_method callable: object / [clinic start generated code]*/ static PyObject * -_testcapi_call_vectorcall_method(PyObject *module, PyObject *callable) -/*[clinic end generated code: output=e661f48dda08b6fb input=5ba81c27511395b6]*/ +_testlimitedcapi_call_vectorcall_method(PyObject *module, PyObject *callable) +/*[clinic end generated code: output=4558323a46cc09eb input=a736f7dbf15f1be5]*/ { PyObject *args[3] = { NULL, NULL, NULL }; PyObject *name = NULL, *kwname = NULL, @@ -167,7 +167,7 @@ static PyType_Slot LimitedVectorallClass_slots[] = { }; static PyType_Spec LimitedVectorCallClass_spec = { - .name = "_testcapi.LimitedVectorCallClass", + .name = "_testlimitedcapi.LimitedVectorCallClass", .basicsize = (int)(sizeof(PyObject) + sizeof(vectorcallfunc)), .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_VECTORCALL @@ -176,8 +176,8 @@ static PyType_Spec LimitedVectorCallClass_spec = { }; static PyMethodDef TestMethods[] = { - _TESTCAPI_CALL_VECTORCALL_METHODDEF - _TESTCAPI_CALL_VECTORCALL_METHOD_METHODDEF + _TESTLIMITEDCAPI_CALL_VECTORCALL_METHODDEF + _TESTLIMITEDCAPI_CALL_VECTORCALL_METHOD_METHODDEF {NULL}, }; diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 4c2185cc7ea1fd3..6889e8f6e12126a 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1,9 +1,9 @@ - /* Thread module */ /* Interface to Sjoerd's portable C thread library */ #include "Python.h" #include "pycore_interp.h" // _PyInterpreterState.threads.count +#include "pycore_lock.h" #include "pycore_moduleobject.h" // _PyModule_GetState() #include "pycore_modsupport.h" // _PyArg_NoKeywords() #include "pycore_pylifecycle.h" @@ -12,7 +12,6 @@ #include "pycore_time.h" // _PyTime_FromSeconds() #include "pycore_weakref.h" // _PyWeakref_GET_REF() -#include #include // offsetof() #ifdef HAVE_SIGNAL_H # include // SIGINT @@ -21,7 +20,6 @@ // ThreadError is just an alias to PyExc_RuntimeError #define ThreadError PyExc_RuntimeError - // Forward declarations static struct PyModuleDef thread_module; @@ -32,6 +30,10 @@ typedef struct { PyTypeObject *local_type; PyTypeObject *local_dummy_type; PyTypeObject *thread_handle_type; + + // Linked list of handles to all non-daemon threads created by the + // threading module. We wait for these to finish at shutdown. + struct llist_node shutdown_handles; } thread_module_state; static inline thread_module_state* @@ -44,24 +46,148 @@ get_thread_state(PyObject *module) // _ThreadHandle type +// Handles state transitions according to the following diagram: +// +// NOT_STARTED -> STARTING -> RUNNING -> DONE +// | ^ +// | | +// +----- error --------+ +typedef enum { + THREAD_HANDLE_NOT_STARTED = 1, + THREAD_HANDLE_STARTING = 2, + THREAD_HANDLE_RUNNING = 3, + THREAD_HANDLE_DONE = 4, +} ThreadHandleState; + +// A handle to wait for thread completion. +// +// This may be used to wait for threads that were spawned by the threading +// module as well as for the "main" thread of the threading module. In the +// former case an OS thread, identified by the `os_handle` field, will be +// associated with the handle. The handle "owns" this thread and ensures that +// the thread is either joined or detached after the handle is destroyed. +// +// Joining the handle is idempotent; the underlying OS thread, if any, is +// joined or detached only once. Concurrent join operations are serialized +// until it is their turn to execute or an earlier operation completes +// successfully. Once a join has completed successfully all future joins +// complete immediately. +// +// This must be separately reference counted because it may be destroyed +// in `thread_run()` after the PyThreadState has been destroyed. typedef struct { - PyObject_HEAD struct llist_node node; // linked list node (see _pythread_runtime_state) + + // linked list node (see thread_module_state) + struct llist_node shutdown_node; + + // The `ident`, `os_handle`, `has_os_handle`, and `state` fields are + // protected by `mutex`. PyThread_ident_t ident; - PyThread_handle_t handle; - char joinable; -} ThreadHandleObject; + PyThread_handle_t os_handle; + int has_os_handle; + + // Holds a value from the `ThreadHandleState` enum. + int state; + + PyMutex mutex; -static ThreadHandleObject* -new_thread_handle(thread_module_state* state) + // Set immediately before `thread_run` returns to indicate that the OS + // thread is about to exit. This is used to avoid false positives when + // detecting self-join attempts. See the comment in `ThreadHandle_join()` + // for a more detailed explanation. + PyEvent thread_is_exiting; + + // Serializes calls to `join` and `set_done`. + _PyOnceFlag once; + + Py_ssize_t refcount; +} ThreadHandle; + +static inline int +get_thread_handle_state(ThreadHandle *handle) +{ + PyMutex_Lock(&handle->mutex); + int state = handle->state; + PyMutex_Unlock(&handle->mutex); + return state; +} + +static inline void +set_thread_handle_state(ThreadHandle *handle, ThreadHandleState state) { - ThreadHandleObject* self = PyObject_New(ThreadHandleObject, state->thread_handle_type); + PyMutex_Lock(&handle->mutex); + handle->state = state; + PyMutex_Unlock(&handle->mutex); +} + +static PyThread_ident_t +ThreadHandle_ident(ThreadHandle *handle) +{ + PyMutex_Lock(&handle->mutex); + PyThread_ident_t ident = handle->ident; + PyMutex_Unlock(&handle->mutex); + return ident; +} + +static int +ThreadHandle_get_os_handle(ThreadHandle *handle, PyThread_handle_t *os_handle) +{ + PyMutex_Lock(&handle->mutex); + int has_os_handle = handle->has_os_handle; + if (has_os_handle) { + *os_handle = handle->os_handle; + } + PyMutex_Unlock(&handle->mutex); + return has_os_handle; +} + +static void +add_to_shutdown_handles(thread_module_state *state, ThreadHandle *handle) +{ + HEAD_LOCK(&_PyRuntime); + llist_insert_tail(&state->shutdown_handles, &handle->shutdown_node); + HEAD_UNLOCK(&_PyRuntime); +} + +static void +clear_shutdown_handles(thread_module_state *state) +{ + HEAD_LOCK(&_PyRuntime); + struct llist_node *node; + llist_for_each_safe(node, &state->shutdown_handles) { + llist_remove(node); + } + HEAD_UNLOCK(&_PyRuntime); +} + +static void +remove_from_shutdown_handles(ThreadHandle *handle) +{ + HEAD_LOCK(&_PyRuntime); + if (handle->shutdown_node.next != NULL) { + llist_remove(&handle->shutdown_node); + } + HEAD_UNLOCK(&_PyRuntime); +} + +static ThreadHandle * +ThreadHandle_new(void) +{ + ThreadHandle *self = + (ThreadHandle *)PyMem_RawCalloc(1, sizeof(ThreadHandle)); if (self == NULL) { + PyErr_NoMemory(); return NULL; } self->ident = 0; - self->handle = 0; - self->joinable = 0; + self->os_handle = 0; + self->has_os_handle = 0; + self->thread_is_exiting = (PyEvent){0}; + self->mutex = (PyMutex){_Py_UNLOCKED}; + self->once = (_PyOnceFlag){0}; + self->state = THREAD_HANDLE_NOT_STARTED; + self->refcount = 1; HEAD_LOCK(&_PyRuntime); llist_insert_tail(&_PyRuntime.threads.handles, &self->node); @@ -71,9 +197,34 @@ new_thread_handle(thread_module_state* state) } static void -ThreadHandle_dealloc(ThreadHandleObject *self) +ThreadHandle_incref(ThreadHandle *self) { - PyObject *tp = (PyObject *) Py_TYPE(self); + _Py_atomic_add_ssize(&self->refcount, 1); +} + +static int +detach_thread(ThreadHandle *self) +{ + if (!self->has_os_handle) { + return 0; + } + // This is typically short so no need to release the GIL + if (PyThread_detach_thread(self->os_handle)) { + fprintf(stderr, "detach_thread: failed detaching thread\n"); + return -1; + } + return 0; +} + +// NB: This may be called after the PyThreadState in `thread_run` has been +// deleted; it cannot call anything that relies on a valid PyThreadState +// existing. +static void +ThreadHandle_decref(ThreadHandle *self) +{ + if (_Py_atomic_add_ssize(&self->refcount, -1) > 1) { + return; + } // Remove ourself from the global list of handles HEAD_LOCK(&_PyRuntime); @@ -82,15 +233,17 @@ ThreadHandle_dealloc(ThreadHandleObject *self) } HEAD_UNLOCK(&_PyRuntime); - if (self->joinable) { - int ret = PyThread_detach_thread(self->handle); - if (ret) { - PyErr_SetString(ThreadError, "Failed detaching thread"); - PyErr_WriteUnraisable(tp); - } + assert(self->shutdown_node.next == NULL); + + // It's safe to access state non-atomically: + // 1. This is the destructor; nothing else holds a reference. + // 2. The refcount going to zero is a "synchronizes-with" event; all + // changes from other threads are visible. + if (self->state == THREAD_HANDLE_RUNNING && !detach_thread(self)) { + self->state = THREAD_HANDLE_DONE; } - PyObject_Free(self); - Py_DECREF(tp); + + PyMem_RawFree(self); } void @@ -104,100 +257,442 @@ _PyThread_AfterFork(struct _pythread_runtime_state *state) struct llist_node *node; llist_for_each_safe(node, &state->handles) { - ThreadHandleObject *hobj = llist_data(node, ThreadHandleObject, node); - if (hobj->ident == current) { + ThreadHandle *handle = llist_data(node, ThreadHandle, node); + if (handle->ident == current) { continue; } - // Disallow calls to detach() and join() as they could crash. - hobj->joinable = 0; + // Mark all threads as done. Any attempts to join or detach the + // underlying OS thread (if any) could crash. We are the only thread; + // it's safe to set this non-atomically. + handle->state = THREAD_HANDLE_DONE; + handle->once = (_PyOnceFlag){_Py_ONCE_INITIALIZED}; + handle->mutex = (PyMutex){_Py_UNLOCKED}; + _PyEvent_Notify(&handle->thread_is_exiting); llist_remove(node); + remove_from_shutdown_handles(handle); + } +} + +// bootstate is used to "bootstrap" new threads. Any arguments needed by +// `thread_run()`, which can only take a single argument due to platform +// limitations, are contained in bootstate. +struct bootstate { + PyThreadState *tstate; + PyObject *func; + PyObject *args; + PyObject *kwargs; + ThreadHandle *handle; + PyEvent handle_ready; +}; + +static void +thread_bootstate_free(struct bootstate *boot, int decref) +{ + if (decref) { + Py_DECREF(boot->func); + Py_DECREF(boot->args); + Py_XDECREF(boot->kwargs); } + ThreadHandle_decref(boot->handle); + PyMem_RawFree(boot); +} + +static void +thread_run(void *boot_raw) +{ + struct bootstate *boot = (struct bootstate *) boot_raw; + PyThreadState *tstate = boot->tstate; + + // Wait until the handle is marked as running + PyEvent_Wait(&boot->handle_ready); + + // `handle` needs to be manipulated after bootstate has been freed + ThreadHandle *handle = boot->handle; + ThreadHandle_incref(handle); + + // gh-108987: If _thread.start_new_thread() is called before or while + // Python is being finalized, thread_run() can called *after*. + // _PyRuntimeState_SetFinalizing() is called. At this point, all Python + // threads must exit, except of the thread calling Py_Finalize() whch holds + // the GIL and must not exit. + // + // At this stage, tstate can be a dangling pointer (point to freed memory), + // it's ok to call _PyThreadState_MustExit() with a dangling pointer. + if (_PyThreadState_MustExit(tstate)) { + // Don't call PyThreadState_Clear() nor _PyThreadState_DeleteCurrent(). + // These functions are called on tstate indirectly by Py_Finalize() + // which calls _PyInterpreterState_Clear(). + // + // Py_DECREF() cannot be called because the GIL is not held: leak + // references on purpose. Python is being finalized anyway. + thread_bootstate_free(boot, 0); + goto exit; + } + + _PyThreadState_Bind(tstate); + PyEval_AcquireThread(tstate); + _Py_atomic_add_ssize(&tstate->interp->threads.count, 1); + + PyObject *res = PyObject_Call(boot->func, boot->args, boot->kwargs); + if (res == NULL) { + if (PyErr_ExceptionMatches(PyExc_SystemExit)) + /* SystemExit is ignored silently */ + PyErr_Clear(); + else { + PyErr_FormatUnraisable( + "Exception ignored in thread started by %R", boot->func); + } + } + else { + Py_DECREF(res); + } + + thread_bootstate_free(boot, 1); + + _Py_atomic_add_ssize(&tstate->interp->threads.count, -1); + PyThreadState_Clear(tstate); + _PyThreadState_DeleteCurrent(tstate); + +exit: + // Don't need to wait for this thread anymore + remove_from_shutdown_handles(handle); + + _PyEvent_Notify(&handle->thread_is_exiting); + ThreadHandle_decref(handle); + + // bpo-44434: Don't call explicitly PyThread_exit_thread(). On Linux with + // the glibc, pthread_exit() can abort the whole process if dlopen() fails + // to open the libgcc_s.so library (ex: EMFILE error). + return; +} + +static int +force_done(ThreadHandle *handle) +{ + assert(get_thread_handle_state(handle) == THREAD_HANDLE_STARTING); + _PyEvent_Notify(&handle->thread_is_exiting); + set_thread_handle_state(handle, THREAD_HANDLE_DONE); + return 0; +} + +static int +ThreadHandle_start(ThreadHandle *self, PyObject *func, PyObject *args, + PyObject *kwargs) +{ + // Mark the handle as starting to prevent any other threads from doing so + PyMutex_Lock(&self->mutex); + if (self->state != THREAD_HANDLE_NOT_STARTED) { + PyMutex_Unlock(&self->mutex); + PyErr_SetString(ThreadError, "thread already started"); + return -1; + } + self->state = THREAD_HANDLE_STARTING; + PyMutex_Unlock(&self->mutex); + + // Do all the heavy lifting outside of the mutex. All other operations on + // the handle should fail since the handle is in the starting state. + + // gh-109795: Use PyMem_RawMalloc() instead of PyMem_Malloc(), + // because it should be possible to call thread_bootstate_free() + // without holding the GIL. + struct bootstate *boot = PyMem_RawMalloc(sizeof(struct bootstate)); + if (boot == NULL) { + PyErr_NoMemory(); + goto start_failed; + } + PyInterpreterState *interp = _PyInterpreterState_GET(); + boot->tstate = _PyThreadState_New(interp, _PyThreadState_WHENCE_THREADING); + if (boot->tstate == NULL) { + PyMem_RawFree(boot); + if (!PyErr_Occurred()) { + PyErr_NoMemory(); + } + goto start_failed; + } + boot->func = Py_NewRef(func); + boot->args = Py_NewRef(args); + boot->kwargs = Py_XNewRef(kwargs); + boot->handle = self; + ThreadHandle_incref(self); + boot->handle_ready = (PyEvent){0}; + + PyThread_ident_t ident; + PyThread_handle_t os_handle; + if (PyThread_start_joinable_thread(thread_run, boot, &ident, &os_handle)) { + PyThreadState_Clear(boot->tstate); + thread_bootstate_free(boot, 1); + PyErr_SetString(ThreadError, "can't start new thread"); + goto start_failed; + } + + // Mark the handle running + PyMutex_Lock(&self->mutex); + assert(self->state == THREAD_HANDLE_STARTING); + self->ident = ident; + self->has_os_handle = 1; + self->os_handle = os_handle; + self->state = THREAD_HANDLE_RUNNING; + PyMutex_Unlock(&self->mutex); + + // Unblock the thread + _PyEvent_Notify(&boot->handle_ready); + + return 0; + +start_failed: + _PyOnceFlag_CallOnce(&self->once, (_Py_once_fn_t *)force_done, self); + return -1; +} + +static int +join_thread(ThreadHandle *handle) +{ + assert(get_thread_handle_state(handle) == THREAD_HANDLE_RUNNING); + PyThread_handle_t os_handle; + if (ThreadHandle_get_os_handle(handle, &os_handle)) { + int err = 0; + Py_BEGIN_ALLOW_THREADS + err = PyThread_join_thread(os_handle); + Py_END_ALLOW_THREADS + if (err) { + PyErr_SetString(ThreadError, "Failed joining thread"); + return -1; + } + } + set_thread_handle_state(handle, THREAD_HANDLE_DONE); + return 0; +} + +static int +check_started(ThreadHandle *self) +{ + ThreadHandleState state = get_thread_handle_state(self); + if (state < THREAD_HANDLE_RUNNING) { + PyErr_SetString(ThreadError, "thread not started"); + return -1; + } + return 0; +} + +static int +ThreadHandle_join(ThreadHandle *self, PyTime_t timeout_ns) +{ + if (check_started(self) < 0) { + return -1; + } + + // We want to perform this check outside of the `_PyOnceFlag` to prevent + // deadlock in the scenario where another thread joins us and we then + // attempt to join ourselves. However, it's not safe to check thread + // identity once the handle's os thread has finished. We may end up reusing + // the identity stored in the handle and erroneously think we are + // attempting to join ourselves. + // + // To work around this, we set `thread_is_exiting` immediately before + // `thread_run` returns. We can be sure that we are not attempting to join + // ourselves if the handle's thread is about to exit. + if (!_PyEvent_IsSet(&self->thread_is_exiting) && + ThreadHandle_ident(self) == PyThread_get_thread_ident_ex()) { + // PyThread_join_thread() would deadlock or error out. + PyErr_SetString(ThreadError, "Cannot join current thread"); + return -1; + } + + // Wait until the deadline for the thread to exit. + PyTime_t deadline = timeout_ns != -1 ? _PyDeadline_Init(timeout_ns) : 0; + while (!PyEvent_WaitTimed(&self->thread_is_exiting, timeout_ns)) { + if (deadline) { + // _PyDeadline_Get will return a negative value if the deadline has + // been exceeded. + timeout_ns = Py_MAX(_PyDeadline_Get(deadline), 0); + } + + if (timeout_ns) { + // Interrupted + if (Py_MakePendingCalls() < 0) { + return -1; + } + } + else { + // Timed out + return 0; + } + } + + if (_PyOnceFlag_CallOnce(&self->once, (_Py_once_fn_t *)join_thread, + self) == -1) { + return -1; + } + assert(get_thread_handle_state(self) == THREAD_HANDLE_DONE); + return 0; +} + +static int +set_done(ThreadHandle *handle) +{ + assert(get_thread_handle_state(handle) == THREAD_HANDLE_RUNNING); + if (detach_thread(handle) < 0) { + PyErr_SetString(ThreadError, "failed detaching handle"); + return -1; + } + _PyEvent_Notify(&handle->thread_is_exiting); + set_thread_handle_state(handle, THREAD_HANDLE_DONE); + return 0; +} + +static int +ThreadHandle_set_done(ThreadHandle *self) +{ + if (check_started(self) < 0) { + return -1; + } + + if (_PyOnceFlag_CallOnce(&self->once, (_Py_once_fn_t *)set_done, self) == + -1) { + return -1; + } + assert(get_thread_handle_state(self) == THREAD_HANDLE_DONE); + return 0; +} + +// A wrapper around a ThreadHandle. +typedef struct { + PyObject_HEAD + + ThreadHandle *handle; +} PyThreadHandleObject; + +static PyThreadHandleObject * +PyThreadHandleObject_new(PyTypeObject *type) +{ + ThreadHandle *handle = ThreadHandle_new(); + if (handle == NULL) { + return NULL; + } + + PyThreadHandleObject *self = + (PyThreadHandleObject *)type->tp_alloc(type, 0); + if (self == NULL) { + ThreadHandle_decref(handle); + return NULL; + } + + self->handle = handle; + + return self; } static PyObject * -ThreadHandle_repr(ThreadHandleObject *self) +PyThreadHandleObject_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { - return PyUnicode_FromFormat("<%s object: ident=%" PY_FORMAT_THREAD_IDENT_T ">", - Py_TYPE(self)->tp_name, self->ident); + return (PyObject *)PyThreadHandleObject_new(type); +} + +static int +PyThreadHandleObject_traverse(PyThreadHandleObject *self, visitproc visit, + void *arg) +{ + Py_VISIT(Py_TYPE(self)); + return 0; +} + +static void +PyThreadHandleObject_dealloc(PyThreadHandleObject *self) +{ + PyObject_GC_UnTrack(self); + PyTypeObject *tp = Py_TYPE(self); + ThreadHandle_decref(self->handle); + tp->tp_free(self); + Py_DECREF(tp); } static PyObject * -ThreadHandle_get_ident(ThreadHandleObject *self, void *ignored) +PyThreadHandleObject_repr(PyThreadHandleObject *self) { - return PyLong_FromUnsignedLongLong(self->ident); + PyThread_ident_t ident = ThreadHandle_ident(self->handle); + return PyUnicode_FromFormat("<%s object: ident=%" PY_FORMAT_THREAD_IDENT_T ">", + Py_TYPE(self)->tp_name, ident); } +static PyObject * +PyThreadHandleObject_get_ident(PyThreadHandleObject *self, + PyObject *Py_UNUSED(ignored)) +{ + return PyLong_FromUnsignedLongLong(ThreadHandle_ident(self->handle)); +} static PyObject * -ThreadHandle_detach(ThreadHandleObject *self, void* ignored) +PyThreadHandleObject_join(PyThreadHandleObject *self, PyObject *args) { - if (!self->joinable) { - PyErr_SetString(PyExc_ValueError, - "the thread is not joinable and thus cannot be detached"); + PyObject *timeout_obj = NULL; + if (!PyArg_ParseTuple(args, "|O:join", &timeout_obj)) { return NULL; } - self->joinable = 0; - // This is typically short so no need to release the GIL - int ret = PyThread_detach_thread(self->handle); - if (ret) { - PyErr_SetString(ThreadError, "Failed detaching thread"); + + PyTime_t timeout_ns = -1; + if (timeout_obj != NULL && timeout_obj != Py_None) { + if (_PyTime_FromSecondsObject(&timeout_ns, timeout_obj, + _PyTime_ROUND_TIMEOUT) < 0) { + return NULL; + } + } + + if (ThreadHandle_join(self->handle, timeout_ns) < 0) { return NULL; } Py_RETURN_NONE; } static PyObject * -ThreadHandle_join(ThreadHandleObject *self, void* ignored) +PyThreadHandleObject_is_done(PyThreadHandleObject *self, + PyObject *Py_UNUSED(ignored)) { - if (!self->joinable) { - PyErr_SetString(PyExc_ValueError, "the thread is not joinable"); - return NULL; + if (_PyEvent_IsSet(&self->handle->thread_is_exiting)) { + Py_RETURN_TRUE; } - if (self->ident == PyThread_get_thread_ident_ex()) { - // PyThread_join_thread() would deadlock or error out. - PyErr_SetString(ThreadError, "Cannot join current thread"); - return NULL; + else { + Py_RETURN_FALSE; } - // Before actually joining, we must first mark the thread as non-joinable, - // as joining several times simultaneously or sequentially is undefined behavior. - self->joinable = 0; - int ret; - Py_BEGIN_ALLOW_THREADS - ret = PyThread_join_thread(self->handle); - Py_END_ALLOW_THREADS - if (ret) { - PyErr_SetString(ThreadError, "Failed joining thread"); +} + +static PyObject * +PyThreadHandleObject_set_done(PyThreadHandleObject *self, + PyObject *Py_UNUSED(ignored)) +{ + if (ThreadHandle_set_done(self->handle) < 0) { return NULL; } Py_RETURN_NONE; } static PyGetSetDef ThreadHandle_getsetlist[] = { - {"ident", (getter)ThreadHandle_get_ident, NULL, NULL}, + {"ident", (getter)PyThreadHandleObject_get_ident, NULL, NULL}, {0}, }; -static PyMethodDef ThreadHandle_methods[] = -{ - {"detach", (PyCFunction)ThreadHandle_detach, METH_NOARGS}, - {"join", (PyCFunction)ThreadHandle_join, METH_NOARGS}, +static PyMethodDef ThreadHandle_methods[] = { + {"join", (PyCFunction)PyThreadHandleObject_join, METH_VARARGS, NULL}, + {"_set_done", (PyCFunction)PyThreadHandleObject_set_done, METH_NOARGS, NULL}, + {"is_done", (PyCFunction)PyThreadHandleObject_is_done, METH_NOARGS, NULL}, {0, 0} }; static PyType_Slot ThreadHandle_Type_slots[] = { - {Py_tp_dealloc, (destructor)ThreadHandle_dealloc}, - {Py_tp_repr, (reprfunc)ThreadHandle_repr}, + {Py_tp_dealloc, (destructor)PyThreadHandleObject_dealloc}, + {Py_tp_repr, (reprfunc)PyThreadHandleObject_repr}, {Py_tp_getset, ThreadHandle_getsetlist}, + {Py_tp_traverse, PyThreadHandleObject_traverse}, {Py_tp_methods, ThreadHandle_methods}, + {Py_tp_new, PyThreadHandleObject_tp_new}, {0, 0} }; static PyType_Spec ThreadHandle_Type_spec = { "_thread._ThreadHandle", - sizeof(ThreadHandleObject), + sizeof(PyThreadHandleObject), 0, - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC, ThreadHandle_Type_slots, }; @@ -324,8 +819,8 @@ lock_PyThread_release_lock(lockobject *self, PyObject *Py_UNUSED(ignored)) return NULL; } - PyThread_release_lock(self->lock_lock); self->locked = 0; + PyThread_release_lock(self->lock_lock); Py_RETURN_NONE; } @@ -1196,13 +1691,9 @@ _localdummy_destroyed(PyObject *localweakref, PyObject *dummyweakref) /* If the thread-local object is still alive and not being cleared, remove the corresponding local dict */ if (self->dummies != NULL) { - PyObject *ldict; - ldict = PyDict_GetItemWithError(self->dummies, dummyweakref); - if (ldict != NULL) { - PyDict_DelItem(self->dummies, dummyweakref); - } - if (PyErr_Occurred()) + if (PyDict_Pop(self->dummies, dummyweakref, NULL) < 0) { PyErr_WriteUnraisable((PyObject*)self); + } } Py_DECREF(self); Py_RETURN_NONE; @@ -1210,82 +1701,6 @@ _localdummy_destroyed(PyObject *localweakref, PyObject *dummyweakref) /* Module functions */ -struct bootstate { - PyThreadState *tstate; - PyObject *func; - PyObject *args; - PyObject *kwargs; -}; - - -static void -thread_bootstate_free(struct bootstate *boot, int decref) -{ - if (decref) { - Py_DECREF(boot->func); - Py_DECREF(boot->args); - Py_XDECREF(boot->kwargs); - } - PyMem_RawFree(boot); -} - - -static void -thread_run(void *boot_raw) -{ - struct bootstate *boot = (struct bootstate *) boot_raw; - PyThreadState *tstate = boot->tstate; - - // gh-108987: If _thread.start_new_thread() is called before or while - // Python is being finalized, thread_run() can called *after*. - // _PyRuntimeState_SetFinalizing() is called. At this point, all Python - // threads must exit, except of the thread calling Py_Finalize() whch holds - // the GIL and must not exit. - // - // At this stage, tstate can be a dangling pointer (point to freed memory), - // it's ok to call _PyThreadState_MustExit() with a dangling pointer. - if (_PyThreadState_MustExit(tstate)) { - // Don't call PyThreadState_Clear() nor _PyThreadState_DeleteCurrent(). - // These functions are called on tstate indirectly by Py_Finalize() - // which calls _PyInterpreterState_Clear(). - // - // Py_DECREF() cannot be called because the GIL is not held: leak - // references on purpose. Python is being finalized anyway. - thread_bootstate_free(boot, 0); - goto exit; - } - - _PyThreadState_Bind(tstate); - PyEval_AcquireThread(tstate); - _Py_atomic_add_ssize(&tstate->interp->threads.count, 1); - - PyObject *res = PyObject_Call(boot->func, boot->args, boot->kwargs); - if (res == NULL) { - if (PyErr_ExceptionMatches(PyExc_SystemExit)) - /* SystemExit is ignored silently */ - PyErr_Clear(); - else { - PyErr_FormatUnraisable( - "Exception ignored in thread started by %R", boot->func); - } - } - else { - Py_DECREF(res); - } - - thread_bootstate_free(boot, 1); - - _Py_atomic_add_ssize(&tstate->interp->threads.count, -1); - PyThreadState_Clear(tstate); - _PyThreadState_DeleteCurrent(tstate); - -exit: - // bpo-44434: Don't call explicitly PyThread_exit_thread(). On Linux with - // the glibc, pthread_exit() can abort the whole process if dlopen() fails - // to open the libgcc_s.so library (ex: EMFILE error). - return; -} - static PyObject * thread_daemon_threads_allowed(PyObject *module, PyObject *Py_UNUSED(ignored)) { @@ -1305,10 +1720,8 @@ Return True if daemon threads are allowed in the current interpreter,\n\ and False otherwise.\n"); static int -do_start_new_thread(thread_module_state* state, - PyObject *func, PyObject* args, PyObject* kwargs, - int joinable, - PyThread_ident_t* ident, PyThread_handle_t* handle) +do_start_new_thread(thread_module_state *state, PyObject *func, PyObject *args, + PyObject *kwargs, ThreadHandle *handle, int daemon) { PyInterpreterState *interp = _PyInterpreterState_GET(); if (!_PyInterpreterState_HasFeature(interp, Py_RTFLAGS_THREADS)) { @@ -1322,40 +1735,20 @@ do_start_new_thread(thread_module_state* state, return -1; } - // gh-109795: Use PyMem_RawMalloc() instead of PyMem_Malloc(), - // because it should be possible to call thread_bootstate_free() - // without holding the GIL. - struct bootstate *boot = PyMem_RawMalloc(sizeof(struct bootstate)); - if (boot == NULL) { - PyErr_NoMemory(); - return -1; + if (!daemon) { + // Add the handle before starting the thread to avoid adding a handle + // to a thread that has already finished (i.e. if the thread finishes + // before the call to `ThreadHandle_start()` below returns). + add_to_shutdown_handles(state, handle); } - boot->tstate = _PyThreadState_New(interp, _PyThreadState_WHENCE_THREADING); - if (boot->tstate == NULL) { - PyMem_RawFree(boot); - if (!PyErr_Occurred()) { - PyErr_NoMemory(); + + if (ThreadHandle_start(handle, func, args, kwargs) < 0) { + if (!daemon) { + remove_from_shutdown_handles(handle); } return -1; } - boot->func = Py_NewRef(func); - boot->args = Py_NewRef(args); - boot->kwargs = Py_XNewRef(kwargs); - int err; - if (joinable) { - err = PyThread_start_joinable_thread(thread_run, (void*) boot, ident, handle); - } else { - *handle = 0; - *ident = PyThread_start_new_thread(thread_run, (void*) boot); - err = (*ident == PYTHREAD_INVALID_THREAD_ID); - } - if (err) { - PyErr_SetString(ThreadError, "can't start new thread"); - PyThreadState_Clear(boot->tstate); - thread_bootstate_free(boot, 1); - return -1; - } return 0; } @@ -1389,12 +1782,19 @@ thread_PyThread_start_new_thread(PyObject *module, PyObject *fargs) return NULL; } - PyThread_ident_t ident = 0; - PyThread_handle_t handle; - if (do_start_new_thread(state, func, args, kwargs, /*joinable=*/ 0, - &ident, &handle)) { + ThreadHandle *handle = ThreadHandle_new(); + if (handle == NULL) { return NULL; } + + int st = + do_start_new_thread(state, func, args, kwargs, handle, /*daemon=*/1); + if (st < 0) { + ThreadHandle_decref(handle); + return NULL; + } + PyThread_ident_t ident = ThreadHandle_ident(handle); + ThreadHandle_decref(handle); return PyLong_FromUnsignedLongLong(ident); } @@ -1412,9 +1812,19 @@ unhandled exception; a stack trace will be printed unless the exception\n\ is SystemExit.\n"); static PyObject * -thread_PyThread_start_joinable_thread(PyObject *module, PyObject *func) +thread_PyThread_start_joinable_thread(PyObject *module, PyObject *fargs, + PyObject *fkwargs) { + static char *keywords[] = {"function", "handle", "daemon", NULL}; + PyObject *func = NULL; + int daemon = 1; thread_module_state *state = get_thread_state(module); + PyObject *hobj = NULL; + if (!PyArg_ParseTupleAndKeywords(fargs, fkwargs, + "O|Op:start_joinable_thread", keywords, + &func, &hobj, &daemon)) { + return NULL; + } if (!PyCallable_Check(func)) { PyErr_SetString(PyExc_TypeError, @@ -1422,32 +1832,45 @@ thread_PyThread_start_joinable_thread(PyObject *module, PyObject *func) return NULL; } - if (PySys_Audit("_thread.start_joinable_thread", "O", func) < 0) { + if (hobj == NULL) { + hobj = Py_None; + } + else if (hobj != Py_None && !Py_IS_TYPE(hobj, state->thread_handle_type)) { + PyErr_SetString(PyExc_TypeError, "'handle' must be a _ThreadHandle"); return NULL; } - PyObject* args = PyTuple_New(0); - if (args == NULL) { + if (PySys_Audit("_thread.start_joinable_thread", "OiO", func, daemon, + hobj) < 0) { return NULL; } - ThreadHandleObject* hobj = new_thread_handle(state); - if (hobj == NULL) { - Py_DECREF(args); + + if (hobj == Py_None) { + hobj = (PyObject *)PyThreadHandleObject_new(state->thread_handle_type); + if (hobj == NULL) { + return NULL; + } + } + else { + Py_INCREF(hobj); + } + + PyObject* args = PyTuple_New(0); + if (args == NULL) { return NULL; } - if (do_start_new_thread(state, func, args, /*kwargs=*/ NULL, /*joinable=*/ 1, - &hobj->ident, &hobj->handle)) { - Py_DECREF(args); + int st = do_start_new_thread(state, func, args, + /*kwargs=*/ NULL, ((PyThreadHandleObject*)hobj)->handle, daemon); + Py_DECREF(args); + if (st < 0) { Py_DECREF(hobj); return NULL; } - Py_DECREF(args); - hobj->joinable = 1; - return (PyObject*) hobj; + return (PyObject *) hobj; } PyDoc_STRVAR(start_joinable_doc, -"start_joinable_thread(function)\n\ +"start_joinable_thread(function[, daemon=True[, handle=None]])\n\ \n\ *For internal use only*: start a new thread.\n\ \n\ @@ -1455,7 +1878,9 @@ Like start_new_thread(), this starts a new thread calling the given function.\n\ Unlike start_new_thread(), this returns a handle object with methods to join\n\ or detach the given thread.\n\ This function is not for third-party code, please use the\n\ -`threading` module instead.\n"); +`threading` module instead. During finalization the runtime will not wait for\n\ +the thread to exit if daemon is True. If handle is provided it must be a\n\ +newly created thread._ThreadHandle instance."); static PyObject * thread_PyThread_exit_thread(PyObject *self, PyObject *Py_UNUSED(ignored)) @@ -1567,67 +1992,6 @@ yet finished.\n\ This function is meant for internal and specialized purposes only.\n\ In most applications `threading.enumerate()` should be used instead."); -static void -release_sentinel(void *weakref_raw) -{ - PyObject *weakref = _PyObject_CAST(weakref_raw); - - /* Tricky: this function is called when the current thread state - is being deleted. Therefore, only simple C code can safely - execute here. */ - lockobject *lock = (lockobject *)_PyWeakref_GET_REF(weakref); - if (lock != NULL) { - if (lock->locked) { - PyThread_release_lock(lock->lock_lock); - lock->locked = 0; - } - Py_DECREF(lock); - } - - /* Deallocating a weakref with a NULL callback only calls - PyObject_GC_Del(), which can't call any Python code. */ - Py_DECREF(weakref); -} - -static PyObject * -thread__set_sentinel(PyObject *module, PyObject *Py_UNUSED(ignored)) -{ - PyObject *wr; - PyThreadState *tstate = _PyThreadState_GET(); - lockobject *lock; - - if (tstate->on_delete_data != NULL) { - /* We must support the re-creation of the lock from a - fork()ed child. */ - assert(tstate->on_delete == &release_sentinel); - wr = (PyObject *) tstate->on_delete_data; - tstate->on_delete = NULL; - tstate->on_delete_data = NULL; - Py_DECREF(wr); - } - lock = newlockobject(module); - if (lock == NULL) - return NULL; - /* The lock is owned by whoever called _set_sentinel(), but the weakref - hangs to the thread state. */ - wr = PyWeakref_NewRef((PyObject *) lock, NULL); - if (wr == NULL) { - Py_DECREF(lock); - return NULL; - } - tstate->on_delete_data = (void *) wr; - tstate->on_delete = &release_sentinel; - return (PyObject *) lock; -} - -PyDoc_STRVAR(_set_sentinel_doc, -"_set_sentinel() -> lock\n\ -\n\ -Set a sentinel lock that will be released when the current thread\n\ -state is finalized (after it is untied from the interpreter).\n\ -\n\ -This is a private API for the threading module."); - static PyObject * thread_stack_size(PyObject *self, PyObject *args) { @@ -1834,13 +2198,101 @@ PyDoc_STRVAR(thread__is_main_interpreter_doc, \n\ Return True if the current interpreter is the main Python interpreter."); +static PyObject * +thread_shutdown(PyObject *self, PyObject *args) +{ + PyThread_ident_t ident = PyThread_get_thread_ident_ex(); + thread_module_state *state = get_thread_state(self); + + for (;;) { + ThreadHandle *handle = NULL; + + // Find a thread that's not yet finished. + HEAD_LOCK(&_PyRuntime); + struct llist_node *node; + llist_for_each_safe(node, &state->shutdown_handles) { + ThreadHandle *cur = llist_data(node, ThreadHandle, shutdown_node); + if (cur->ident != ident) { + ThreadHandle_incref(cur); + handle = cur; + break; + } + } + HEAD_UNLOCK(&_PyRuntime); + + if (!handle) { + // No more threads to wait on! + break; + } + + // Wait for the thread to finish. If we're interrupted, such + // as by a ctrl-c we print the error and exit early. + if (ThreadHandle_join(handle, -1) < 0) { + PyErr_WriteUnraisable(NULL); + ThreadHandle_decref(handle); + Py_RETURN_NONE; + } + + ThreadHandle_decref(handle); + } + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(shutdown_doc, +"_shutdown()\n\ +\n\ +Wait for all non-daemon threads (other than the calling thread) to stop."); + +static PyObject * +thread__make_thread_handle(PyObject *module, PyObject *identobj) +{ + thread_module_state *state = get_thread_state(module); + if (!PyLong_Check(identobj)) { + PyErr_SetString(PyExc_TypeError, "ident must be an integer"); + return NULL; + } + PyThread_ident_t ident = PyLong_AsUnsignedLongLong(identobj); + if (PyErr_Occurred()) { + return NULL; + } + PyThreadHandleObject *hobj = + PyThreadHandleObject_new(state->thread_handle_type); + if (hobj == NULL) { + return NULL; + } + PyMutex_Lock(&hobj->handle->mutex); + hobj->handle->ident = ident; + hobj->handle->state = THREAD_HANDLE_RUNNING; + PyMutex_Unlock(&hobj->handle->mutex); + return (PyObject*) hobj; +} + +PyDoc_STRVAR(thread__make_thread_handle_doc, +"_make_thread_handle(ident)\n\ +\n\ +Internal only. Make a thread handle for threads not spawned\n\ +by the _thread or threading module."); + +static PyObject * +thread__get_main_thread_ident(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return PyLong_FromUnsignedLongLong(_PyRuntime.main_thread); +} + +PyDoc_STRVAR(thread__get_main_thread_ident_doc, +"_get_main_thread_ident()\n\ +\n\ +Internal only. Return a non-zero integer that uniquely identifies the main thread\n\ +of the main interpreter."); + static PyMethodDef thread_methods[] = { {"start_new_thread", (PyCFunction)thread_PyThread_start_new_thread, METH_VARARGS, start_new_doc}, {"start_new", (PyCFunction)thread_PyThread_start_new_thread, METH_VARARGS, start_new_doc}, - {"start_joinable_thread", (PyCFunction)thread_PyThread_start_joinable_thread, - METH_O, start_joinable_doc}, + {"start_joinable_thread", _PyCFunction_CAST(thread_PyThread_start_joinable_thread), + METH_VARARGS | METH_KEYWORDS, start_joinable_doc}, {"daemon_threads_allowed", (PyCFunction)thread_daemon_threads_allowed, METH_NOARGS, daemon_threads_allowed_doc}, {"allocate_lock", thread_PyThread_allocate_lock, @@ -1863,12 +2315,16 @@ static PyMethodDef thread_methods[] = { METH_NOARGS, _count_doc}, {"stack_size", (PyCFunction)thread_stack_size, METH_VARARGS, stack_size_doc}, - {"_set_sentinel", thread__set_sentinel, - METH_NOARGS, _set_sentinel_doc}, {"_excepthook", thread_excepthook, METH_O, excepthook_doc}, {"_is_main_interpreter", thread__is_main_interpreter, METH_NOARGS, thread__is_main_interpreter_doc}, + {"_shutdown", thread_shutdown, + METH_NOARGS, shutdown_doc}, + {"_make_thread_handle", thread__make_thread_handle, + METH_O, thread__make_thread_handle_doc}, + {"_get_main_thread_ident", thread__get_main_thread_ident, + METH_NOARGS, thread__get_main_thread_ident_doc}, {NULL, NULL} /* sentinel */ }; @@ -1958,6 +2414,8 @@ thread_module_exec(PyObject *module) return -1; } + llist_init(&state->shutdown_handles); + return 0; } @@ -1983,6 +2441,10 @@ thread_module_clear(PyObject *module) Py_CLEAR(state->local_type); Py_CLEAR(state->local_dummy_type); Py_CLEAR(state->thread_handle_type); + // Remove any remaining handles (e.g. if shutdown exited early due to + // interrupt) so that attempts to unlink the handle after our module state + // is destroyed do not crash. + clear_shutdown_handles(state); return 0; } diff --git a/Modules/_uuidmodule.c b/Modules/_uuidmodule.c index 4b6852c0d0ec73d..052cb9fef3b21c0 100644 --- a/Modules/_uuidmodule.c +++ b/Modules/_uuidmodule.c @@ -3,11 +3,10 @@ * DCE compatible Universally Unique Identifier library. */ +// Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED #include "pyconfig.h" // Py_GIL_DISABLED - #ifndef Py_GIL_DISABLED -// Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED -#define Py_LIMITED_API 0x030c0000 +# define Py_LIMITED_API 0x030c0000 #endif #include "Python.h" diff --git a/Modules/_xxinterpchannelsmodule.c b/Modules/_xxinterpchannelsmodule.c index 82d2ae7fc4c9634..28ec00a159d6cd2 100644 --- a/Modules/_xxinterpchannelsmodule.c +++ b/Modules/_xxinterpchannelsmodule.c @@ -17,7 +17,9 @@ #include // sched_yield() #endif +#define REGISTERS_HEAP_TYPES #include "_interpreters_common.h" +#undef REGISTERS_HEAP_TYPES /* @@ -93,38 +95,6 @@ API.. The module does not create any objects that are shared globally. PyMem_RawFree(VAR) -struct xid_class_registry { - size_t count; -#define MAX_XID_CLASSES 5 - struct { - PyTypeObject *cls; - } added[MAX_XID_CLASSES]; -}; - -static int -register_xid_class(PyTypeObject *cls, crossinterpdatafunc shared, - struct xid_class_registry *classes) -{ - int res = ensure_xid_class(cls, shared); - if (res == 0) { - assert(classes->count < MAX_XID_CLASSES); - // The class has refs elsewhere, so we need to incref here. - classes->added[classes->count].cls = cls; - classes->count += 1; - } - return res; -} - -static void -clear_xid_class_registry(struct xid_class_registry *classes) -{ - while (classes->count > 0) { - classes->count -= 1; - PyTypeObject *cls = classes->added[classes->count].cls; - _PyCrossInterpreterData_UnregisterClass(cls); - } -} - #define XID_IGNORE_EXC 1 #define XID_FREE 2 @@ -223,28 +193,6 @@ add_new_exception(PyObject *mod, const char *name, PyObject *base) #define ADD_NEW_EXCEPTION(MOD, NAME, BASE) \ add_new_exception(MOD, MODULE_NAME_STR "." Py_STRINGIFY(NAME), BASE) -static PyTypeObject * -add_new_type(PyObject *mod, PyType_Spec *spec, crossinterpdatafunc shared, - struct xid_class_registry *classes) -{ - PyTypeObject *cls = (PyTypeObject *)PyType_FromModuleAndSpec( - mod, spec, NULL); - if (cls == NULL) { - return NULL; - } - if (PyModule_AddType(mod, cls) < 0) { - Py_DECREF(cls); - return NULL; - } - if (shared != NULL) { - if (register_xid_class(cls, shared, classes)) { - Py_DECREF(cls); - return NULL; - } - } - return cls; -} - static int wait_for_lock(PyThread_type_lock mutex, PY_TIMEOUT_T timeout) { @@ -269,8 +217,6 @@ wait_for_lock(PyThread_type_lock mutex, PY_TIMEOUT_T timeout) /* module state *************************************************************/ typedef struct { - struct xid_class_registry xid_classes; - /* Added at runtime by interpreters module. */ PyTypeObject *send_channel_type; PyTypeObject *recv_channel_type; @@ -332,19 +278,33 @@ traverse_module_state(module_state *state, visitproc visit, void *arg) return 0; } -static int -clear_module_state(module_state *state) +static void +clear_xid_types(module_state *state) { /* external types */ - Py_CLEAR(state->send_channel_type); - Py_CLEAR(state->recv_channel_type); + if (state->send_channel_type != NULL) { + (void)clear_xid_class(state->send_channel_type); + Py_CLEAR(state->send_channel_type); + } + if (state->recv_channel_type != NULL) { + (void)clear_xid_class(state->recv_channel_type); + Py_CLEAR(state->recv_channel_type); + } /* heap types */ - Py_CLEAR(state->ChannelInfoType); if (state->ChannelIDType != NULL) { - (void)_PyCrossInterpreterData_UnregisterClass(state->ChannelIDType); + (void)clear_xid_class(state->ChannelIDType); + Py_CLEAR(state->ChannelIDType); } - Py_CLEAR(state->ChannelIDType); +} + +static int +clear_module_state(module_state *state) +{ + clear_xid_types(state); + + /* heap types */ + Py_CLEAR(state->ChannelInfoType); /* exceptions */ Py_CLEAR(state->ChannelError); @@ -2614,6 +2574,25 @@ static PyType_Spec channelid_typespec = { .slots = channelid_typeslots, }; +static PyTypeObject * +add_channelid_type(PyObject *mod) +{ + PyTypeObject *cls = (PyTypeObject *)PyType_FromModuleAndSpec( + mod, &channelid_typespec, NULL); + if (cls == NULL) { + return NULL; + } + if (PyModule_AddType(mod, cls) < 0) { + Py_DECREF(cls); + return NULL; + } + if (ensure_xid_class(cls, _channelid_shared) < 0) { + Py_DECREF(cls); + return NULL; + } + return cls; +} + /* SendChannel and RecvChannel classes */ @@ -2697,21 +2676,29 @@ set_channelend_types(PyObject *mod, PyTypeObject *send, PyTypeObject *recv) if (state == NULL) { return -1; } - struct xid_class_registry *xid_classes = &state->xid_classes; - if (state->send_channel_type != NULL - || state->recv_channel_type != NULL) - { - PyErr_SetString(PyExc_TypeError, "already registered"); - return -1; + // Clear the old values if the .py module was reloaded. + if (state->send_channel_type != NULL) { + (void)clear_xid_class(state->send_channel_type); + Py_CLEAR(state->send_channel_type); + } + if (state->recv_channel_type != NULL) { + (void)clear_xid_class(state->recv_channel_type); + Py_CLEAR(state->recv_channel_type); } + + // Add and register the types. state->send_channel_type = (PyTypeObject *)Py_NewRef(send); state->recv_channel_type = (PyTypeObject *)Py_NewRef(recv); - - if (register_xid_class(send, _channelend_shared, xid_classes)) { + if (ensure_xid_class(send, _channelend_shared) < 0) { + Py_CLEAR(state->send_channel_type); + Py_CLEAR(state->recv_channel_type); return -1; } - if (register_xid_class(recv, _channelend_shared, xid_classes)) { + if (ensure_xid_class(recv, _channelend_shared) < 0) { + (void)clear_xid_class(state->send_channel_type); + Py_CLEAR(state->send_channel_type); + Py_CLEAR(state->recv_channel_type); return -1; } @@ -3294,13 +3281,11 @@ module_exec(PyObject *mod) if (_globals_init() != 0) { return -1; } - struct xid_class_registry *xid_classes = NULL; module_state *state = get_module_state(mod); if (state == NULL) { goto error; } - xid_classes = &state->xid_classes; /* Add exception types */ if (exceptions_init(mod) != 0) { @@ -3319,8 +3304,7 @@ module_exec(PyObject *mod) } // ChannelID - state->ChannelIDType = add_new_type( - mod, &channelid_typespec, _channelid_shared, xid_classes); + state->ChannelIDType = add_channelid_type(mod); if (state->ChannelIDType == NULL) { goto error; } @@ -3332,8 +3316,8 @@ module_exec(PyObject *mod) return 0; error: - if (xid_classes != NULL) { - clear_xid_class_registry(xid_classes); + if (state != NULL) { + clear_xid_types(state); } _globals_fini(); return -1; @@ -3360,9 +3344,6 @@ module_clear(PyObject *mod) module_state *state = get_module_state(mod); assert(state != NULL); - // Before clearing anything, we unregister the various XID types. */ - clear_xid_class_registry(&state->xid_classes); - // Now we clear the module state. clear_module_state(state); return 0; @@ -3374,9 +3355,6 @@ module_free(void *mod) module_state *state = get_module_state(mod); assert(state != NULL); - // Before clearing anything, we unregister the various XID types. */ - clear_xid_class_registry(&state->xid_classes); - // Now we clear the module state. clear_module_state(state); diff --git a/Modules/_xxinterpqueuesmodule.c b/Modules/_xxinterpqueuesmodule.c index 7d8c67f49fefb85..96f6eeeea94b5e4 100644 --- a/Modules/_xxinterpqueuesmodule.c +++ b/Modules/_xxinterpqueuesmodule.c @@ -8,7 +8,9 @@ #include "Python.h" #include "pycore_crossinterp.h" // struct _xid +#define REGISTERS_HEAP_TYPES #include "_interpreters_common.h" +#undef REGISTERS_HEAP_TYPES #define MODULE_NAME _xxinterpqueues @@ -128,6 +130,22 @@ idarg_int64_converter(PyObject *arg, void *ptr) } +static int +ensure_highlevel_module_loaded(void) +{ + PyObject *highlevel = PyImport_ImportModule("interpreters.queues"); + if (highlevel == NULL) { + PyErr_Clear(); + highlevel = PyImport_ImportModule("test.support.interpreters.queues"); + if (highlevel == NULL) { + return -1; + } + } + Py_DECREF(highlevel); + return 0; +} + + /* module state *************************************************************/ typedef struct { @@ -169,6 +187,9 @@ static int clear_module_state(module_state *state) { /* external types */ + if (state->queue_type != NULL) { + (void)clear_xid_class(state->queue_type); + } Py_CLEAR(state->queue_type); /* QueueError */ @@ -192,6 +213,9 @@ clear_module_state(module_state *state) // single-queue errors #define ERR_QUEUE_EMPTY (-21) #define ERR_QUEUE_FULL (-22) +#define ERR_QUEUE_NEVER_BOUND (-23) + +static int ensure_external_exc_types(module_state *); static int resolve_module_errcode(module_state *state, int errcode, int64_t qid, @@ -209,13 +233,23 @@ resolve_module_errcode(module_state *state, int errcode, int64_t qid, msg = PyUnicode_FromFormat("queue %" PRId64 " not found", qid); break; case ERR_QUEUE_EMPTY: + if (ensure_external_exc_types(state) < 0) { + return -1; + } exctype = state->QueueEmpty; msg = PyUnicode_FromFormat("queue %" PRId64 " is empty", qid); break; case ERR_QUEUE_FULL: + if (ensure_external_exc_types(state) < 0) { + return -1; + } exctype = state->QueueFull; msg = PyUnicode_FromFormat("queue %" PRId64 " is full", qid); break; + case ERR_QUEUE_NEVER_BOUND: + exctype = state->QueueError; + msg = PyUnicode_FromFormat("queue %" PRId64 " never bound", qid); + break; default: PyErr_Format(PyExc_ValueError, "unsupported error code %d", errcode); @@ -264,20 +298,59 @@ add_QueueError(PyObject *mod) #define PREFIX "test.support.interpreters." #define ADD_EXCTYPE(NAME, BASE, DOC) \ + assert(state->NAME == NULL); \ if (add_exctype(mod, &state->NAME, PREFIX #NAME, DOC, BASE) < 0) { \ return -1; \ } ADD_EXCTYPE(QueueError, PyExc_RuntimeError, "Indicates that a queue-related error happened.") ADD_EXCTYPE(QueueNotFoundError, state->QueueError, NULL) - ADD_EXCTYPE(QueueEmpty, state->QueueError, NULL) - ADD_EXCTYPE(QueueFull, state->QueueError, NULL) + // QueueEmpty and QueueFull are set by set_external_exc_types(). + state->QueueEmpty = NULL; + state->QueueFull = NULL; #undef ADD_EXCTYPE #undef PREFIX return 0; } +static int +set_external_exc_types(module_state *state, + PyObject *emptyerror, PyObject *fullerror) +{ + if (state->QueueEmpty != NULL) { + assert(state->QueueFull != NULL); + Py_CLEAR(state->QueueEmpty); + Py_CLEAR(state->QueueFull); + } + else { + assert(state->QueueFull == NULL); + } + assert(PyObject_IsSubclass(emptyerror, state->QueueError)); + assert(PyObject_IsSubclass(fullerror, state->QueueError)); + state->QueueEmpty = Py_NewRef(emptyerror); + state->QueueFull = Py_NewRef(fullerror); + return 0; +} + +static int +ensure_external_exc_types(module_state *state) +{ + if (state->QueueEmpty != NULL) { + assert(state->QueueFull != NULL); + return 0; + } + assert(state->QueueFull == NULL); + + // Force the module to be loaded, to register the type. + if (ensure_highlevel_module_loaded() < 0) { + return -1; + } + assert(state->QueueEmpty != NULL); + assert(state->QueueFull != NULL); + return 0; +} + static int handle_queue_error(int err, PyObject *mod, int64_t qid) { @@ -294,6 +367,8 @@ handle_queue_error(int err, PyObject *mod, int64_t qid) case ERR_QUEUES_ALLOC: PyErr_NoMemory(); break; + case -1: + return -1; default: state = get_module_state(mod); assert(state->QueueError != NULL); @@ -320,14 +395,17 @@ struct _queueitem; typedef struct _queueitem { _PyCrossInterpreterData *data; + int fmt; struct _queueitem *next; } _queueitem; static void -_queueitem_init(_queueitem *item, _PyCrossInterpreterData *data) +_queueitem_init(_queueitem *item, + _PyCrossInterpreterData *data, int fmt) { *item = (_queueitem){ .data = data, + .fmt = fmt, }; } @@ -344,14 +422,14 @@ _queueitem_clear(_queueitem *item) } static _queueitem * -_queueitem_new(_PyCrossInterpreterData *data) +_queueitem_new(_PyCrossInterpreterData *data, int fmt) { _queueitem *item = GLOBAL_MALLOC(_queueitem); if (item == NULL) { PyErr_NoMemory(); return NULL; } - _queueitem_init(item, data); + _queueitem_init(item, data, fmt); return item; } @@ -373,9 +451,11 @@ _queueitem_free_all(_queueitem *item) } static void -_queueitem_popped(_queueitem *item, _PyCrossInterpreterData **p_data) +_queueitem_popped(_queueitem *item, + _PyCrossInterpreterData **p_data, int *p_fmt) { *p_data = item->data; + *p_fmt = item->fmt; // We clear them here, so they won't be released in _queueitem_clear(). item->data = NULL; _queueitem_free(item); @@ -383,6 +463,7 @@ _queueitem_popped(_queueitem *item, _PyCrossInterpreterData **p_data) /* the queue */ + typedef struct _queue { Py_ssize_t num_waiters; // protected by global lock PyThread_type_lock mutex; @@ -393,10 +474,11 @@ typedef struct _queue { _queueitem *first; _queueitem *last; } items; + int fmt; } _queue; static int -_queue_init(_queue *queue, Py_ssize_t maxsize) +_queue_init(_queue *queue, Py_ssize_t maxsize, int fmt) { PyThread_type_lock mutex = PyThread_allocate_lock(); if (mutex == NULL) { @@ -408,6 +490,7 @@ _queue_init(_queue *queue, Py_ssize_t maxsize) .items = { .maxsize = maxsize, }, + .fmt = fmt, }; return 0; } @@ -423,6 +506,8 @@ _queue_clear(_queue *queue) *queue = (_queue){0}; } +static void _queue_free(_queue *); + static void _queue_kill_and_wait(_queue *queue) { @@ -486,7 +571,7 @@ _queue_unlock(_queue *queue) } static int -_queue_add(_queue *queue, _PyCrossInterpreterData *data) +_queue_add(_queue *queue, _PyCrossInterpreterData *data, int fmt) { int err = _queue_lock(queue); if (err < 0) { @@ -502,7 +587,7 @@ _queue_add(_queue *queue, _PyCrossInterpreterData *data) return ERR_QUEUE_FULL; } - _queueitem *item = _queueitem_new(data); + _queueitem *item = _queueitem_new(data, fmt); if (item == NULL) { _queue_unlock(queue); return -1; @@ -522,7 +607,8 @@ _queue_add(_queue *queue, _PyCrossInterpreterData *data) } static int -_queue_next(_queue *queue, _PyCrossInterpreterData **p_data) +_queue_next(_queue *queue, + _PyCrossInterpreterData **p_data, int *p_fmt) { int err = _queue_lock(queue); if (err < 0) { @@ -541,7 +627,7 @@ _queue_next(_queue *queue, _PyCrossInterpreterData **p_data) } queue->items.count -= 1; - _queueitem_popped(item, p_data); + _queueitem_popped(item, p_data, p_fmt); _queue_unlock(queue); return 0; @@ -654,6 +740,32 @@ _queuerefs_find(_queueref *first, int64_t qid, _queueref **pprev) return ref; } +static void +_queuerefs_clear(_queueref *head) +{ + _queueref *next = head; + while (next != NULL) { + _queueref *ref = next; + next = ref->next; + +#ifdef Py_DEBUG + int64_t qid = ref->qid; + fprintf(stderr, "queue %" PRId64 " still exists\n", qid); +#endif + _queue *queue = ref->queue; + GLOBAL_FREE(ref); + + _queue_kill_and_wait(queue); +#ifdef Py_DEBUG + if (queue->items.count > 0) { + fprintf(stderr, "queue %" PRId64 " still holds %zd items\n", + qid, queue->items.count); + } +#endif + _queue_free(queue); + } +} + /* a collection of queues ***************************************************/ @@ -676,8 +788,15 @@ _queues_init(_queues *queues, PyThread_type_lock mutex) static void _queues_fini(_queues *queues) { - assert(queues->count == 0); - assert(queues->head == NULL); + if (queues->count > 0) { + PyThread_acquire_lock(queues->mutex, WAIT_LOCK); + assert((queues->count == 0) != (queues->head != NULL)); + _queueref *head = queues->head; + queues->head = NULL; + queues->count = 0; + PyThread_release_lock(queues->mutex); + _queuerefs_clear(head); + } if (queues->mutex != NULL) { PyThread_free_lock(queues->mutex); queues->mutex = NULL; @@ -809,19 +928,21 @@ _queues_incref(_queues *queues, int64_t qid) return res; } -static void _queue_free(_queue *); - -static void +static int _queues_decref(_queues *queues, int64_t qid) { + int res = -1; PyThread_acquire_lock(queues->mutex, WAIT_LOCK); _queueref *prev = NULL; _queueref *ref = _queuerefs_find(queues->head, qid, &prev); if (ref == NULL) { assert(!PyErr_Occurred()); - // Already destroyed. - // XXX Warn? + res = ERR_QUEUE_NOT_FOUND; + goto finally; + } + if (ref->refcount == 0) { + res = ERR_QUEUE_NEVER_BOUND; goto finally; } assert(ref->refcount > 0); @@ -836,25 +957,35 @@ _queues_decref(_queues *queues, int64_t qid) _queue_kill_and_wait(queue); _queue_free(queue); - return; + return 0; } + res = 0; finally: PyThread_release_lock(queues->mutex); + return res; } -static int64_t * +struct queue_id_and_fmt { + int64_t id; + int fmt; +}; + +static struct queue_id_and_fmt * _queues_list_all(_queues *queues, int64_t *count) { - int64_t *qids = NULL; + struct queue_id_and_fmt *qids = NULL; PyThread_acquire_lock(queues->mutex, WAIT_LOCK); - int64_t *ids = PyMem_NEW(int64_t, (Py_ssize_t)(queues->count)); + struct queue_id_and_fmt *ids = PyMem_NEW(struct queue_id_and_fmt, + (Py_ssize_t)(queues->count)); if (ids == NULL) { goto done; } _queueref *ref = queues->head; for (int64_t i=0; ref != NULL; ref = ref->next, i++) { - ids[i] = ref->qid; + ids[i].id = ref->qid; + assert(ref->queue != NULL); + ids[i].fmt = ref->queue->fmt; } *count = queues->count; @@ -890,13 +1021,13 @@ _queue_free(_queue *queue) // Create a new queue. static int64_t -queue_create(_queues *queues, Py_ssize_t maxsize) +queue_create(_queues *queues, Py_ssize_t maxsize, int fmt) { _queue *queue = GLOBAL_MALLOC(_queue); if (queue == NULL) { return ERR_QUEUE_ALLOC; } - int err = _queue_init(queue, maxsize); + int err = _queue_init(queue, maxsize, fmt); if (err < 0) { GLOBAL_FREE(queue); return (int64_t)err; @@ -925,7 +1056,7 @@ queue_destroy(_queues *queues, int64_t qid) // Push an object onto the queue. static int -queue_put(_queues *queues, int64_t qid, PyObject *obj) +queue_put(_queues *queues, int64_t qid, PyObject *obj, int fmt) { // Look up the queue. _queue *queue = NULL; @@ -948,7 +1079,7 @@ queue_put(_queues *queues, int64_t qid, PyObject *obj) } // Add the data to the queue. - int res = _queue_add(queue, data); + int res = _queue_add(queue, data, fmt); _queue_unmark_waiter(queue, queues->mutex); if (res != 0) { // We may chain an exception here: @@ -963,7 +1094,7 @@ queue_put(_queues *queues, int64_t qid, PyObject *obj) // Pop the next object off the queue. Fail if empty. // XXX Support a "wait" mutex? static int -queue_get(_queues *queues, int64_t qid, PyObject **res) +queue_get(_queues *queues, int64_t qid, PyObject **res, int *p_fmt) { int err; *res = NULL; @@ -979,7 +1110,7 @@ queue_get(_queues *queues, int64_t qid, PyObject **res) // Pop off the next item from the queue. _PyCrossInterpreterData *data = NULL; - err = _queue_next(queue, &data); + err = _queue_next(queue, &data, p_fmt); _queue_unmark_waiter(queue, queues->mutex); if (err != 0) { return err; @@ -1056,19 +1187,19 @@ static int _queueobj_shared(PyThreadState *, PyObject *, _PyCrossInterpreterData *); static int -set_external_queue_type(PyObject *module, PyTypeObject *queue_type) +set_external_queue_type(module_state *state, PyTypeObject *queue_type) { - module_state *state = get_module_state(module); - + // Clear the old value if the .py module was reloaded. if (state->queue_type != NULL) { - PyErr_SetString(PyExc_TypeError, "already registered"); - return -1; + (void)clear_xid_class(state->queue_type); + Py_CLEAR(state->queue_type); } - state->queue_type = (PyTypeObject *)Py_NewRef(queue_type); + // Add and register the new type. if (ensure_xid_class(queue_type, _queueobj_shared) < 0) { return -1; } + state->queue_type = (PyTypeObject *)Py_NewRef(queue_type); return 0; } @@ -1081,15 +1212,9 @@ get_external_queue_type(PyObject *module) PyTypeObject *cls = state->queue_type; if (cls == NULL) { // Force the module to be loaded, to register the type. - PyObject *highlevel = PyImport_ImportModule("interpreters.queue"); - if (highlevel == NULL) { - PyErr_Clear(); - highlevel = PyImport_ImportModule("test.support.interpreters.queue"); - if (highlevel == NULL) { - return NULL; - } + if (ensure_highlevel_module_loaded() < 0) { + return NULL; } - Py_DECREF(highlevel); cls = state->queue_type; assert(cls != NULL); } @@ -1128,7 +1253,14 @@ _queueid_xid_free(void *data) int64_t qid = ((struct _queueid_xid *)data)->qid; PyMem_RawFree(data); _queues *queues = _get_global_queues(); - _queues_decref(queues, qid); + int res = _queues_decref(queues, qid); + if (res == ERR_QUEUE_NOT_FOUND) { + // Already destroyed. + // XXX Warn? + } + else { + assert(res == 0); + } } static PyObject * @@ -1171,7 +1303,7 @@ _queueobj_shared(PyThreadState *tstate, PyObject *queueobj, .label = "queue ID", }; int res = idarg_int64_converter(qidobj, &converted); - Py_DECREF(qidobj); + Py_CLEAR(qidobj); if (!res) { assert(PyErr_Occurred()); return -1; @@ -1179,12 +1311,10 @@ _queueobj_shared(PyThreadState *tstate, PyObject *queueobj, void *raw = _queueid_xid_new(converted.id); if (raw == NULL) { - Py_DECREF(qidobj); return -1; } _PyCrossInterpreterData_Init(data, tstate->interp, raw, NULL, _queueobj_from_xid); - Py_DECREF(qidobj); _PyCrossInterpreterData_SET_FREE(data, _queueid_xid_free); return 0; } @@ -1267,14 +1397,15 @@ qidarg_converter(PyObject *arg, void *ptr) static PyObject * queuesmod_create(PyObject *self, PyObject *args, PyObject *kwds) { - static char *kwlist[] = {"maxsize", NULL}; - Py_ssize_t maxsize = -1; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|n:create", kwlist, - &maxsize)) { + static char *kwlist[] = {"maxsize", "fmt", NULL}; + Py_ssize_t maxsize; + int fmt; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ni:create", kwlist, + &maxsize, &fmt)) { return NULL; } - int64_t qid = queue_create(&_globals.queues, maxsize); + int64_t qid = queue_create(&_globals.queues, maxsize, fmt); if (qid < 0) { (void)handle_queue_error((int)qid, self, qid); return NULL; @@ -1296,10 +1427,13 @@ queuesmod_create(PyObject *self, PyObject *args, PyObject *kwds) } PyDoc_STRVAR(queuesmod_create_doc, -"create() -> qid\n\ +"create(maxsize, fmt) -> qid\n\ \n\ Create a new cross-interpreter queue and return its unique generated ID.\n\ -It is a new reference as though bind() had been called on the queue."); +It is a new reference as though bind() had been called on the queue.\n\ +\n\ +The caller is responsible for calling destroy() for the new queue\n\ +before the runtime is finalized."); static PyObject * queuesmod_destroy(PyObject *self, PyObject *args, PyObject *kwds) @@ -1329,7 +1463,7 @@ static PyObject * queuesmod_list_all(PyObject *self, PyObject *Py_UNUSED(ignored)) { int64_t count = 0; - int64_t *qids = _queues_list_all(&_globals.queues, &count); + struct queue_id_and_fmt *qids = _queues_list_all(&_globals.queues, &count); if (qids == NULL) { if (count == 0) { return PyList_New(0); @@ -1340,14 +1474,14 @@ queuesmod_list_all(PyObject *self, PyObject *Py_UNUSED(ignored)) if (ids == NULL) { goto finally; } - int64_t *cur = qids; + struct queue_id_and_fmt *cur = qids; for (int64_t i=0; i < count; cur++, i++) { - PyObject *qidobj = PyLong_FromLongLong(*cur); - if (qidobj == NULL) { + PyObject *item = Py_BuildValue("Li", cur->id, cur->fmt); + if (item == NULL) { Py_SETREF(ids, NULL); break; } - PyList_SET_ITEM(ids, (Py_ssize_t)i, qidobj); + PyList_SET_ITEM(ids, (Py_ssize_t)i, item); } finally: @@ -1356,24 +1490,27 @@ queuesmod_list_all(PyObject *self, PyObject *Py_UNUSED(ignored)) } PyDoc_STRVAR(queuesmod_list_all_doc, -"list_all() -> [qid]\n\ +"list_all() -> [(qid, fmt)]\n\ \n\ -Return the list of IDs for all queues."); +Return the list of IDs for all queues.\n\ +Each corresponding default format is also included."); static PyObject * queuesmod_put(PyObject *self, PyObject *args, PyObject *kwds) { - static char *kwlist[] = {"qid", "obj", NULL}; + static char *kwlist[] = {"qid", "obj", "fmt", NULL}; qidarg_converter_data qidarg; PyObject *obj; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&O:put", kwlist, - qidarg_converter, &qidarg, &obj)) { + int fmt; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&Oi:put", kwlist, + qidarg_converter, &qidarg, &obj, &fmt)) { return NULL; } int64_t qid = qidarg.id; /* Queue up the object. */ - int err = queue_put(&_globals.queues, qid, obj); + int err = queue_put(&_globals.queues, qid, obj, fmt); + // This is the only place that raises QueueFull. if (handle_queue_error(err, self, qid)) { return NULL; } @@ -1382,41 +1519,41 @@ queuesmod_put(PyObject *self, PyObject *args, PyObject *kwds) } PyDoc_STRVAR(queuesmod_put_doc, -"put(qid, obj)\n\ +"put(qid, obj, fmt)\n\ \n\ Add the object's data to the queue."); static PyObject * queuesmod_get(PyObject *self, PyObject *args, PyObject *kwds) { - static char *kwlist[] = {"qid", "default", NULL}; + static char *kwlist[] = {"qid", NULL}; qidarg_converter_data qidarg; - PyObject *dflt = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|O:get", kwlist, - qidarg_converter, &qidarg, &dflt)) { + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&:get", kwlist, + qidarg_converter, &qidarg)) { return NULL; } int64_t qid = qidarg.id; PyObject *obj = NULL; - int err = queue_get(&_globals.queues, qid, &obj); - if (err == ERR_QUEUE_EMPTY && dflt != NULL) { - assert(obj == NULL); - obj = Py_NewRef(dflt); - } - else if (handle_queue_error(err, self, qid)) { + int fmt = 0; + int err = queue_get(&_globals.queues, qid, &obj, &fmt); + // This is the only place that raises QueueEmpty. + if (handle_queue_error(err, self, qid)) { return NULL; } - return obj; + + PyObject *res = Py_BuildValue("Oi", obj, fmt); + Py_DECREF(obj); + return res; } PyDoc_STRVAR(queuesmod_get_doc, -"get(qid, [default]) -> obj\n\ +"get(qid) -> (obj, fmt)\n\ \n\ Return a new object from the data at the front of the queue.\n\ +The object's format is also returned.\n\ \n\ -If there is nothing to receive then raise QueueEmpty, unless\n\ -a default value is provided. In that case return it."); +If there is nothing to receive then raise QueueEmpty."); static PyObject * queuesmod_bind(PyObject *self, PyObject *args, PyObject *kwds) @@ -1463,7 +1600,10 @@ queuesmod_release(PyObject *self, PyObject *args, PyObject *kwds) // XXX Check module state if bound already. // XXX Update module state. - _queues_decref(&_globals.queues, qid); + int err = _queues_decref(&_globals.queues, qid); + if (handle_queue_error(err, self, qid)) { + return NULL; + } Py_RETURN_NONE; } @@ -1499,6 +1639,41 @@ PyDoc_STRVAR(queuesmod_get_maxsize_doc, \n\ Return the maximum number of items in the queue."); +static PyObject * +queuesmod_get_queue_defaults(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"qid", NULL}; + qidarg_converter_data qidarg; + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "O&:get_queue_defaults", kwlist, + qidarg_converter, &qidarg)) { + return NULL; + } + int64_t qid = qidarg.id; + + _queue *queue = NULL; + int err = _queues_lookup(&_globals.queues, qid, &queue); + if (handle_queue_error(err, self, qid)) { + return NULL; + } + int fmt = queue->fmt; + _queue_unmark_waiter(queue, _globals.queues.mutex); + + PyObject *fmt_obj = PyLong_FromLong(fmt); + if (fmt_obj == NULL) { + return NULL; + } + // For now queues only have one default. + PyObject *res = PyTuple_Pack(1, fmt_obj); + Py_DECREF(fmt_obj); + return res; +} + +PyDoc_STRVAR(queuesmod_get_queue_defaults_doc, +"get_queue_defaults(qid)\n\ +\n\ +Return the queue's default values, set when it was created."); + static PyObject * queuesmod_is_full(PyObject *self, PyObject *args, PyObject *kwds) { @@ -1554,22 +1729,39 @@ PyDoc_STRVAR(queuesmod_get_count_doc, Return the number of items in the queue."); static PyObject * -queuesmod__register_queue_type(PyObject *self, PyObject *args, PyObject *kwds) +queuesmod__register_heap_types(PyObject *self, PyObject *args, PyObject *kwds) { - static char *kwlist[] = {"queuetype", NULL}; + static char *kwlist[] = {"queuetype", "emptyerror", "fullerror", NULL}; PyObject *queuetype; + PyObject *emptyerror; + PyObject *fullerror; if (!PyArg_ParseTupleAndKeywords(args, kwds, - "O:_register_queue_type", kwlist, - &queuetype)) { + "OOO:_register_heap_types", kwlist, + &queuetype, &emptyerror, &fullerror)) { return NULL; } if (!PyType_Check(queuetype)) { - PyErr_SetString(PyExc_TypeError, "expected a type for 'queuetype'"); + PyErr_SetString(PyExc_TypeError, + "expected a type for 'queuetype'"); + return NULL; + } + if (!PyExceptionClass_Check(emptyerror)) { + PyErr_SetString(PyExc_TypeError, + "expected an exception type for 'emptyerror'"); + return NULL; + } + if (!PyExceptionClass_Check(fullerror)) { + PyErr_SetString(PyExc_TypeError, + "expected an exception type for 'fullerror'"); return NULL; } - PyTypeObject *cls_queue = (PyTypeObject *)queuetype; - if (set_external_queue_type(self, cls_queue) < 0) { + module_state *state = get_module_state(self); + + if (set_external_queue_type(state, (PyTypeObject *)queuetype) < 0) { + return NULL; + } + if (set_external_exc_types(state, emptyerror, fullerror) < 0) { return NULL; } @@ -1583,21 +1775,23 @@ static PyMethodDef module_functions[] = { METH_VARARGS | METH_KEYWORDS, queuesmod_destroy_doc}, {"list_all", queuesmod_list_all, METH_NOARGS, queuesmod_list_all_doc}, - {"put", _PyCFunction_CAST(queuesmod_put), + {"put", _PyCFunction_CAST(queuesmod_put), METH_VARARGS | METH_KEYWORDS, queuesmod_put_doc}, - {"get", _PyCFunction_CAST(queuesmod_get), + {"get", _PyCFunction_CAST(queuesmod_get), METH_VARARGS | METH_KEYWORDS, queuesmod_get_doc}, - {"bind", _PyCFunction_CAST(queuesmod_bind), + {"bind", _PyCFunction_CAST(queuesmod_bind), METH_VARARGS | METH_KEYWORDS, queuesmod_bind_doc}, {"release", _PyCFunction_CAST(queuesmod_release), METH_VARARGS | METH_KEYWORDS, queuesmod_release_doc}, {"get_maxsize", _PyCFunction_CAST(queuesmod_get_maxsize), METH_VARARGS | METH_KEYWORDS, queuesmod_get_maxsize_doc}, + {"get_queue_defaults", _PyCFunction_CAST(queuesmod_get_queue_defaults), + METH_VARARGS | METH_KEYWORDS, queuesmod_get_queue_defaults_doc}, {"is_full", _PyCFunction_CAST(queuesmod_is_full), METH_VARARGS | METH_KEYWORDS, queuesmod_is_full_doc}, {"get_count", _PyCFunction_CAST(queuesmod_get_count), METH_VARARGS | METH_KEYWORDS, queuesmod_get_count_doc}, - {"_register_queue_type", _PyCFunction_CAST(queuesmod__register_queue_type), + {"_register_heap_types", _PyCFunction_CAST(queuesmod__register_heap_types), METH_VARARGS | METH_KEYWORDS, NULL}, {NULL, NULL} /* sentinel */ @@ -1652,10 +1846,6 @@ module_clear(PyObject *mod) { module_state *state = get_module_state(mod); - if (state->queue_type != NULL) { - (void)_PyCrossInterpreterData_UnregisterClass(state->queue_type); - } - // Now we clear the module state. clear_module_state(state); return 0; diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index b4004d165078f71..28c2f9c08bc0daf 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -902,6 +902,56 @@ The code/function must not take any arguments or be a closure\n\ If a function is provided, its code object is used and all its state\n\ is ignored, including its __globals__ dict."); +static PyObject * +interp_call(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"id", "callable", "args", "kwargs", NULL}; + PyObject *id, *callable; + PyObject *args_obj = NULL; + PyObject *kwargs_obj = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "OO|OO:" MODULE_NAME_STR ".call", kwlist, + &id, &callable, &args_obj, &kwargs_obj)) { + return NULL; + } + + if (args_obj != NULL) { + PyErr_SetString(PyExc_ValueError, "got unexpected args"); + return NULL; + } + if (kwargs_obj != NULL) { + PyErr_SetString(PyExc_ValueError, "got unexpected kwargs"); + return NULL; + } + + PyObject *code = (PyObject *)convert_code_arg(callable, MODULE_NAME_STR ".call", + "argument 2", "a function"); + if (code == NULL) { + return NULL; + } + + PyObject *excinfo = NULL; + int res = _interp_exec(self, id, code, NULL, &excinfo); + Py_DECREF(code); + if (res < 0) { + assert((excinfo == NULL) != (PyErr_Occurred() == NULL)); + return excinfo; + } + Py_RETURN_NONE; +} + +PyDoc_STRVAR(call_doc, +"call(id, callable, args=None, kwargs=None)\n\ +\n\ +Call the provided object in the identified interpreter.\n\ +Pass the given args and kwargs, if possible.\n\ +\n\ +\"callable\" may be a plain function with no free vars that takes\n\ +no arguments.\n\ +\n\ +The function's code object is used and all its state\n\ +is ignored, including its __globals__ dict."); + static PyObject * interp_run_string(PyObject *self, PyObject *args, PyObject *kwds) { @@ -1085,6 +1135,8 @@ static PyMethodDef module_functions[] = { METH_VARARGS | METH_KEYWORDS, is_running_doc}, {"exec", _PyCFunction_CAST(interp_exec), METH_VARARGS | METH_KEYWORDS, exec_doc}, + {"call", _PyCFunction_CAST(interp_call), + METH_VARARGS | METH_KEYWORDS, call_doc}, {"run_string", _PyCFunction_CAST(interp_run_string), METH_VARARGS | METH_KEYWORDS, run_string_doc}, {"run_func", _PyCFunction_CAST(interp_run_func), @@ -1113,6 +1165,7 @@ The 'interpreters' module provides a more convenient interface."); static int module_exec(PyObject *mod) { + PyInterpreterState *interp = PyInterpreterState_Get(); module_state *state = get_module_state(mod); // exceptions @@ -1122,6 +1175,11 @@ module_exec(PyObject *mod) if (PyModule_AddType(mod, (PyTypeObject *)PyExc_InterpreterNotFoundError) < 0) { goto error; } + PyObject *PyExc_NotShareableError = \ + _PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError; + if (PyModule_AddType(mod, (PyTypeObject *)PyExc_NotShareableError) < 0) { + goto error; + } if (register_memoryview_xid(mod, &state->XIBufferViewType) < 0) { goto error; diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c index df09d9d84789f73..317f4974814945e 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -247,7 +247,7 @@ BB_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) if (!PyArg_Parse(v, "b;array item must be integer", &x)) return -1; if (i >= 0) - ((char *)ap->ob_item)[i] = x; + ((unsigned char *)ap->ob_item)[i] = x; return 0; } diff --git a/Modules/cjkcodecs/multibytecodec.c b/Modules/cjkcodecs/multibytecodec.c index 2125da437963d2d..e5433d7dd853066 100644 --- a/Modules/cjkcodecs/multibytecodec.c +++ b/Modules/cjkcodecs/multibytecodec.c @@ -825,8 +825,15 @@ encoder_encode_stateful(MultibyteStatefulEncoderContext *ctx, if (inpos < datalen) { if (datalen - inpos > MAXENCPENDING) { /* normal codecs can't reach here */ - PyErr_SetString(PyExc_UnicodeError, - "pending buffer overflow"); + PyObject *excobj = PyObject_CallFunction(PyExc_UnicodeEncodeError, + "sOnns", + ctx->codec->encoding, + inbuf, + inpos, datalen, + "pending buffer overflow"); + if (excobj == NULL) goto errorexit; + PyErr_SetObject(PyExc_UnicodeEncodeError, excobj); + Py_DECREF(excobj); goto errorexit; } ctx->pending = PyUnicode_Substring(inbuf, inpos, datalen); @@ -857,7 +864,16 @@ decoder_append_pending(MultibyteStatefulDecoderContext *ctx, npendings = (Py_ssize_t)(buf->inbuf_end - buf->inbuf); if (npendings + ctx->pendingsize > MAXDECPENDING || npendings > PY_SSIZE_T_MAX - ctx->pendingsize) { - PyErr_SetString(PyExc_UnicodeError, "pending buffer overflow"); + Py_ssize_t bufsize = (Py_ssize_t)(buf->inbuf_end - buf->inbuf_top); + PyObject *excobj = PyUnicodeDecodeError_Create(ctx->codec->encoding, + (const char *)buf->inbuf_top, + bufsize, + 0, + bufsize, + "pending buffer overflow"); + if (excobj == NULL) return -1; + PyErr_SetObject(PyExc_UnicodeDecodeError, excobj); + Py_DECREF(excobj); return -1; } memcpy(ctx->pending + ctx->pendingsize, buf->inbuf, npendings); @@ -938,7 +954,17 @@ _multibytecodec_MultibyteIncrementalEncoder_getstate_impl(MultibyteIncrementalEn return NULL; } if (pendingsize > MAXENCPENDING*4) { - PyErr_SetString(PyExc_UnicodeError, "pending buffer too large"); + PyObject *excobj = PyObject_CallFunction(PyExc_UnicodeEncodeError, + "sOnns", + self->codec->encoding, + self->pending, + 0, PyUnicode_GET_LENGTH(self->pending), + "pending buffer too large"); + if (excobj == NULL) { + return NULL; + } + PyErr_SetObject(PyExc_UnicodeEncodeError, excobj); + Py_DECREF(excobj); return NULL; } statebytes[0] = (unsigned char)pendingsize; @@ -1267,7 +1293,13 @@ _multibytecodec_MultibyteIncrementalDecoder_setstate_impl(MultibyteIncrementalDe } if (buffersize > MAXDECPENDING) { - PyErr_SetString(PyExc_UnicodeError, "pending buffer too large"); + PyObject *excobj = PyUnicodeDecodeError_Create(self->codec->encoding, + PyBytes_AS_STRING(buffer), buffersize, + 0, buffersize, + "pending buffer too large"); + if (excobj == NULL) return NULL; + PyErr_SetObject(PyExc_UnicodeDecodeError, excobj); + Py_DECREF(excobj); return NULL; } diff --git a/Modules/clinic/_elementtree.c.h b/Modules/clinic/_elementtree.c.h index 9622591a1aa8552..10b2dd1c15f7fd6 100644 --- a/Modules/clinic/_elementtree.c.h +++ b/Modules/clinic/_elementtree.c.h @@ -1169,6 +1169,23 @@ _elementtree_XMLParser_close(XMLParserObject *self, PyObject *Py_UNUSED(ignored) return _elementtree_XMLParser_close_impl(self); } +PyDoc_STRVAR(_elementtree_XMLParser_flush__doc__, +"flush($self, /)\n" +"--\n" +"\n"); + +#define _ELEMENTTREE_XMLPARSER_FLUSH_METHODDEF \ + {"flush", (PyCFunction)_elementtree_XMLParser_flush, METH_NOARGS, _elementtree_XMLParser_flush__doc__}, + +static PyObject * +_elementtree_XMLParser_flush_impl(XMLParserObject *self); + +static PyObject * +_elementtree_XMLParser_flush(XMLParserObject *self, PyObject *Py_UNUSED(ignored)) +{ + return _elementtree_XMLParser_flush_impl(self); +} + PyDoc_STRVAR(_elementtree_XMLParser_feed__doc__, "feed($self, data, /)\n" "--\n" @@ -1219,4 +1236,4 @@ _elementtree_XMLParser__setevents(XMLParserObject *self, PyObject *const *args, exit: return return_value; } -/*[clinic end generated code: output=218ec9e6a889f796 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=aed9f53eeb0404e0 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_ssl.c.h b/Modules/clinic/_ssl.c.h index 2940f16a2cb7f6a..e8d1342ed35e662 100644 --- a/Modules/clinic/_ssl.c.h +++ b/Modules/clinic/_ssl.c.h @@ -6,7 +6,6 @@ preserve # include "pycore_gc.h" // PyGC_Head # include "pycore_runtime.h" // _Py_ID() #endif -#include "pycore_abstract.h" // _PyNumber_Index() #include "pycore_modsupport.h" // _PyArg_CheckPositional() PyDoc_STRVAR(_ssl__SSLSocket_do_handshake__doc__, @@ -1664,4 +1663,4 @@ _ssl_enum_crls(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje #ifndef _SSL_ENUM_CRLS_METHODDEF #define _SSL_ENUM_CRLS_METHODDEF #endif /* !defined(_SSL_ENUM_CRLS_METHODDEF) */ -/*[clinic end generated code: output=fd1c3378fbba5240 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=28a22f2b09d631cb input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_statisticsmodule.c.h b/Modules/clinic/_statisticsmodule.c.h index 653a2138aaad704..cd5b9bea8db2f2e 100644 --- a/Modules/clinic/_statisticsmodule.c.h +++ b/Modules/clinic/_statisticsmodule.c.h @@ -2,15 +2,13 @@ preserve [clinic start generated code]*/ -#include "pycore_modsupport.h" // _PyArg_CheckPositional() - PyDoc_STRVAR(_statistics__normal_dist_inv_cdf__doc__, "_normal_dist_inv_cdf($module, p, mu, sigma, /)\n" "--\n" "\n"); #define _STATISTICS__NORMAL_DIST_INV_CDF_METHODDEF \ - {"_normal_dist_inv_cdf", _PyCFunction_CAST(_statistics__normal_dist_inv_cdf), METH_FASTCALL, _statistics__normal_dist_inv_cdf__doc__}, + {"_normal_dist_inv_cdf", (PyCFunction)(void(*)(void))_statistics__normal_dist_inv_cdf, METH_FASTCALL, _statistics__normal_dist_inv_cdf__doc__}, static double _statistics__normal_dist_inv_cdf_impl(PyObject *module, double p, double mu, @@ -25,38 +23,21 @@ _statistics__normal_dist_inv_cdf(PyObject *module, PyObject *const *args, Py_ssi double sigma; double _return_value; - if (!_PyArg_CheckPositional("_normal_dist_inv_cdf", nargs, 3, 3)) { + if (nargs != 3) { + PyErr_Format(PyExc_TypeError, "_normal_dist_inv_cdf expected 3 arguments, got %zd", nargs); goto exit; } - if (PyFloat_CheckExact(args[0])) { - p = PyFloat_AS_DOUBLE(args[0]); - } - else - { - p = PyFloat_AsDouble(args[0]); - if (p == -1.0 && PyErr_Occurred()) { - goto exit; - } - } - if (PyFloat_CheckExact(args[1])) { - mu = PyFloat_AS_DOUBLE(args[1]); - } - else - { - mu = PyFloat_AsDouble(args[1]); - if (mu == -1.0 && PyErr_Occurred()) { - goto exit; - } + p = PyFloat_AsDouble(args[0]); + if (p == -1.0 && PyErr_Occurred()) { + goto exit; } - if (PyFloat_CheckExact(args[2])) { - sigma = PyFloat_AS_DOUBLE(args[2]); + mu = PyFloat_AsDouble(args[1]); + if (mu == -1.0 && PyErr_Occurred()) { + goto exit; } - else - { - sigma = PyFloat_AsDouble(args[2]); - if (sigma == -1.0 && PyErr_Occurred()) { - goto exit; - } + sigma = PyFloat_AsDouble(args[2]); + if (sigma == -1.0 && PyErr_Occurred()) { + goto exit; } _return_value = _statistics__normal_dist_inv_cdf_impl(module, p, mu, sigma); if ((_return_value == -1.0) && PyErr_Occurred()) { @@ -67,4 +48,4 @@ _statistics__normal_dist_inv_cdf(PyObject *module, PyObject *const *args, Py_ssi exit: return return_value; } -/*[clinic end generated code: output=e7cead17f9f3e19f input=a9049054013a1b77]*/ +/*[clinic end generated code: output=0f0c849d51f16f1b input=a9049054013a1b77]*/ diff --git a/Modules/clinic/_testclinic_limited.c.h b/Modules/clinic/_testclinic_limited.c.h index eff72cee24975c2..94897f4c6dc4276 100644 --- a/Modules/clinic/_testclinic_limited.c.h +++ b/Modules/clinic/_testclinic_limited.c.h @@ -91,4 +91,119 @@ my_int_sum(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=5cf64baf978d2288 input=a9049054013a1b77]*/ + +PyDoc_STRVAR(my_float_sum__doc__, +"my_float_sum($module, x, y, /)\n" +"--\n" +"\n"); + +#define MY_FLOAT_SUM_METHODDEF \ + {"my_float_sum", (PyCFunction)(void(*)(void))my_float_sum, METH_FASTCALL, my_float_sum__doc__}, + +static float +my_float_sum_impl(PyObject *module, float x, float y); + +static PyObject * +my_float_sum(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + float x; + float y; + float _return_value; + + if (nargs != 2) { + PyErr_Format(PyExc_TypeError, "my_float_sum expected 2 arguments, got %zd", nargs); + goto exit; + } + x = (float) PyFloat_AsDouble(args[0]); + if (x == -1.0 && PyErr_Occurred()) { + goto exit; + } + y = (float) PyFloat_AsDouble(args[1]); + if (y == -1.0 && PyErr_Occurred()) { + goto exit; + } + _return_value = my_float_sum_impl(module, x, y); + if ((_return_value == -1.0) && PyErr_Occurred()) { + goto exit; + } + return_value = PyFloat_FromDouble((double)_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(my_double_sum__doc__, +"my_double_sum($module, x, y, /)\n" +"--\n" +"\n"); + +#define MY_DOUBLE_SUM_METHODDEF \ + {"my_double_sum", (PyCFunction)(void(*)(void))my_double_sum, METH_FASTCALL, my_double_sum__doc__}, + +static double +my_double_sum_impl(PyObject *module, double x, double y); + +static PyObject * +my_double_sum(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + double x; + double y; + double _return_value; + + if (nargs != 2) { + PyErr_Format(PyExc_TypeError, "my_double_sum expected 2 arguments, got %zd", nargs); + goto exit; + } + x = PyFloat_AsDouble(args[0]); + if (x == -1.0 && PyErr_Occurred()) { + goto exit; + } + y = PyFloat_AsDouble(args[1]); + if (y == -1.0 && PyErr_Occurred()) { + goto exit; + } + _return_value = my_double_sum_impl(module, x, y); + if ((_return_value == -1.0) && PyErr_Occurred()) { + goto exit; + } + return_value = PyFloat_FromDouble(_return_value); + +exit: + return return_value; +} + +PyDoc_STRVAR(get_file_descriptor__doc__, +"get_file_descriptor($module, file, /)\n" +"--\n" +"\n" +"Get a file descriptor."); + +#define GET_FILE_DESCRIPTOR_METHODDEF \ + {"get_file_descriptor", (PyCFunction)get_file_descriptor, METH_O, get_file_descriptor__doc__}, + +static int +get_file_descriptor_impl(PyObject *module, int fd); + +static PyObject * +get_file_descriptor(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + int fd; + int _return_value; + + fd = PyObject_AsFileDescriptor(arg); + if (fd < 0) { + goto exit; + } + _return_value = get_file_descriptor_impl(module, fd); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyLong_FromLong((long)_return_value); + +exit: + return return_value; +} +/*[clinic end generated code: output=03fd7811c056dc74 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/fcntlmodule.c.h b/Modules/clinic/fcntlmodule.c.h index 5dc2fc068d0f7e4..d4846ddf8df7e43 100644 --- a/Modules/clinic/fcntlmodule.c.h +++ b/Modules/clinic/fcntlmodule.c.h @@ -2,9 +2,6 @@ preserve [clinic start generated code]*/ -#include "pycore_fileutils.h" // _PyLong_FileDescriptor_Converter() -#include "pycore_modsupport.h" // _PyArg_CheckPositional() - PyDoc_STRVAR(fcntl_fcntl__doc__, "fcntl($module, fd, cmd, arg=0, /)\n" "--\n" @@ -22,7 +19,7 @@ PyDoc_STRVAR(fcntl_fcntl__doc__, "corresponding to the return value of the fcntl call in the C code."); #define FCNTL_FCNTL_METHODDEF \ - {"fcntl", _PyCFunction_CAST(fcntl_fcntl), METH_FASTCALL, fcntl_fcntl__doc__}, + {"fcntl", (PyCFunction)(void(*)(void))fcntl_fcntl, METH_FASTCALL, fcntl_fcntl__doc__}, static PyObject * fcntl_fcntl_impl(PyObject *module, int fd, int code, PyObject *arg); @@ -35,10 +32,16 @@ fcntl_fcntl(PyObject *module, PyObject *const *args, Py_ssize_t nargs) int code; PyObject *arg = NULL; - if (!_PyArg_CheckPositional("fcntl", nargs, 2, 3)) { + if (nargs < 2) { + PyErr_Format(PyExc_TypeError, "fcntl expected at least 2 arguments, got %zd", nargs); + goto exit; + } + if (nargs > 3) { + PyErr_Format(PyExc_TypeError, "fcntl expected at most 3 arguments, got %zd", nargs); goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } code = PyLong_AsInt(args[1]); @@ -90,7 +93,7 @@ PyDoc_STRVAR(fcntl_ioctl__doc__, "code."); #define FCNTL_IOCTL_METHODDEF \ - {"ioctl", _PyCFunction_CAST(fcntl_ioctl), METH_FASTCALL, fcntl_ioctl__doc__}, + {"ioctl", (PyCFunction)(void(*)(void))fcntl_ioctl, METH_FASTCALL, fcntl_ioctl__doc__}, static PyObject * fcntl_ioctl_impl(PyObject *module, int fd, unsigned int code, @@ -105,10 +108,16 @@ fcntl_ioctl(PyObject *module, PyObject *const *args, Py_ssize_t nargs) PyObject *ob_arg = NULL; int mutate_arg = 1; - if (!_PyArg_CheckPositional("ioctl", nargs, 2, 4)) { + if (nargs < 2) { + PyErr_Format(PyExc_TypeError, "ioctl expected at least 2 arguments, got %zd", nargs); goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + if (nargs > 4) { + PyErr_Format(PyExc_TypeError, "ioctl expected at most 4 arguments, got %zd", nargs); + goto exit; + } + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } code = (unsigned int)PyLong_AsUnsignedLongMask(args[1]); @@ -143,7 +152,7 @@ PyDoc_STRVAR(fcntl_flock__doc__, "function is emulated using fcntl())."); #define FCNTL_FLOCK_METHODDEF \ - {"flock", _PyCFunction_CAST(fcntl_flock), METH_FASTCALL, fcntl_flock__doc__}, + {"flock", (PyCFunction)(void(*)(void))fcntl_flock, METH_FASTCALL, fcntl_flock__doc__}, static PyObject * fcntl_flock_impl(PyObject *module, int fd, int code); @@ -155,10 +164,12 @@ fcntl_flock(PyObject *module, PyObject *const *args, Py_ssize_t nargs) int fd; int code; - if (!_PyArg_CheckPositional("flock", nargs, 2, 2)) { + if (nargs != 2) { + PyErr_Format(PyExc_TypeError, "flock expected 2 arguments, got %zd", nargs); goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } code = PyLong_AsInt(args[1]); @@ -199,7 +210,7 @@ PyDoc_STRVAR(fcntl_lockf__doc__, " 2 - relative to the end of the file (SEEK_END)"); #define FCNTL_LOCKF_METHODDEF \ - {"lockf", _PyCFunction_CAST(fcntl_lockf), METH_FASTCALL, fcntl_lockf__doc__}, + {"lockf", (PyCFunction)(void(*)(void))fcntl_lockf, METH_FASTCALL, fcntl_lockf__doc__}, static PyObject * fcntl_lockf_impl(PyObject *module, int fd, int code, PyObject *lenobj, @@ -215,10 +226,16 @@ fcntl_lockf(PyObject *module, PyObject *const *args, Py_ssize_t nargs) PyObject *startobj = NULL; int whence = 0; - if (!_PyArg_CheckPositional("lockf", nargs, 2, 5)) { + if (nargs < 2) { + PyErr_Format(PyExc_TypeError, "lockf expected at least 2 arguments, got %zd", nargs); + goto exit; + } + if (nargs > 5) { + PyErr_Format(PyExc_TypeError, "lockf expected at most 5 arguments, got %zd", nargs); goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } code = PyLong_AsInt(args[1]); @@ -246,4 +263,4 @@ fcntl_lockf(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=732e33ba92042031 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=26793691ab1c75ba input=a9049054013a1b77]*/ diff --git a/Modules/clinic/gcmodule.c.h b/Modules/clinic/gcmodule.c.h index d50d170589a2cd5..9fff4da616ba003 100644 --- a/Modules/clinic/gcmodule.c.h +++ b/Modules/clinic/gcmodule.c.h @@ -469,6 +469,25 @@ PyDoc_STRVAR(gc_is_tracked__doc__, #define GC_IS_TRACKED_METHODDEF \ {"is_tracked", (PyCFunction)gc_is_tracked, METH_O, gc_is_tracked__doc__}, +static int +gc_is_tracked_impl(PyObject *module, PyObject *obj); + +static PyObject * +gc_is_tracked(PyObject *module, PyObject *obj) +{ + PyObject *return_value = NULL; + int _return_value; + + _return_value = gc_is_tracked_impl(module, obj); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyBool_FromLong((long)_return_value); + +exit: + return return_value; +} + PyDoc_STRVAR(gc_is_finalized__doc__, "is_finalized($module, obj, /)\n" "--\n" @@ -478,6 +497,25 @@ PyDoc_STRVAR(gc_is_finalized__doc__, #define GC_IS_FINALIZED_METHODDEF \ {"is_finalized", (PyCFunction)gc_is_finalized, METH_O, gc_is_finalized__doc__}, +static int +gc_is_finalized_impl(PyObject *module, PyObject *obj); + +static PyObject * +gc_is_finalized(PyObject *module, PyObject *obj) +{ + PyObject *return_value = NULL; + int _return_value; + + _return_value = gc_is_finalized_impl(module, obj); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyBool_FromLong((long)_return_value); + +exit: + return return_value; +} + PyDoc_STRVAR(gc_freeze__doc__, "freeze($module, /)\n" "--\n" @@ -547,4 +585,4 @@ gc_get_freeze_count(PyObject *module, PyObject *Py_UNUSED(ignored)) exit: return return_value; } -/*[clinic end generated code: output=258f92524c1141fc input=a9049054013a1b77]*/ +/*[clinic end generated code: output=0a7e91917adcb937 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/grpmodule.c.h b/Modules/clinic/grpmodule.c.h index 1da061f3fd8eb12..cc0ad210f427434 100644 --- a/Modules/clinic/grpmodule.c.h +++ b/Modules/clinic/grpmodule.c.h @@ -2,12 +2,6 @@ preserve [clinic start generated code]*/ -#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) -# include "pycore_gc.h" // PyGC_Head -# include "pycore_runtime.h" // _Py_ID() -#endif -#include "pycore_modsupport.h" // _PyArg_UnpackKeywords() - PyDoc_STRVAR(grp_getgrgid__doc__, "getgrgid($module, /, id)\n" "--\n" @@ -17,48 +11,21 @@ PyDoc_STRVAR(grp_getgrgid__doc__, "If id is not valid, raise KeyError."); #define GRP_GETGRGID_METHODDEF \ - {"getgrgid", _PyCFunction_CAST(grp_getgrgid), METH_FASTCALL|METH_KEYWORDS, grp_getgrgid__doc__}, + {"getgrgid", (PyCFunction)(void(*)(void))grp_getgrgid, METH_VARARGS|METH_KEYWORDS, grp_getgrgid__doc__}, static PyObject * grp_getgrgid_impl(PyObject *module, PyObject *id); static PyObject * -grp_getgrgid(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +grp_getgrgid(PyObject *module, PyObject *args, PyObject *kwargs) { PyObject *return_value = NULL; - #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - - #define NUM_KEYWORDS 1 - static struct { - PyGC_Head _this_is_not_used; - PyObject_VAR_HEAD - PyObject *ob_item[NUM_KEYWORDS]; - } _kwtuple = { - .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) - .ob_item = { &_Py_ID(id), }, - }; - #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[] = {"id", NULL}; - static _PyArg_Parser _parser = { - .keywords = _keywords, - .fname = "getgrgid", - .kwtuple = KWTUPLE, - }; - #undef KWTUPLE - PyObject *argsbuf[1]; + static char *_keywords[] = {"id", NULL}; PyObject *id; - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); - if (!args) { + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O:getgrgid", _keywords, + &id)) goto exit; - } - id = args[0]; return_value = grp_getgrgid_impl(module, id); exit: @@ -74,52 +41,21 @@ PyDoc_STRVAR(grp_getgrnam__doc__, "If name is not valid, raise KeyError."); #define GRP_GETGRNAM_METHODDEF \ - {"getgrnam", _PyCFunction_CAST(grp_getgrnam), METH_FASTCALL|METH_KEYWORDS, grp_getgrnam__doc__}, + {"getgrnam", (PyCFunction)(void(*)(void))grp_getgrnam, METH_VARARGS|METH_KEYWORDS, grp_getgrnam__doc__}, static PyObject * grp_getgrnam_impl(PyObject *module, PyObject *name); static PyObject * -grp_getgrnam(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +grp_getgrnam(PyObject *module, PyObject *args, PyObject *kwargs) { PyObject *return_value = NULL; - #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - - #define NUM_KEYWORDS 1 - static struct { - PyGC_Head _this_is_not_used; - PyObject_VAR_HEAD - PyObject *ob_item[NUM_KEYWORDS]; - } _kwtuple = { - .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) - .ob_item = { &_Py_ID(name), }, - }; - #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[] = {"name", NULL}; - static _PyArg_Parser _parser = { - .keywords = _keywords, - .fname = "getgrnam", - .kwtuple = KWTUPLE, - }; - #undef KWTUPLE - PyObject *argsbuf[1]; + static char *_keywords[] = {"name", NULL}; PyObject *name; - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); - if (!args) { - goto exit; - } - if (!PyUnicode_Check(args[0])) { - _PyArg_BadArgument("getgrnam", "argument 'name'", "str", args[0]); + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "U:getgrnam", _keywords, + &name)) goto exit; - } - name = args[0]; return_value = grp_getgrnam_impl(module, name); exit: @@ -146,4 +82,4 @@ grp_getgrall(PyObject *module, PyObject *Py_UNUSED(ignored)) { return grp_getgrall_impl(module); } -/*[clinic end generated code: output=2f7011384604d38d input=a9049054013a1b77]*/ +/*[clinic end generated code: output=81f180beb67fc585 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h index ca14c03f16f706a..666b6b3790dae53 100644 --- a/Modules/clinic/mathmodule.c.h +++ b/Modules/clinic/mathmodule.c.h @@ -204,6 +204,67 @@ PyDoc_STRVAR(math_log10__doc__, #define MATH_LOG10_METHODDEF \ {"log10", (PyCFunction)math_log10, METH_O, math_log10__doc__}, +PyDoc_STRVAR(math_fma__doc__, +"fma($module, x, y, z, /)\n" +"--\n" +"\n" +"Fused multiply-add operation.\n" +"\n" +"Compute (x * y) + z with a single round."); + +#define MATH_FMA_METHODDEF \ + {"fma", _PyCFunction_CAST(math_fma), METH_FASTCALL, math_fma__doc__}, + +static PyObject * +math_fma_impl(PyObject *module, double x, double y, double z); + +static PyObject * +math_fma(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + double x; + double y; + double z; + + if (!_PyArg_CheckPositional("fma", nargs, 3, 3)) { + goto exit; + } + if (PyFloat_CheckExact(args[0])) { + x = PyFloat_AS_DOUBLE(args[0]); + } + else + { + x = PyFloat_AsDouble(args[0]); + if (x == -1.0 && PyErr_Occurred()) { + goto exit; + } + } + if (PyFloat_CheckExact(args[1])) { + y = PyFloat_AS_DOUBLE(args[1]); + } + else + { + y = PyFloat_AsDouble(args[1]); + if (y == -1.0 && PyErr_Occurred()) { + goto exit; + } + } + if (PyFloat_CheckExact(args[2])) { + z = PyFloat_AS_DOUBLE(args[2]); + } + else + { + z = PyFloat_AsDouble(args[2]); + if (z == -1.0 && PyErr_Occurred()) { + goto exit; + } + } + return_value = math_fma_impl(module, x, y, z); + +exit: + return return_value; +} + PyDoc_STRVAR(math_fmod__doc__, "fmod($module, x, y, /)\n" "--\n" @@ -950,4 +1011,4 @@ math_ulp(PyObject *module, PyObject *arg) exit: return return_value; } -/*[clinic end generated code: output=6b2eeaed8d8a76d5 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=9fe3f007f474e015 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index b49d64d4281889e..0398629e3c10ce9 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -7,7 +7,6 @@ preserve # include "pycore_runtime.h" // _Py_ID() #endif #include "pycore_abstract.h" // _PyNumber_Index() -#include "pycore_fileutils.h" // _PyLong_FileDescriptor_Converter() #include "pycore_long.h" // _PyLong_UnsignedInt_Converter() #include "pycore_modsupport.h" // _PyArg_UnpackKeywords() @@ -481,7 +480,8 @@ os_fchdir(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *k if (!args) { goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } return_value = os_fchdir_impl(module, fd); @@ -1024,7 +1024,8 @@ os_fsync(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw if (!args) { goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } return_value = os_fsync_impl(module, fd); @@ -1107,7 +1108,8 @@ os_fdatasync(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject if (!args) { goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } return_value = os_fdatasync_impl(module, fd); @@ -4531,7 +4533,8 @@ os_grantpt(PyObject *module, PyObject *arg) PyObject *return_value = NULL; int fd; - if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + fd = PyObject_AsFileDescriptor(arg); + if (fd < 0) { goto exit; } return_value = os_grantpt_impl(module, fd); @@ -4567,7 +4570,8 @@ os_unlockpt(PyObject *module, PyObject *arg) PyObject *return_value = NULL; int fd; - if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + fd = PyObject_AsFileDescriptor(arg); + if (fd < 0) { goto exit; } return_value = os_unlockpt_impl(module, fd); @@ -4604,7 +4608,8 @@ os_ptsname(PyObject *module, PyObject *arg) PyObject *return_value = NULL; int fd; - if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + fd = PyObject_AsFileDescriptor(arg); + if (fd < 0) { goto exit; } return_value = os_ptsname_impl(module, fd); @@ -4664,7 +4669,8 @@ os_login_tty(PyObject *module, PyObject *arg) PyObject *return_value = NULL; int fd; - if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + fd = PyObject_AsFileDescriptor(arg); + if (fd < 0) { goto exit; } return_value = os_login_tty_impl(module, fd); @@ -5881,7 +5887,8 @@ os_setns(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw if (!args) { goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } if (!noptargs) { @@ -6279,8 +6286,8 @@ PyDoc_STRVAR(os_timerfd_settime__doc__, {"timerfd_settime", _PyCFunction_CAST(os_timerfd_settime), METH_FASTCALL|METH_KEYWORDS, os_timerfd_settime__doc__}, static PyObject * -os_timerfd_settime_impl(PyObject *module, int fd, int flags, double initial, - double interval); +os_timerfd_settime_impl(PyObject *module, int fd, int flags, + double initial_double, double interval_double); static PyObject * os_timerfd_settime(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -6315,14 +6322,15 @@ os_timerfd_settime(PyObject *module, PyObject *const *args, Py_ssize_t nargs, Py Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; int fd; int flags = 0; - double initial = 0.0; - double interval = 0.0; + double initial_double = 0.0; + double interval_double = 0.0; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); if (!args) { goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } if (!noptargs) { @@ -6339,12 +6347,12 @@ os_timerfd_settime(PyObject *module, PyObject *const *args, Py_ssize_t nargs, Py } if (args[2]) { if (PyFloat_CheckExact(args[2])) { - initial = PyFloat_AS_DOUBLE(args[2]); + initial_double = PyFloat_AS_DOUBLE(args[2]); } else { - initial = PyFloat_AsDouble(args[2]); - if (initial == -1.0 && PyErr_Occurred()) { + initial_double = PyFloat_AsDouble(args[2]); + if (initial_double == -1.0 && PyErr_Occurred()) { goto exit; } } @@ -6353,17 +6361,17 @@ os_timerfd_settime(PyObject *module, PyObject *const *args, Py_ssize_t nargs, Py } } if (PyFloat_CheckExact(args[3])) { - interval = PyFloat_AS_DOUBLE(args[3]); + interval_double = PyFloat_AS_DOUBLE(args[3]); } else { - interval = PyFloat_AsDouble(args[3]); - if (interval == -1.0 && PyErr_Occurred()) { + interval_double = PyFloat_AsDouble(args[3]); + if (interval_double == -1.0 && PyErr_Occurred()) { goto exit; } } skip_optional_kwonly: - return_value = os_timerfd_settime_impl(module, fd, flags, initial, interval); + return_value = os_timerfd_settime_impl(module, fd, flags, initial_double, interval_double); exit: return return_value; @@ -6435,7 +6443,8 @@ os_timerfd_settime_ns(PyObject *module, PyObject *const *args, Py_ssize_t nargs, if (!args) { goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } if (!noptargs) { @@ -6495,7 +6504,8 @@ os_timerfd_gettime(PyObject *module, PyObject *arg) PyObject *return_value = NULL; int fd; - if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + fd = PyObject_AsFileDescriptor(arg); + if (fd < 0) { goto exit; } return_value = os_timerfd_gettime_impl(module, fd); @@ -6529,7 +6539,8 @@ os_timerfd_gettime_ns(PyObject *module, PyObject *arg) PyObject *return_value = NULL; int fd; - if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + fd = PyObject_AsFileDescriptor(arg); + if (fd < 0) { goto exit; } return_value = os_timerfd_gettime_ns_impl(module, fd); @@ -9691,7 +9702,8 @@ os_fpathconf(PyObject *module, PyObject *const *args, Py_ssize_t nargs) if (!_PyArg_CheckPositional("fpathconf", nargs, 2, 2)) { goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } if (!conv_path_confname(args[1], &name)) { @@ -10834,7 +10846,8 @@ os_eventfd_read(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObj if (!args) { goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } return_value = os_eventfd_read_impl(module, fd); @@ -10896,7 +10909,8 @@ os_eventfd_write(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb if (!args) { goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } if (!_PyLong_UnsignedLongLong_Converter(args[1], &value)) { @@ -12588,4 +12602,4 @@ os__supports_virtual_terminal(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #define OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #endif /* !defined(OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF) */ -/*[clinic end generated code: output=268af5cbc8baa9d4 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=511f0788a6b90db0 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/pwdmodule.c.h b/Modules/clinic/pwdmodule.c.h index 43d4825031c7e61..365d99aab1dd221 100644 --- a/Modules/clinic/pwdmodule.c.h +++ b/Modules/clinic/pwdmodule.c.h @@ -2,8 +2,6 @@ preserve [clinic start generated code]*/ -#include "pycore_modsupport.h" // _PyArg_BadArgument() - PyDoc_STRVAR(pwd_getpwuid__doc__, "getpwuid($module, uidobj, /)\n" "--\n" @@ -36,7 +34,7 @@ pwd_getpwnam(PyObject *module, PyObject *arg) PyObject *name; if (!PyUnicode_Check(arg)) { - _PyArg_BadArgument("getpwnam", "argument", "str", arg); + PyErr_Format(PyExc_TypeError, "getpwnam() argument must be str, not %T", arg); goto exit; } name = arg; @@ -73,4 +71,4 @@ pwd_getpwall(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef PWD_GETPWALL_METHODDEF #define PWD_GETPWALL_METHODDEF #endif /* !defined(PWD_GETPWALL_METHODDEF) */ -/*[clinic end generated code: output=5a8fb12939ff4ea3 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=dac88d500f6d6f49 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/pyexpat.c.h b/Modules/clinic/pyexpat.c.h index a5b93e68598204a..343cb91b9750388 100644 --- a/Modules/clinic/pyexpat.c.h +++ b/Modules/clinic/pyexpat.c.h @@ -8,6 +8,53 @@ preserve #endif #include "pycore_modsupport.h" // _PyArg_UnpackKeywords() +PyDoc_STRVAR(pyexpat_xmlparser_SetReparseDeferralEnabled__doc__, +"SetReparseDeferralEnabled($self, enabled, /)\n" +"--\n" +"\n" +"Enable/Disable reparse deferral; enabled by default with Expat >=2.6.0."); + +#define PYEXPAT_XMLPARSER_SETREPARSEDEFERRALENABLED_METHODDEF \ + {"SetReparseDeferralEnabled", (PyCFunction)pyexpat_xmlparser_SetReparseDeferralEnabled, METH_O, pyexpat_xmlparser_SetReparseDeferralEnabled__doc__}, + +static PyObject * +pyexpat_xmlparser_SetReparseDeferralEnabled_impl(xmlparseobject *self, + int enabled); + +static PyObject * +pyexpat_xmlparser_SetReparseDeferralEnabled(xmlparseobject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + int enabled; + + enabled = PyObject_IsTrue(arg); + if (enabled < 0) { + goto exit; + } + return_value = pyexpat_xmlparser_SetReparseDeferralEnabled_impl(self, enabled); + +exit: + return return_value; +} + +PyDoc_STRVAR(pyexpat_xmlparser_GetReparseDeferralEnabled__doc__, +"GetReparseDeferralEnabled($self, /)\n" +"--\n" +"\n" +"Retrieve reparse deferral enabled status; always returns false with Expat <2.6.0."); + +#define PYEXPAT_XMLPARSER_GETREPARSEDEFERRALENABLED_METHODDEF \ + {"GetReparseDeferralEnabled", (PyCFunction)pyexpat_xmlparser_GetReparseDeferralEnabled, METH_NOARGS, pyexpat_xmlparser_GetReparseDeferralEnabled__doc__}, + +static PyObject * +pyexpat_xmlparser_GetReparseDeferralEnabled_impl(xmlparseobject *self); + +static PyObject * +pyexpat_xmlparser_GetReparseDeferralEnabled(xmlparseobject *self, PyObject *Py_UNUSED(ignored)) +{ + return pyexpat_xmlparser_GetReparseDeferralEnabled_impl(self); +} + PyDoc_STRVAR(pyexpat_xmlparser_Parse__doc__, "Parse($self, data, isfinal=False, /)\n" "--\n" @@ -498,4 +545,4 @@ pyexpat_ErrorString(PyObject *module, PyObject *arg) #ifndef PYEXPAT_XMLPARSER_USEFOREIGNDTD_METHODDEF #define PYEXPAT_XMLPARSER_USEFOREIGNDTD_METHODDEF #endif /* !defined(PYEXPAT_XMLPARSER_USEFOREIGNDTD_METHODDEF) */ -/*[clinic end generated code: output=48c4296e43777df4 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=892e48e41f9b6e4b input=a9049054013a1b77]*/ diff --git a/Modules/clinic/selectmodule.c.h b/Modules/clinic/selectmodule.c.h index 67e76d4c89f59ac..dc7d3fb814396df 100644 --- a/Modules/clinic/selectmodule.c.h +++ b/Modules/clinic/selectmodule.c.h @@ -6,7 +6,6 @@ preserve # include "pycore_gc.h" // PyGC_Head # include "pycore_runtime.h" // _Py_ID() #endif -#include "pycore_fileutils.h" // _PyLong_FileDescriptor_Converter() #include "pycore_long.h" // _PyLong_UnsignedShort_Converter() #include "pycore_modsupport.h" // _PyArg_CheckPositional() @@ -100,7 +99,8 @@ select_poll_register(pollObject *self, PyObject *const *args, Py_ssize_t nargs) if (!_PyArg_CheckPositional("register", nargs, 1, 2)) { goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } if (nargs < 2) { @@ -148,7 +148,8 @@ select_poll_modify(pollObject *self, PyObject *const *args, Py_ssize_t nargs) if (!_PyArg_CheckPositional("modify", nargs, 2, 2)) { goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } if (!_PyLong_UnsignedShort_Converter(args[1], &eventmask)) { @@ -182,7 +183,8 @@ select_poll_unregister(pollObject *self, PyObject *arg) PyObject *return_value = NULL; int fd; - if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + fd = PyObject_AsFileDescriptor(arg); + if (fd < 0) { goto exit; } return_value = select_poll_unregister_impl(self, fd); @@ -268,7 +270,8 @@ select_devpoll_register(devpollObject *self, PyObject *const *args, Py_ssize_t n if (!_PyArg_CheckPositional("register", nargs, 1, 2)) { goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } if (nargs < 2) { @@ -318,7 +321,8 @@ select_devpoll_modify(devpollObject *self, PyObject *const *args, Py_ssize_t nar if (!_PyArg_CheckPositional("modify", nargs, 1, 2)) { goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } if (nargs < 2) { @@ -356,7 +360,8 @@ select_devpoll_unregister(devpollObject *self, PyObject *arg) PyObject *return_value = NULL; int fd; - if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + fd = PyObject_AsFileDescriptor(arg); + if (fd < 0) { goto exit; } return_value = select_devpoll_unregister_impl(self, fd); @@ -730,7 +735,8 @@ select_epoll_register(pyEpoll_Object *self, PyObject *const *args, Py_ssize_t na if (!args) { goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } if (!noptargs) { @@ -806,7 +812,8 @@ select_epoll_modify(pyEpoll_Object *self, PyObject *const *args, Py_ssize_t narg if (!args) { goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } eventmask = (unsigned int)PyLong_AsUnsignedLongMask(args[1]); @@ -874,7 +881,8 @@ select_epoll_unregister(pyEpoll_Object *self, PyObject *const *args, Py_ssize_t if (!args) { goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } return_value = select_epoll_unregister_impl(self, fd); @@ -1311,4 +1319,4 @@ select_kqueue_control(kqueue_queue_Object *self, PyObject *const *args, Py_ssize #ifndef SELECT_KQUEUE_CONTROL_METHODDEF #define SELECT_KQUEUE_CONTROL_METHODDEF #endif /* !defined(SELECT_KQUEUE_CONTROL_METHODDEF) */ -/*[clinic end generated code: output=4c2dcb31cb17c2c6 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=4fc17ae9b6cfdc86 input=a9049054013a1b77]*/ diff --git a/Modules/clinic/termios.c.h b/Modules/clinic/termios.c.h index 1c340681e5862fd..83f5a4f6e9f8820 100644 --- a/Modules/clinic/termios.c.h +++ b/Modules/clinic/termios.c.h @@ -2,9 +2,6 @@ preserve [clinic start generated code]*/ -#include "pycore_fileutils.h" // _PyLong_FileDescriptor_Converter() -#include "pycore_modsupport.h" // _PyArg_CheckPositional() - PyDoc_STRVAR(termios_tcgetattr__doc__, "tcgetattr($module, fd, /)\n" "--\n" @@ -30,7 +27,8 @@ termios_tcgetattr(PyObject *module, PyObject *arg) PyObject *return_value = NULL; int fd; - if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + fd = PyObject_AsFileDescriptor(arg); + if (fd < 0) { goto exit; } return_value = termios_tcgetattr_impl(module, fd); @@ -53,7 +51,7 @@ PyDoc_STRVAR(termios_tcsetattr__doc__, "queued output and discarding all queued input."); #define TERMIOS_TCSETATTR_METHODDEF \ - {"tcsetattr", _PyCFunction_CAST(termios_tcsetattr), METH_FASTCALL, termios_tcsetattr__doc__}, + {"tcsetattr", (PyCFunction)(void(*)(void))termios_tcsetattr, METH_FASTCALL, termios_tcsetattr__doc__}, static PyObject * termios_tcsetattr_impl(PyObject *module, int fd, int when, PyObject *term); @@ -66,10 +64,12 @@ termios_tcsetattr(PyObject *module, PyObject *const *args, Py_ssize_t nargs) int when; PyObject *term; - if (!_PyArg_CheckPositional("tcsetattr", nargs, 3, 3)) { + if (nargs != 3) { + PyErr_Format(PyExc_TypeError, "tcsetattr expected 3 arguments, got %zd", nargs); goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } when = PyLong_AsInt(args[1]); @@ -93,7 +93,7 @@ PyDoc_STRVAR(termios_tcsendbreak__doc__, "has a system dependent meaning."); #define TERMIOS_TCSENDBREAK_METHODDEF \ - {"tcsendbreak", _PyCFunction_CAST(termios_tcsendbreak), METH_FASTCALL, termios_tcsendbreak__doc__}, + {"tcsendbreak", (PyCFunction)(void(*)(void))termios_tcsendbreak, METH_FASTCALL, termios_tcsendbreak__doc__}, static PyObject * termios_tcsendbreak_impl(PyObject *module, int fd, int duration); @@ -105,10 +105,12 @@ termios_tcsendbreak(PyObject *module, PyObject *const *args, Py_ssize_t nargs) int fd; int duration; - if (!_PyArg_CheckPositional("tcsendbreak", nargs, 2, 2)) { + if (nargs != 2) { + PyErr_Format(PyExc_TypeError, "tcsendbreak expected 2 arguments, got %zd", nargs); goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } duration = PyLong_AsInt(args[1]); @@ -139,7 +141,8 @@ termios_tcdrain(PyObject *module, PyObject *arg) PyObject *return_value = NULL; int fd; - if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + fd = PyObject_AsFileDescriptor(arg); + if (fd < 0) { goto exit; } return_value = termios_tcdrain_impl(module, fd); @@ -159,7 +162,7 @@ PyDoc_STRVAR(termios_tcflush__doc__, "both queues."); #define TERMIOS_TCFLUSH_METHODDEF \ - {"tcflush", _PyCFunction_CAST(termios_tcflush), METH_FASTCALL, termios_tcflush__doc__}, + {"tcflush", (PyCFunction)(void(*)(void))termios_tcflush, METH_FASTCALL, termios_tcflush__doc__}, static PyObject * termios_tcflush_impl(PyObject *module, int fd, int queue); @@ -171,10 +174,12 @@ termios_tcflush(PyObject *module, PyObject *const *args, Py_ssize_t nargs) int fd; int queue; - if (!_PyArg_CheckPositional("tcflush", nargs, 2, 2)) { + if (nargs != 2) { + PyErr_Format(PyExc_TypeError, "tcflush expected 2 arguments, got %zd", nargs); goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } queue = PyLong_AsInt(args[1]); @@ -198,7 +203,7 @@ PyDoc_STRVAR(termios_tcflow__doc__, "or termios.TCION to restart input."); #define TERMIOS_TCFLOW_METHODDEF \ - {"tcflow", _PyCFunction_CAST(termios_tcflow), METH_FASTCALL, termios_tcflow__doc__}, + {"tcflow", (PyCFunction)(void(*)(void))termios_tcflow, METH_FASTCALL, termios_tcflow__doc__}, static PyObject * termios_tcflow_impl(PyObject *module, int fd, int action); @@ -210,10 +215,12 @@ termios_tcflow(PyObject *module, PyObject *const *args, Py_ssize_t nargs) int fd; int action; - if (!_PyArg_CheckPositional("tcflow", nargs, 2, 2)) { + if (nargs != 2) { + PyErr_Format(PyExc_TypeError, "tcflow expected 2 arguments, got %zd", nargs); goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } action = PyLong_AsInt(args[1]); @@ -246,7 +253,8 @@ termios_tcgetwinsize(PyObject *module, PyObject *arg) PyObject *return_value = NULL; int fd; - if (!_PyLong_FileDescriptor_Converter(arg, &fd)) { + fd = PyObject_AsFileDescriptor(arg); + if (fd < 0) { goto exit; } return_value = termios_tcgetwinsize_impl(module, fd); @@ -265,7 +273,7 @@ PyDoc_STRVAR(termios_tcsetwinsize__doc__, "is a two-item tuple (ws_row, ws_col) like the one returned by tcgetwinsize()."); #define TERMIOS_TCSETWINSIZE_METHODDEF \ - {"tcsetwinsize", _PyCFunction_CAST(termios_tcsetwinsize), METH_FASTCALL, termios_tcsetwinsize__doc__}, + {"tcsetwinsize", (PyCFunction)(void(*)(void))termios_tcsetwinsize, METH_FASTCALL, termios_tcsetwinsize__doc__}, static PyObject * termios_tcsetwinsize_impl(PyObject *module, int fd, PyObject *winsz); @@ -277,10 +285,12 @@ termios_tcsetwinsize(PyObject *module, PyObject *const *args, Py_ssize_t nargs) int fd; PyObject *winsz; - if (!_PyArg_CheckPositional("tcsetwinsize", nargs, 2, 2)) { + if (nargs != 2) { + PyErr_Format(PyExc_TypeError, "tcsetwinsize expected 2 arguments, got %zd", nargs); goto exit; } - if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) { + fd = PyObject_AsFileDescriptor(args[0]); + if (fd < 0) { goto exit; } winsz = args[1]; @@ -289,4 +299,4 @@ termios_tcsetwinsize(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=f31382658135c774 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c6c6192583b0da36 input=a9049054013a1b77]*/ diff --git a/Modules/errnomodule.c b/Modules/errnomodule.c index 1100e9f6094352b..97e5f0180d76fb1 100644 --- a/Modules/errnomodule.c +++ b/Modules/errnomodule.c @@ -1,10 +1,9 @@ /* Errno module */ +// Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED #include "pyconfig.h" // Py_GIL_DISABLED - #ifndef Py_GIL_DISABLED -// Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED -#define Py_LIMITED_API 0x030c0000 +# define Py_LIMITED_API 0x030c0000 #endif #include "Python.h" diff --git a/Modules/expat/pyexpatns.h b/Modules/expat/pyexpatns.h index d45d9b6c4571595..8ee03ef07928157 100644 --- a/Modules/expat/pyexpatns.h +++ b/Modules/expat/pyexpatns.h @@ -108,6 +108,7 @@ #define XML_SetNotStandaloneHandler PyExpat_XML_SetNotStandaloneHandler #define XML_SetParamEntityParsing PyExpat_XML_SetParamEntityParsing #define XML_SetProcessingInstructionHandler PyExpat_XML_SetProcessingInstructionHandler +#define XML_SetReparseDeferralEnabled PyExpat_XML_SetReparseDeferralEnabled #define XML_SetReturnNSTriplet PyExpat_XML_SetReturnNSTriplet #define XML_SetSkippedEntityHandler PyExpat_XML_SetSkippedEntityHandler #define XML_SetStartCdataSectionHandler PyExpat_XML_SetStartCdataSectionHandler diff --git a/Modules/fcntlmodule.c b/Modules/fcntlmodule.c index 0d16602692b62d0..e24e5f98f4bc4d7 100644 --- a/Modules/fcntlmodule.c +++ b/Modules/fcntlmodule.c @@ -1,22 +1,25 @@ /* fcntl module */ -#ifndef Py_BUILD_CORE_BUILTIN -# define Py_BUILD_CORE_MODULE 1 +// Need limited C API version 3.13 for PyLong_AsInt() +#include "pyconfig.h" // Py_GIL_DISABLED +#ifndef Py_GIL_DISABLED +# define Py_LIMITED_API 0x030d0000 #endif #include "Python.h" +#include // EINTR +#include // fcntl() +#include // memcpy() +#include // ioctl() #ifdef HAVE_SYS_FILE_H -#include +# include // flock() #endif #ifdef HAVE_LINUX_FS_H -#include +# include #endif - -#include -#include #ifdef HAVE_STROPTS_H -#include +# include // I_FLUSHBAND #endif /*[clinic input] diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 961165e16a0fee9..9807d2e7d48a364 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -383,7 +383,7 @@ gc_get_stats_impl(PyObject *module) /*[clinic input] -gc.is_tracked +gc.is_tracked -> bool obj: object / @@ -393,21 +393,15 @@ Returns true if the object is tracked by the garbage collector. Simple atomic objects will return false. [clinic start generated code]*/ -static PyObject * -gc_is_tracked(PyObject *module, PyObject *obj) -/*[clinic end generated code: output=14f0103423b28e31 input=d83057f170ea2723]*/ +static int +gc_is_tracked_impl(PyObject *module, PyObject *obj) +/*[clinic end generated code: output=91c8d086b7f47a33 input=423b98ec680c3126]*/ { - PyObject *result; - - if (_PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj)) - result = Py_True; - else - result = Py_False; - return Py_NewRef(result); + return PyObject_GC_IsTracked(obj); } /*[clinic input] -gc.is_finalized +gc.is_finalized -> bool obj: object / @@ -415,14 +409,11 @@ gc.is_finalized Returns true if the object has been already finalized by the GC. [clinic start generated code]*/ -static PyObject * -gc_is_finalized(PyObject *module, PyObject *obj) -/*[clinic end generated code: output=e1516ac119a918ed input=201d0c58f69ae390]*/ +static int +gc_is_finalized_impl(PyObject *module, PyObject *obj) +/*[clinic end generated code: output=401ff5d6fc660429 input=ca4d111c8f8c4e3a]*/ { - if (_PyObject_IS_GC(obj) && _PyGC_FINALIZED(obj)) { - Py_RETURN_TRUE; - } - Py_RETURN_FALSE; + return PyObject_GC_IsFinalized(obj); } /*[clinic input] diff --git a/Modules/grpmodule.c b/Modules/grpmodule.c index 9756f1c2fb15b1a..a1fa6cf20f71fd8 100644 --- a/Modules/grpmodule.c +++ b/Modules/grpmodule.c @@ -1,15 +1,17 @@ - /* UNIX group file access module */ -// clinic/grpmodule.c.h uses internal pycore_modsupport.h API -#ifndef Py_BUILD_CORE_BUILTIN -# define Py_BUILD_CORE_MODULE 1 +// Need limited C API version 3.13 for PyMem_RawRealloc() +#include "pyconfig.h" // Py_GIL_DISABLED +#ifndef Py_GIL_DISABLED +#define Py_LIMITED_API 0x030d0000 #endif #include "Python.h" #include "posixmodule.h" +#include // ERANGE #include // getgrgid_r() +#include // memcpy() #include // sysconf() #include "clinic/grpmodule.c.h" @@ -88,7 +90,7 @@ mkgrent(PyObject *module, struct group *p) Py_DECREF(x); } -#define SET(i,val) PyStructSequence_SET_ITEM(v, i, val) +#define SET(i,val) PyStructSequence_SetItem(v, i, val) SET(setIndex++, PyUnicode_DecodeFSDefault(p->gr_name)); if (p->gr_passwd) SET(setIndex++, PyUnicode_DecodeFSDefault(p->gr_passwd)); diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 164741495c7baf9..44b92f8dcffe4df 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -815,10 +815,9 @@ teedataobject_traverse(teedataobject *tdo, visitproc visit, void * arg) } static void -teedataobject_safe_decref(PyObject *obj, PyTypeObject *tdo_type) +teedataobject_safe_decref(PyObject *obj) { - while (obj && Py_IS_TYPE(obj, tdo_type) && - Py_REFCNT(obj) == 1) { + while (obj && Py_REFCNT(obj) == 1) { PyObject *nextlink = ((teedataobject *)obj)->nextlink; ((teedataobject *)obj)->nextlink = NULL; Py_SETREF(obj, nextlink); @@ -837,8 +836,7 @@ teedataobject_clear(teedataobject *tdo) Py_CLEAR(tdo->values[i]); tmp = tdo->nextlink; tdo->nextlink = NULL; - itertools_state *state = get_module_state_by_cls(Py_TYPE(tdo)); - teedataobject_safe_decref(tmp, state->teedataobject_type); + teedataobject_safe_decref(tmp); return 0; } @@ -4623,15 +4621,15 @@ batched(p, n) --> [p0, p1, ..., p_n-1], [p_n, p_n+1, ..., p_2n-1], ...\n\ chain(p, q, ...) --> p0, p1, ... plast, q0, q1, ...\n\ chain.from_iterable([p, q, ...]) --> p0, p1, ... plast, q0, q1, ...\n\ compress(data, selectors) --> (d[0] if s[0]), (d[1] if s[1]), ...\n\ -dropwhile(pred, seq) --> seq[n], seq[n+1], starting when pred fails\n\ +dropwhile(predicate, seq) --> seq[n], seq[n+1], starting when predicate fails\n\ groupby(iterable[, keyfunc]) --> sub-iterators grouped by value of keyfunc(v)\n\ -filterfalse(pred, seq) --> elements of seq where pred(elem) is False\n\ +filterfalse(predicate, seq) --> elements of seq where predicate(elem) is False\n\ islice(seq, [start,] stop [, step]) --> elements from\n\ seq[start:stop:step]\n\ pairwise(s) --> (s[0],s[1]), (s[1],s[2]), (s[2], s[3]), ...\n\ starmap(fun, seq) --> fun(*seq[0]), fun(*seq[1]), ...\n\ tee(it, n=2) --> (it1, it2 , ... itn) splits one iterator into n\n\ -takewhile(pred, seq) --> seq[0], seq[1], until pred fails\n\ +takewhile(predicate, seq) --> seq[0], seq[1], until predicate fails\n\ zip_longest(p, q, ...) --> (p[0], q[0]), (p[1], q[1]), ...\n\ \n\ Combinatoric generators:\n\ diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index a877bfcd6afb687..8ba0431f4a47b79 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -2321,6 +2321,48 @@ math_log10(PyObject *module, PyObject *x) } +/*[clinic input] +math.fma + + x: double + y: double + z: double + / + +Fused multiply-add operation. + +Compute (x * y) + z with a single round. +[clinic start generated code]*/ + +static PyObject * +math_fma_impl(PyObject *module, double x, double y, double z) +/*[clinic end generated code: output=4fc8626dbc278d17 input=e3ad1f4a4c89626e]*/ +{ + double r = fma(x, y, z); + + /* Fast path: if we got a finite result, we're done. */ + if (Py_IS_FINITE(r)) { + return PyFloat_FromDouble(r); + } + + /* Non-finite result. Raise an exception if appropriate, else return r. */ + if (Py_IS_NAN(r)) { + if (!Py_IS_NAN(x) && !Py_IS_NAN(y) && !Py_IS_NAN(z)) { + /* NaN result from non-NaN inputs. */ + PyErr_SetString(PyExc_ValueError, "invalid operation in fma"); + return NULL; + } + } + else if (Py_IS_FINITE(x) && Py_IS_FINITE(y) && Py_IS_FINITE(z)) { + /* Infinite result from finite inputs. */ + PyErr_SetString(PyExc_OverflowError, "overflow in fma"); + return NULL; + } + + return PyFloat_FromDouble(r); +} + + /*[clinic input] math.fmod @@ -4094,6 +4136,7 @@ static PyMethodDef math_methods[] = { {"fabs", math_fabs, METH_O, math_fabs_doc}, MATH_FACTORIAL_METHODDEF MATH_FLOOR_METHODDEF + MATH_FMA_METHODDEF MATH_FMOD_METHODDEF MATH_FREXP_METHODDEF MATH_FSUM_METHODDEF diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index fd70b38bddec79e..7b2d3661ee55462 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -9115,7 +9115,81 @@ os_setpgrp_impl(PyObject *module) #ifdef HAVE_GETPPID #ifdef MS_WINDOWS -#include +#include +#include + +// The structure definition in winternl.h may be incomplete. +// This structure is the full version from the MSDN documentation. +typedef struct _PROCESS_BASIC_INFORMATION_FULL { + NTSTATUS ExitStatus; + PVOID PebBaseAddress; + ULONG_PTR AffinityMask; + LONG BasePriority; + ULONG_PTR UniqueProcessId; + ULONG_PTR InheritedFromUniqueProcessId; +} PROCESS_BASIC_INFORMATION_FULL; + +typedef NTSTATUS (NTAPI *PNT_QUERY_INFORMATION_PROCESS) ( + IN HANDLE ProcessHandle, + IN PROCESSINFOCLASS ProcessInformationClass, + OUT PVOID ProcessInformation, + IN ULONG ProcessInformationLength, + OUT PULONG ReturnLength OPTIONAL); + +// This function returns the process ID of the parent process. +// Returns 0 on failure. +static ULONG +win32_getppid_fast(void) +{ + NTSTATUS status; + HMODULE ntdll; + PNT_QUERY_INFORMATION_PROCESS pNtQueryInformationProcess; + PROCESS_BASIC_INFORMATION_FULL basic_information; + static ULONG cached_ppid = 0; + + if (cached_ppid) { + // No need to query the kernel again. + return cached_ppid; + } + + ntdll = GetModuleHandleW(L"ntdll.dll"); + if (!ntdll) { + return 0; + } + + pNtQueryInformationProcess = (PNT_QUERY_INFORMATION_PROCESS) GetProcAddress(ntdll, "NtQueryInformationProcess"); + if (!pNtQueryInformationProcess) { + return 0; + } + + status = pNtQueryInformationProcess(GetCurrentProcess(), + ProcessBasicInformation, + &basic_information, + sizeof(basic_information), + NULL); + + if (!NT_SUCCESS(status)) { + return 0; + } + + // Perform sanity check on the parent process ID we received from NtQueryInformationProcess. + // The check covers values which exceed the 32-bit range (if running on x64) as well as + // zero and (ULONG) -1. + + if (basic_information.InheritedFromUniqueProcessId == 0 || + basic_information.InheritedFromUniqueProcessId >= ULONG_MAX) + { + return 0; + } + + // Now that we have reached this point, the BasicInformation.InheritedFromUniqueProcessId + // structure member contains a ULONG_PTR which represents the process ID of our parent + // process. This process ID will be correctly returned even if the parent process has + // exited or been terminated. + + cached_ppid = (ULONG) basic_information.InheritedFromUniqueProcessId; + return cached_ppid; +} static PyObject* win32_getppid(void) @@ -9123,8 +9197,16 @@ win32_getppid(void) DWORD error; PyObject* result = NULL; HANDLE process = GetCurrentProcess(); - HPSS snapshot = NULL; + ULONG pid; + + pid = win32_getppid_fast(); + if (pid != 0) { + return PyLong_FromUnsignedLong(pid); + } + + // If failure occurs in win32_getppid_fast(), fall back to using the PSS API. + error = PssCaptureSnapshot(process, PSS_CAPTURE_NONE, 0, &snapshot); if (error != ERROR_SUCCESS) { return PyErr_SetFromWindowsErr(error); @@ -9577,36 +9659,39 @@ wait_helper(PyObject *module, pid_t pid, int status, struct rusage *ru) if (!result) return NULL; + int pos = 0; + #ifndef doubletime #define doubletime(TV) ((double)(TV).tv_sec + (TV).tv_usec * 0.000001) #endif - PyStructSequence_SET_ITEM(result, 0, - PyFloat_FromDouble(doubletime(ru->ru_utime))); - PyStructSequence_SET_ITEM(result, 1, - PyFloat_FromDouble(doubletime(ru->ru_stime))); -#define SET_INT(result, index, value)\ - PyStructSequence_SET_ITEM(result, index, PyLong_FromLong(value)) - SET_INT(result, 2, ru->ru_maxrss); - SET_INT(result, 3, ru->ru_ixrss); - SET_INT(result, 4, ru->ru_idrss); - SET_INT(result, 5, ru->ru_isrss); - SET_INT(result, 6, ru->ru_minflt); - SET_INT(result, 7, ru->ru_majflt); - SET_INT(result, 8, ru->ru_nswap); - SET_INT(result, 9, ru->ru_inblock); - SET_INT(result, 10, ru->ru_oublock); - SET_INT(result, 11, ru->ru_msgsnd); - SET_INT(result, 12, ru->ru_msgrcv); - SET_INT(result, 13, ru->ru_nsignals); - SET_INT(result, 14, ru->ru_nvcsw); - SET_INT(result, 15, ru->ru_nivcsw); -#undef SET_INT - - if (PyErr_Occurred()) { - Py_DECREF(result); - return NULL; - } +#define SET_RESULT(CALL) \ + do { \ + PyObject *item = (CALL); \ + if (item == NULL) { \ + Py_DECREF(result); \ + return NULL; \ + } \ + PyStructSequence_SET_ITEM(result, pos++, item); \ + } while(0) + + SET_RESULT(PyFloat_FromDouble(doubletime(ru->ru_utime))); + SET_RESULT(PyFloat_FromDouble(doubletime(ru->ru_stime))); + SET_RESULT(PyLong_FromLong(ru->ru_maxrss)); + SET_RESULT(PyLong_FromLong(ru->ru_ixrss)); + SET_RESULT(PyLong_FromLong(ru->ru_idrss)); + SET_RESULT(PyLong_FromLong(ru->ru_isrss)); + SET_RESULT(PyLong_FromLong(ru->ru_minflt)); + SET_RESULT(PyLong_FromLong(ru->ru_majflt)); + SET_RESULT(PyLong_FromLong(ru->ru_nswap)); + SET_RESULT(PyLong_FromLong(ru->ru_inblock)); + SET_RESULT(PyLong_FromLong(ru->ru_oublock)); + SET_RESULT(PyLong_FromLong(ru->ru_msgsnd)); + SET_RESULT(PyLong_FromLong(ru->ru_msgrcv)); + SET_RESULT(PyLong_FromLong(ru->ru_nsignals)); + SET_RESULT(PyLong_FromLong(ru->ru_nvcsw)); + SET_RESULT(PyLong_FromLong(ru->ru_nivcsw)); +#undef SET_RESULT return Py_BuildValue("NiN", PyLong_FromPid(pid), status, result); } @@ -9729,15 +9814,25 @@ os_waitid_impl(PyObject *module, idtype_t idtype, id_t id, int options) if (!result) return NULL; - PyStructSequence_SET_ITEM(result, 0, PyLong_FromPid(si.si_pid)); - PyStructSequence_SET_ITEM(result, 1, _PyLong_FromUid(si.si_uid)); - PyStructSequence_SET_ITEM(result, 2, PyLong_FromLong((long)(si.si_signo))); - PyStructSequence_SET_ITEM(result, 3, PyLong_FromLong((long)(si.si_status))); - PyStructSequence_SET_ITEM(result, 4, PyLong_FromLong((long)(si.si_code))); - if (PyErr_Occurred()) { - Py_DECREF(result); - return NULL; - } + int pos = 0; + +#define SET_RESULT(CALL) \ + do { \ + PyObject *item = (CALL); \ + if (item == NULL) { \ + Py_DECREF(result); \ + return NULL; \ + } \ + PyStructSequence_SET_ITEM(result, pos++, item); \ + } while(0) + + SET_RESULT(PyLong_FromPid(si.si_pid)); + SET_RESULT(_PyLong_FromUid(si.si_uid)); + SET_RESULT(PyLong_FromLong((long)(si.si_signo))); + SET_RESULT(PyLong_FromLong((long)(si.si_status))); + SET_RESULT(PyLong_FromLong((long)(si.si_code))); + +#undef SET_RESULT return result; } @@ -10470,30 +10565,40 @@ os.timerfd_settime * flags: int = 0 0 or a bit mask of TFD_TIMER_ABSTIME or TFD_TIMER_CANCEL_ON_SET. - initial: double = 0.0 + initial as initial_double: double = 0.0 The initial expiration time, in seconds. - interval: double = 0.0 + interval as interval_double: double = 0.0 The timer's interval, in seconds. Alter a timer file descriptor's internal timer in seconds. [clinic start generated code]*/ static PyObject * -os_timerfd_settime_impl(PyObject *module, int fd, int flags, double initial, - double interval) -/*[clinic end generated code: output=0dda31115317adb9 input=6c24e47e7a4d799e]*/ +os_timerfd_settime_impl(PyObject *module, int fd, int flags, + double initial_double, double interval_double) +/*[clinic end generated code: output=df4c1bce6859224e input=81d2c0d7e936e8a7]*/ { - struct itimerspec new_value; - struct itimerspec old_value; - int result; - if (_PyTime_AsTimespec(_PyTime_FromSecondsDouble(initial, _PyTime_ROUND_FLOOR), &new_value.it_value) < 0) { + PyTime_t initial, interval; + if (_PyTime_FromSecondsDouble(initial_double, _PyTime_ROUND_FLOOR, + &initial) < 0) { + return NULL; + } + if (_PyTime_FromSecondsDouble(interval_double, _PyTime_ROUND_FLOOR, + &interval) < 0) { + return NULL; + } + + struct itimerspec new_value, old_value; + if (_PyTime_AsTimespec(initial, &new_value.it_value) < 0) { PyErr_SetString(PyExc_ValueError, "invalid initial value"); return NULL; } - if (_PyTime_AsTimespec(_PyTime_FromSecondsDouble(interval, _PyTime_ROUND_FLOOR), &new_value.it_interval) < 0) { + if (_PyTime_AsTimespec(interval, &new_value.it_interval) < 0) { PyErr_SetString(PyExc_ValueError, "invalid interval value"); return NULL; } + + int result; Py_BEGIN_ALLOW_THREADS result = timerfd_settime(fd, flags, &new_value, &old_value); Py_END_ALLOW_THREADS @@ -12957,46 +13062,50 @@ _pystatvfs_fromstructstatvfs(PyObject *module, struct statvfs st) { if (v == NULL) return NULL; + int pos = 0; + +#define SET_RESULT(CALL) \ + do { \ + PyObject *item = (CALL); \ + if (item == NULL) { \ + Py_DECREF(v); \ + return NULL; \ + } \ + PyStructSequence_SET_ITEM(v, pos++, item); \ + } while(0) + #if !defined(HAVE_LARGEFILE_SUPPORT) - PyStructSequence_SET_ITEM(v, 0, PyLong_FromLong((long) st.f_bsize)); - PyStructSequence_SET_ITEM(v, 1, PyLong_FromLong((long) st.f_frsize)); - PyStructSequence_SET_ITEM(v, 2, PyLong_FromLong((long) st.f_blocks)); - PyStructSequence_SET_ITEM(v, 3, PyLong_FromLong((long) st.f_bfree)); - PyStructSequence_SET_ITEM(v, 4, PyLong_FromLong((long) st.f_bavail)); - PyStructSequence_SET_ITEM(v, 5, PyLong_FromLong((long) st.f_files)); - PyStructSequence_SET_ITEM(v, 6, PyLong_FromLong((long) st.f_ffree)); - PyStructSequence_SET_ITEM(v, 7, PyLong_FromLong((long) st.f_favail)); - PyStructSequence_SET_ITEM(v, 8, PyLong_FromLong((long) st.f_flag)); - PyStructSequence_SET_ITEM(v, 9, PyLong_FromLong((long) st.f_namemax)); + SET_RESULT(PyLong_FromLong((long) st.f_bsize)); + SET_RESULT(PyLong_FromLong((long) st.f_frsize)); + SET_RESULT(PyLong_FromLong((long) st.f_blocks)); + SET_RESULT(PyLong_FromLong((long) st.f_bfree)); + SET_RESULT(PyLong_FromLong((long) st.f_bavail)); + SET_RESULT(PyLong_FromLong((long) st.f_files)); + SET_RESULT(PyLong_FromLong((long) st.f_ffree)); + SET_RESULT(PyLong_FromLong((long) st.f_favail)); + SET_RESULT(PyLong_FromLong((long) st.f_flag)); + SET_RESULT(PyLong_FromLong((long) st.f_namemax)); #else - PyStructSequence_SET_ITEM(v, 0, PyLong_FromLong((long) st.f_bsize)); - PyStructSequence_SET_ITEM(v, 1, PyLong_FromLong((long) st.f_frsize)); - PyStructSequence_SET_ITEM(v, 2, - PyLong_FromLongLong((long long) st.f_blocks)); - PyStructSequence_SET_ITEM(v, 3, - PyLong_FromLongLong((long long) st.f_bfree)); - PyStructSequence_SET_ITEM(v, 4, - PyLong_FromLongLong((long long) st.f_bavail)); - PyStructSequence_SET_ITEM(v, 5, - PyLong_FromLongLong((long long) st.f_files)); - PyStructSequence_SET_ITEM(v, 6, - PyLong_FromLongLong((long long) st.f_ffree)); - PyStructSequence_SET_ITEM(v, 7, - PyLong_FromLongLong((long long) st.f_favail)); - PyStructSequence_SET_ITEM(v, 8, PyLong_FromLong((long) st.f_flag)); - PyStructSequence_SET_ITEM(v, 9, PyLong_FromLong((long) st.f_namemax)); + SET_RESULT(PyLong_FromLong((long) st.f_bsize)); + SET_RESULT(PyLong_FromLong((long) st.f_frsize)); + SET_RESULT(PyLong_FromLongLong((long long) st.f_blocks)); + SET_RESULT(PyLong_FromLongLong((long long) st.f_bfree)); + SET_RESULT(PyLong_FromLongLong((long long) st.f_bavail)); + SET_RESULT(PyLong_FromLongLong((long long) st.f_files)); + SET_RESULT(PyLong_FromLongLong((long long) st.f_ffree)); + SET_RESULT(PyLong_FromLongLong((long long) st.f_favail)); + SET_RESULT(PyLong_FromLong((long) st.f_flag)); + SET_RESULT(PyLong_FromLong((long) st.f_namemax)); #endif /* The _ALL_SOURCE feature test macro defines f_fsid as a structure * (issue #32390). */ #if defined(_AIX) && defined(_ALL_SOURCE) - PyStructSequence_SET_ITEM(v, 10, PyLong_FromUnsignedLong(st.f_fsid.val[0])); + SET_RESULT(PyLong_FromUnsignedLong(st.f_fsid.val[0])); #else - PyStructSequence_SET_ITEM(v, 10, PyLong_FromUnsignedLong(st.f_fsid)); + SET_RESULT(PyLong_FromUnsignedLong(st.f_fsid)); #endif - if (PyErr_Occurred()) { - Py_DECREF(v); - return NULL; - } + +#undef SET_RESULT return v; } @@ -14968,12 +15077,23 @@ os_get_terminal_size_impl(PyObject *module, int fd) termsize = PyStructSequence_New((PyTypeObject *)TerminalSizeType); if (termsize == NULL) return NULL; - PyStructSequence_SET_ITEM(termsize, 0, PyLong_FromLong(columns)); - PyStructSequence_SET_ITEM(termsize, 1, PyLong_FromLong(lines)); - if (PyErr_Occurred()) { - Py_DECREF(termsize); - return NULL; - } + + int pos = 0; + +#define SET_TERMSIZE(CALL) \ + do { \ + PyObject *item = (CALL); \ + if (item == NULL) { \ + Py_DECREF(termsize); \ + return NULL; \ + } \ + PyStructSequence_SET_ITEM(termsize, pos++, item); \ + } while(0) + + SET_TERMSIZE(PyLong_FromLong(columns)); + SET_TERMSIZE(PyLong_FromLong(lines)); +#undef SET_TERMSIZE + return termsize; } #endif /* defined(TERMSIZE_USE_CONIO) || defined(TERMSIZE_USE_IOCTL) */ @@ -17553,11 +17673,11 @@ posixmodule_exec(PyObject *m) return -1; } - if (PyDict_DelItemString(dct, "pwritev") == -1) { - PyErr_Clear(); + if (PyDict_PopString(dct, "pwritev", NULL) < 0) { + return -1; } - if (PyDict_DelItemString(dct, "preadv") == -1) { - PyErr_Clear(); + if (PyDict_PopString(dct, "preadv", NULL) < 0) { + return -1; } } #endif diff --git a/Modules/posixmodule.h b/Modules/posixmodule.h index 8827ce153fed8c5..be732db04faf945 100644 --- a/Modules/posixmodule.h +++ b/Modules/posixmodule.h @@ -2,7 +2,6 @@ #ifndef Py_POSIXMODULE_H #define Py_POSIXMODULE_H -#ifndef Py_LIMITED_API #ifdef __cplusplus extern "C" { #endif @@ -34,5 +33,4 @@ extern int _Py_Sigset_Converter(PyObject *, void *); #ifdef __cplusplus } #endif -#endif // !Py_LIMITED_API #endif // !Py_POSIXMODULE_H diff --git a/Modules/pwdmodule.c b/Modules/pwdmodule.c index b7034369c4731ee..f58735aff997992 100644 --- a/Modules/pwdmodule.c +++ b/Modules/pwdmodule.c @@ -1,9 +1,16 @@ /* UNIX password file access module */ +// Need limited C API version 3.13 for PyMem_RawRealloc() +#include "pyconfig.h" // Py_GIL_DISABLED +#ifndef Py_GIL_DISABLED +# define Py_LIMITED_API 0x030d0000 +#endif + #include "Python.h" #include "posixmodule.h" +#include // ERANGE #include // getpwuid() #include // sysconf() @@ -64,53 +71,52 @@ static struct PyModuleDef pwdmodule; #define DEFAULT_BUFFER_SIZE 1024 -static void -sets(PyObject *v, int i, const char* val) -{ - if (val) { - PyObject *o = PyUnicode_DecodeFSDefault(val); - PyStructSequence_SET_ITEM(v, i, o); - } - else { - PyStructSequence_SET_ITEM(v, i, Py_None); - Py_INCREF(Py_None); - } -} - static PyObject * mkpwent(PyObject *module, struct passwd *p) { - int setIndex = 0; PyObject *v = PyStructSequence_New(get_pwd_state(module)->StructPwdType); - if (v == NULL) + if (v == NULL) { return NULL; + } -#define SETS(i,val) sets(v, i, val) + int setIndex = 0; + +#define SET_STRING(VAL) \ + SET_RESULT((VAL) ? PyUnicode_DecodeFSDefault((VAL)) : Py_NewRef(Py_None)) - SETS(setIndex++, p->pw_name); +#define SET_RESULT(CALL) \ + do { \ + PyObject *item = (CALL); \ + if (item == NULL) { \ + goto error; \ + } \ + PyStructSequence_SetItem(v, setIndex++, item); \ + } while(0) + + SET_STRING(p->pw_name); #if defined(HAVE_STRUCT_PASSWD_PW_PASSWD) && !defined(__ANDROID__) - SETS(setIndex++, p->pw_passwd); + SET_STRING(p->pw_passwd); #else - SETS(setIndex++, ""); + SET_STRING(""); #endif - PyStructSequence_SET_ITEM(v, setIndex++, _PyLong_FromUid(p->pw_uid)); - PyStructSequence_SET_ITEM(v, setIndex++, _PyLong_FromGid(p->pw_gid)); + SET_RESULT(_PyLong_FromUid(p->pw_uid)); + SET_RESULT(_PyLong_FromGid(p->pw_gid)); #if defined(HAVE_STRUCT_PASSWD_PW_GECOS) - SETS(setIndex++, p->pw_gecos); + SET_STRING(p->pw_gecos); #else - SETS(setIndex++, ""); + SET_STRING(""); #endif - SETS(setIndex++, p->pw_dir); - SETS(setIndex++, p->pw_shell); + SET_STRING(p->pw_dir); + SET_STRING(p->pw_shell); -#undef SETS - - if (PyErr_Occurred()) { - Py_XDECREF(v); - return NULL; - } +#undef SET_STRING +#undef SET_RESULT return v; + +error: + Py_DECREF(v); + return NULL; } /*[clinic input] diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c index 62cd262a7885e93..f04f96bc2f7601c 100644 --- a/Modules/pyexpat.c +++ b/Modules/pyexpat.c @@ -7,6 +7,7 @@ #include "pycore_pyhash.h" // _Py_HashSecret #include "pycore_traceback.h" // _PyTraceback_Add() +#include #include // offsetof() #include "expat.h" #include "pyexpat.h" @@ -81,6 +82,12 @@ typedef struct { /* NULL if not enabled */ int buffer_size; /* Size of buffer, in XML_Char units */ int buffer_used; /* Buffer units in use */ + bool reparse_deferral_enabled; /* Whether to defer reparsing of + unfinished XML tokens; a de-facto cache of + what Expat has the authority on, for lack + of a getter API function + "XML_GetReparseDeferralEnabled" in Expat + 2.6.0 */ PyObject *intern; /* Dictionary to intern strings */ PyObject **handlers; } xmlparseobject; @@ -703,6 +710,40 @@ get_parse_result(pyexpat_state *state, xmlparseobject *self, int rv) #define MAX_CHUNK_SIZE (1 << 20) +/*[clinic input] +pyexpat.xmlparser.SetReparseDeferralEnabled + + enabled: bool + / + +Enable/Disable reparse deferral; enabled by default with Expat >=2.6.0. +[clinic start generated code]*/ + +static PyObject * +pyexpat_xmlparser_SetReparseDeferralEnabled_impl(xmlparseobject *self, + int enabled) +/*[clinic end generated code: output=5ec539e3b63c8c49 input=021eb9e0bafc32c5]*/ +{ +#if XML_COMBINED_VERSION >= 20600 + XML_SetReparseDeferralEnabled(self->itself, enabled ? XML_TRUE : XML_FALSE); + self->reparse_deferral_enabled = (bool)enabled; +#endif + Py_RETURN_NONE; +} + +/*[clinic input] +pyexpat.xmlparser.GetReparseDeferralEnabled + +Retrieve reparse deferral enabled status; always returns false with Expat <2.6.0. +[clinic start generated code]*/ + +static PyObject * +pyexpat_xmlparser_GetReparseDeferralEnabled_impl(xmlparseobject *self) +/*[clinic end generated code: output=4e91312e88a595a8 input=54b5f11d32b20f3e]*/ +{ + return PyBool_FromLong(self->reparse_deferral_enabled); +} + /*[clinic input] pyexpat.xmlparser.Parse @@ -1063,6 +1104,8 @@ static struct PyMethodDef xmlparse_methods[] = { #if XML_COMBINED_VERSION >= 19505 PYEXPAT_XMLPARSER_USEFOREIGNDTD_METHODDEF #endif + PYEXPAT_XMLPARSER_SETREPARSEDEFERRALENABLED_METHODDEF + PYEXPAT_XMLPARSER_GETREPARSEDEFERRALENABLED_METHODDEF {NULL, NULL} /* sentinel */ }; @@ -1158,6 +1201,11 @@ newxmlparseobject(pyexpat_state *state, const char *encoding, self->ns_prefixes = 0; self->handlers = NULL; self->intern = Py_XNewRef(intern); +#if XML_COMBINED_VERSION >= 20600 + self->reparse_deferral_enabled = true; +#else + self->reparse_deferral_enabled = false; +#endif /* namespace_separator is either NULL or contains one char + \0 */ self->itself = XML_ParserCreate_MM(encoding, &ExpatMemoryHandler, @@ -2019,6 +2067,11 @@ pyexpat_exec(PyObject *mod) #else capi->SetHashSalt = NULL; #endif +#if XML_COMBINED_VERSION >= 20600 + capi->SetReparseDeferralEnabled = XML_SetReparseDeferralEnabled; +#else + capi->SetReparseDeferralEnabled = NULL; +#endif /* export using capsule */ PyObject *capi_object = PyCapsule_New(capi, PyExpat_CAPSULE_NAME, diff --git a/Modules/resource.c b/Modules/resource.c index 19020b8cc1b6db2..8ee07bd0c8054c1 100644 --- a/Modules/resource.c +++ b/Modules/resource.c @@ -1,8 +1,7 @@ +// Need limited C API version 3.13 for PySys_Audit() #include "pyconfig.h" // Py_GIL_DISABLED - #ifndef Py_GIL_DISABLED -// Need limited C API version 3.13 for PySys_Audit() -#define Py_LIMITED_API 0x030d0000 +# define Py_LIMITED_API 0x030d0000 #endif #include "Python.h" diff --git a/Modules/rotatingtree.c b/Modules/rotatingtree.c index 07e08bc3167c0a4..217e495b3d2a9d9 100644 --- a/Modules/rotatingtree.c +++ b/Modules/rotatingtree.c @@ -1,3 +1,9 @@ +#ifndef Py_BUILD_CORE_BUILTIN +# define Py_BUILD_CORE_MODULE 1 +#endif + +#include "Python.h" +#include "pycore_lock.h" #include "rotatingtree.h" #define KEY_LOWER_THAN(key1, key2) ((char*)(key1) < (char*)(key2)) @@ -10,17 +16,20 @@ static unsigned int random_value = 1; static unsigned int random_stream = 0; +static PyMutex random_mutex = {0}; static int randombits(int bits) { int result; + PyMutex_Lock(&random_mutex); if (random_stream < (1U << bits)) { random_value *= 1082527; random_stream = random_value; } result = random_stream & ((1<>= bits; + PyMutex_Unlock(&random_mutex); return result; } diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index cd9a803648be712..7720d59e46590ea 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -1054,8 +1054,8 @@ init_sockobject(socket_state *state, PySocketSockObject *s, else #endif { - s->sock_timeout = state->defaulttimeout; - if (state->defaulttimeout >= 0) { + s->sock_timeout = _Py_atomic_load_int64_relaxed(&state->defaulttimeout); + if (s->sock_timeout >= 0) { if (internal_setblocking(s, 0) == -1) { return -1; } @@ -6913,11 +6913,12 @@ static PyObject * socket_getdefaulttimeout(PyObject *self, PyObject *Py_UNUSED(ignored)) { socket_state *state = get_module_state(self); - if (state->defaulttimeout < 0) { + PyTime_t timeout = _Py_atomic_load_int64_relaxed(&state->defaulttimeout); + if (timeout < 0) { Py_RETURN_NONE; } else { - double seconds = PyTime_AsSecondsDouble(state->defaulttimeout); + double seconds = PyTime_AsSecondsDouble(timeout); return PyFloat_FromDouble(seconds); } } @@ -6938,7 +6939,7 @@ socket_setdefaulttimeout(PyObject *self, PyObject *arg) return NULL; socket_state *state = get_module_state(self); - state->defaulttimeout = timeout; + _Py_atomic_store_int64_relaxed(&state->defaulttimeout, timeout); Py_RETURN_NONE; } diff --git a/Modules/termios.c b/Modules/termios.c index 4635fefb8f3f5ae..a29474d650127f1 100644 --- a/Modules/termios.c +++ b/Modules/termios.c @@ -1,11 +1,19 @@ /* termios.c -- POSIX terminal I/O module implementation. */ -#ifndef Py_BUILD_CORE_BUILTIN -# define Py_BUILD_CORE_MODULE 1 +// Need limited C API version 3.13 for PyLong_AsInt() +// in code generated by Argument Clinic. +#include "pyconfig.h" // Py_GIL_DISABLED +#ifndef Py_GIL_DISABLED +# define Py_LIMITED_API 0x030d0000 #endif #include "Python.h" +#include // memset() +#include +#include +#include // _POSIX_VDISABLE + // On QNX 6, struct termio must be declared by including sys/termio.h // if TCGETA, TCSETA, TCSETAW, or TCSETAF are used. sys/termio.h must // be included before termios.h or it will generate an error. @@ -19,28 +27,26 @@ # define CTRL(c) ((c)&037) #endif +// We could do better. Check bpo-32660 #if defined(__sun) -/* We could do better. Check issue-32660 */ -#include -#include +# include +# include #endif -#include -#include -#include // _POSIX_VDISABLE - /* HP-UX requires that this be included to pick up MDCD, MCTS, MDSR, * MDTR, MRI, and MRTS (apparently used internally by some things * defined as macros; these are not used here directly). */ #ifdef HAVE_SYS_MODEM_H -#include +# include #endif + /* HP-UX requires that this be included to pick up TIOCGPGRP and friends */ #ifdef HAVE_SYS_BSDTTY_H -#include +# include #endif + /*[clinic input] module termios [clinic start generated code]*/ @@ -120,7 +126,7 @@ termios_tcgetattr_impl(PyObject *module, int fd) v = PyBytes_FromStringAndSize(&ch, 1); if (v == NULL) goto err; - PyList_SET_ITEM(cc, i, v); + PyList_SetItem(cc, i, v); } /* Convert the MIN and TIME slots to integer. On some systems, the @@ -154,7 +160,7 @@ termios_tcgetattr_impl(PyObject *module, int fd) Py_DECREF(v); \ goto err; \ } \ - PyList_SET_ITEM(v, index, l); \ + PyList_SetItem(v, index, l); \ } while (0) ADD_LONG_ITEM(0, mode.c_iflag); @@ -165,7 +171,7 @@ termios_tcgetattr_impl(PyObject *module, int fd) ADD_LONG_ITEM(5, ospeed); #undef ADD_LONG_ITEM - PyList_SET_ITEM(v, 6, cc); + PyList_SetItem(v, 6, cc); return v; err: Py_DECREF(cc); @@ -214,7 +220,7 @@ termios_tcsetattr_impl(PyObject *module, int fd, int when, PyObject *term) speed_t ispeed, ospeed; #define SET_FROM_LIST(TYPE, VAR, LIST, N) do { \ - PyObject *item = PyList_GET_ITEM(LIST, N); \ + PyObject *item = PyList_GetItem(LIST, N); \ long num = PyLong_AsLong(item); \ if (num == -1 && PyErr_Occurred()) { \ return NULL; \ @@ -230,7 +236,7 @@ termios_tcsetattr_impl(PyObject *module, int fd, int when, PyObject *term) SET_FROM_LIST(speed_t, ospeed, term, 5); #undef SET_FROM_LIST - PyObject *cc = PyList_GET_ITEM(term, 6); + PyObject *cc = PyList_GetItem(term, 6); if (!PyList_Check(cc) || PyList_Size(cc) != NCCS) { PyErr_Format(PyExc_TypeError, "tcsetattr: attributes[6] must be %d element list", diff --git a/Modules/timemodule.c b/Modules/timemodule.c index ed41ffd3662aa8c..2ec5aff235c2936 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -1928,20 +1928,20 @@ time_exec(PyObject *module) return -1; } - if (PyDict_DelItemString(dct, "clock_gettime") == -1) { - PyErr_Clear(); + if (PyDict_PopString(dct, "clock_gettime", NULL) < 0) { + return -1; } - if (PyDict_DelItemString(dct, "clock_gettime_ns") == -1) { - PyErr_Clear(); + if (PyDict_PopString(dct, "clock_gettime_ns", NULL) < 0) { + return -1; } - if (PyDict_DelItemString(dct, "clock_settime") == -1) { - PyErr_Clear(); + if (PyDict_PopString(dct, "clock_settime", NULL) < 0) { + return -1; } - if (PyDict_DelItemString(dct, "clock_settime_ns") == -1) { - PyErr_Clear(); + if (PyDict_PopString(dct, "clock_settime_ns", NULL) < 0) { + return -1; } - if (PyDict_DelItemString(dct, "clock_getres") == -1) { - PyErr_Clear(); + if (PyDict_PopString(dct, "clock_getres", NULL) < 0) { + return -1; } } #endif @@ -1951,11 +1951,11 @@ time_exec(PyObject *module) } else { PyObject* dct = PyModule_GetDict(module); - if (PyDict_DelItemString(dct, "thread_time") == -1) { - PyErr_Clear(); + if (PyDict_PopString(dct, "thread_time", NULL) < 0) { + return -1; } - if (PyDict_DelItemString(dct, "thread_time_ns") == -1) { - PyErr_Clear(); + if (PyDict_PopString(dct, "thread_time_ns", NULL) < 0) { + return -1; } } #endif diff --git a/Modules/xxlimited.c b/Modules/xxlimited.c index 0bb5e12d7c3dd98..3357b8076b67b11 100644 --- a/Modules/xxlimited.c +++ b/Modules/xxlimited.c @@ -62,11 +62,10 @@ pass */ +// Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED #include "pyconfig.h" // Py_GIL_DISABLED - #ifndef Py_GIL_DISABLED -// Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED -#define Py_LIMITED_API 0x030c0000 +# define Py_LIMITED_API 0x030c0000 #endif #include "Python.h" diff --git a/Modules/xxlimited_35.c b/Modules/xxlimited_35.c index 754a368f77e9401..52690d9d10a81f8 100644 --- a/Modules/xxlimited_35.c +++ b/Modules/xxlimited_35.c @@ -5,10 +5,10 @@ * See the xxlimited module for an extension module template. */ +// Test the limited C API version 3.5 #include "pyconfig.h" // Py_GIL_DISABLED - #ifndef Py_GIL_DISABLED -#define Py_LIMITED_API 0x03050000 +# define Py_LIMITED_API 0x03050000 #endif #include "Python.h" diff --git a/Objects/abstract.c b/Objects/abstract.c index 07d4b89fe188c84..8357175aa5591ea 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -1390,7 +1390,7 @@ _PyNumber_InPlacePowerNoMod(PyObject *lhs, PyObject *rhs) } UNARY_FUNC(PyNumber_Negative, nb_negative, __neg__, "unary -") -UNARY_FUNC(PyNumber_Positive, nb_positive, __pow__, "unary +") +UNARY_FUNC(PyNumber_Positive, nb_positive, __pos__, "unary +") UNARY_FUNC(PyNumber_Invert, nb_invert, __invert__, "unary ~") UNARY_FUNC(PyNumber_Absolute, nb_absolute, __abs__, "abs()") diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index acc59b926448ca4..5e3b3affbc76c53 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1729,6 +1729,10 @@ bytearray_extend(PyByteArrayObject *self, PyObject *iterable_of_ints) while ((item = PyIter_Next(it)) != NULL) { if (! _getbytevalue(item, &value)) { + if (PyErr_ExceptionMatches(PyExc_TypeError) && PyUnicode_Check(iterable_of_ints)) { + PyErr_Format(PyExc_TypeError, + "expected iterable of integers; got: 'str'"); + } Py_DECREF(item); Py_DECREF(it); Py_DECREF(bytearray_obj); diff --git a/Objects/clinic/listobject.c.h b/Objects/clinic/listobject.c.h index a61550a49b66fc8..588e021fb71fd39 100644 --- a/Objects/clinic/listobject.c.h +++ b/Objects/clinic/listobject.c.h @@ -125,29 +125,14 @@ list_append(PyListObject *self, PyObject *object) return return_value; } -PyDoc_STRVAR(py_list_extend__doc__, +PyDoc_STRVAR(list_extend__doc__, "extend($self, iterable, /)\n" "--\n" "\n" "Extend list by appending elements from the iterable."); -#define PY_LIST_EXTEND_METHODDEF \ - {"extend", (PyCFunction)py_list_extend, METH_O, py_list_extend__doc__}, - -static PyObject * -py_list_extend_impl(PyListObject *self, PyObject *iterable); - -static PyObject * -py_list_extend(PyListObject *self, PyObject *iterable) -{ - PyObject *return_value = NULL; - - Py_BEGIN_CRITICAL_SECTION2(self, iterable); - return_value = py_list_extend_impl(self, iterable); - Py_END_CRITICAL_SECTION2(); - - return return_value; -} +#define LIST_EXTEND_METHODDEF \ + {"extend", (PyCFunction)list_extend, METH_O, list_extend__doc__}, PyDoc_STRVAR(list_pop__doc__, "pop($self, index=-1, /)\n" @@ -268,7 +253,9 @@ list_sort(PyListObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject goto exit; } skip_optional_kwonly: + Py_BEGIN_CRITICAL_SECTION(self); return_value = list_sort_impl(self, keyfunc, reverse); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -452,4 +439,4 @@ list___reversed__(PyListObject *self, PyObject *Py_UNUSED(ignored)) { return list___reversed___impl(self); } -/*[clinic end generated code: output=26dfb2c9846348f9 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=854957a1d4a89bbd input=a9049054013a1b77]*/ diff --git a/Objects/clinic/setobject.c.h b/Objects/clinic/setobject.c.h index f3c96995ede60dd..3853ce3bce685b4 100644 --- a/Objects/clinic/setobject.c.h +++ b/Objects/clinic/setobject.c.h @@ -2,6 +2,7 @@ preserve [clinic start generated code]*/ +#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION() #include "pycore_modsupport.h" // _PyArg_CheckPositional() PyDoc_STRVAR(set_pop__doc__, @@ -21,7 +22,13 @@ set_pop_impl(PySetObject *so); static PyObject * set_pop(PySetObject *so, PyObject *Py_UNUSED(ignored)) { - return set_pop_impl(so); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(so); + return_value = set_pop_impl(so); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(set_update__doc__, @@ -74,7 +81,13 @@ set_copy_impl(PySetObject *so); static PyObject * set_copy(PySetObject *so, PyObject *Py_UNUSED(ignored)) { - return set_copy_impl(so); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(so); + return_value = set_copy_impl(so); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(frozenset_copy__doc__, @@ -92,7 +105,13 @@ frozenset_copy_impl(PySetObject *so); static PyObject * frozenset_copy(PySetObject *so, PyObject *Py_UNUSED(ignored)) { - return frozenset_copy_impl(so); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(so); + return_value = frozenset_copy_impl(so); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(set_clear__doc__, @@ -110,7 +129,13 @@ set_clear_impl(PySetObject *so); static PyObject * set_clear(PySetObject *so, PyObject *Py_UNUSED(ignored)) { - return set_clear_impl(so); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(so); + return_value = set_clear_impl(so); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(set_union__doc__, @@ -227,6 +252,21 @@ PyDoc_STRVAR(set_isdisjoint__doc__, #define SET_ISDISJOINT_METHODDEF \ {"isdisjoint", (PyCFunction)set_isdisjoint, METH_O, set_isdisjoint__doc__}, +static PyObject * +set_isdisjoint_impl(PySetObject *so, PyObject *other); + +static PyObject * +set_isdisjoint(PySetObject *so, PyObject *other) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION2(so, other); + return_value = set_isdisjoint_impl(so, other); + Py_END_CRITICAL_SECTION2(); + + return return_value; +} + PyDoc_STRVAR(set_difference_update__doc__, "difference_update($self, /, *others)\n" "--\n" @@ -315,6 +355,21 @@ PyDoc_STRVAR(set_symmetric_difference__doc__, #define SET_SYMMETRIC_DIFFERENCE_METHODDEF \ {"symmetric_difference", (PyCFunction)set_symmetric_difference, METH_O, set_symmetric_difference__doc__}, +static PyObject * +set_symmetric_difference_impl(PySetObject *so, PyObject *other); + +static PyObject * +set_symmetric_difference(PySetObject *so, PyObject *other) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION2(so, other); + return_value = set_symmetric_difference_impl(so, other); + Py_END_CRITICAL_SECTION2(); + + return return_value; +} + PyDoc_STRVAR(set_issubset__doc__, "issubset($self, other, /)\n" "--\n" @@ -324,6 +379,21 @@ PyDoc_STRVAR(set_issubset__doc__, #define SET_ISSUBSET_METHODDEF \ {"issubset", (PyCFunction)set_issubset, METH_O, set_issubset__doc__}, +static PyObject * +set_issubset_impl(PySetObject *so, PyObject *other); + +static PyObject * +set_issubset(PySetObject *so, PyObject *other) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION2(so, other); + return_value = set_issubset_impl(so, other); + Py_END_CRITICAL_SECTION2(); + + return return_value; +} + PyDoc_STRVAR(set_issuperset__doc__, "issuperset($self, other, /)\n" "--\n" @@ -333,6 +403,21 @@ PyDoc_STRVAR(set_issuperset__doc__, #define SET_ISSUPERSET_METHODDEF \ {"issuperset", (PyCFunction)set_issuperset, METH_O, set_issuperset__doc__}, +static PyObject * +set_issuperset_impl(PySetObject *so, PyObject *other); + +static PyObject * +set_issuperset(PySetObject *so, PyObject *other) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION2(so, other); + return_value = set_issuperset_impl(so, other); + Py_END_CRITICAL_SECTION2(); + + return return_value; +} + PyDoc_STRVAR(set_add__doc__, "add($self, object, /)\n" "--\n" @@ -344,6 +429,21 @@ PyDoc_STRVAR(set_add__doc__, #define SET_ADD_METHODDEF \ {"add", (PyCFunction)set_add, METH_O, set_add__doc__}, +static PyObject * +set_add_impl(PySetObject *so, PyObject *key); + +static PyObject * +set_add(PySetObject *so, PyObject *key) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(so); + return_value = set_add_impl(so, key); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(set___contains____doc__, "__contains__($self, object, /)\n" "--\n" @@ -353,6 +453,21 @@ PyDoc_STRVAR(set___contains____doc__, #define SET___CONTAINS___METHODDEF \ {"__contains__", (PyCFunction)set___contains__, METH_O|METH_COEXIST, set___contains____doc__}, +static PyObject * +set___contains___impl(PySetObject *so, PyObject *key); + +static PyObject * +set___contains__(PySetObject *so, PyObject *key) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(so); + return_value = set___contains___impl(so, key); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(set_remove__doc__, "remove($self, object, /)\n" "--\n" @@ -364,6 +479,21 @@ PyDoc_STRVAR(set_remove__doc__, #define SET_REMOVE_METHODDEF \ {"remove", (PyCFunction)set_remove, METH_O, set_remove__doc__}, +static PyObject * +set_remove_impl(PySetObject *so, PyObject *key); + +static PyObject * +set_remove(PySetObject *so, PyObject *key) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(so); + return_value = set_remove_impl(so, key); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(set_discard__doc__, "discard($self, object, /)\n" "--\n" @@ -376,6 +506,21 @@ PyDoc_STRVAR(set_discard__doc__, #define SET_DISCARD_METHODDEF \ {"discard", (PyCFunction)set_discard, METH_O, set_discard__doc__}, +static PyObject * +set_discard_impl(PySetObject *so, PyObject *key); + +static PyObject * +set_discard(PySetObject *so, PyObject *key) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(so); + return_value = set_discard_impl(so, key); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(set___reduce____doc__, "__reduce__($self, /)\n" "--\n" @@ -391,7 +536,13 @@ set___reduce___impl(PySetObject *so); static PyObject * set___reduce__(PySetObject *so, PyObject *Py_UNUSED(ignored)) { - return set___reduce___impl(so); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(so); + return_value = set___reduce___impl(so); + Py_END_CRITICAL_SECTION(); + + return return_value; } PyDoc_STRVAR(set___sizeof____doc__, @@ -409,6 +560,12 @@ set___sizeof___impl(PySetObject *so); static PyObject * set___sizeof__(PySetObject *so, PyObject *Py_UNUSED(ignored)) { - return set___sizeof___impl(so); + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(so); + return_value = set___sizeof___impl(so); + Py_END_CRITICAL_SECTION(); + + return return_value; } -/*[clinic end generated code: output=34a30591148da884 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=de4ee725bd29f758 input=a9049054013a1b77]*/ diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 5ae4c3dbea23800..536746ca41eed59 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -21,6 +21,7 @@ As of Python 3.6, this is compact and ordered. Basic idea is described here: | dk_log2_size | | dk_log2_index_bytes | | dk_kind | +| dk_version | | dk_usable | | dk_nentries | +---------------------+ @@ -55,7 +56,7 @@ The DictObject can be in one of two forms. Either: A combined table: ma_values == NULL, dk_refcnt == 1. - Values are stored in the me_value field of the PyDictKeysObject. + Values are stored in the me_value field of the PyDictKeyEntry. Or: A split table: ma_values != NULL, dk_refcnt >= 1 @@ -182,9 +183,6 @@ set_values(PyDictObject *mp, PyDictValues *values) _Py_atomic_store_ptr_release(&mp->ma_values, values); } -// Defined until we get QSBR -#define _PyMem_FreeQsbr PyMem_Free - #define LOCK_KEYS(keys) PyMutex_LockFlags(&keys->dk_mutex, _Py_LOCK_DONT_DETACH) #define UNLOCK_KEYS(keys) PyMutex_Unlock(&keys->dk_mutex) @@ -250,6 +248,14 @@ load_keys_nentries(PyDictObject *mp) #endif +#define STORE_KEY(ep, key) FT_ATOMIC_STORE_PTR_RELEASE(ep->me_key, key) +#define STORE_VALUE(ep, value) FT_ATOMIC_STORE_PTR_RELEASE(ep->me_value, value) +#define STORE_SPLIT_VALUE(mp, idx, value) FT_ATOMIC_STORE_PTR_RELEASE(mp->ma_values->values[idx], value) +#define STORE_HASH(ep, hash) FT_ATOMIC_STORE_SSIZE_RELAXED(ep->me_hash, hash) +#define STORE_KEYS_USABLE(keys, usable) FT_ATOMIC_STORE_SSIZE_RELAXED(keys->dk_usable, usable) +#define STORE_KEYS_NENTRIES(keys, nentries) FT_ATOMIC_STORE_SSIZE_RELAXED(keys->dk_nentries, nentries) +#define STORE_USED(mp, used) FT_ATOMIC_STORE_SSIZE_RELAXED(mp->ma_used, used) + #define PERTURB_SHIFT 5 /* @@ -797,7 +803,7 @@ free_keys_object(PyDictKeysObject *keys, bool use_qsbr) { #ifdef Py_GIL_DISABLED if (use_qsbr) { - _PyMem_FreeQsbr(keys); + _PyMem_FreeDelayed(keys); return; } #endif @@ -837,7 +843,7 @@ free_values(PyDictValues *values, bool use_qsbr) int prefix_size = DICT_VALUES_SIZE(values); #ifdef Py_GIL_DISABLED if (use_qsbr) { - _PyMem_FreeQsbr(((char *)values)-prefix_size); + _PyMem_FreeDelayed(((char *)values)-prefix_size); return; } #endif @@ -1229,10 +1235,10 @@ _Py_dict_lookup(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **valu return ix; } +#ifdef Py_GIL_DISABLED static inline void ensure_shared_on_read(PyDictObject *mp) { -#ifdef Py_GIL_DISABLED if (!_Py_IsOwnedByCurrentThread((PyObject *)mp) && !IS_DICT_SHARED(mp)) { // The first time we access a dict from a non-owning thread we mark it // as shared. This ensures that a concurrent resize operation will @@ -1244,8 +1250,8 @@ ensure_shared_on_read(PyDictObject *mp) } Py_END_CRITICAL_SECTION(); } -#endif } +#endif static inline void ensure_shared_on_resize(PyDictObject *mp) @@ -1589,19 +1595,11 @@ insertion_resize(PyInterpreterState *interp, PyDictObject *mp, int unicode) } static Py_ssize_t -insert_into_splitdictkeys(PyDictKeysObject *keys, PyObject *name) +insert_into_splitdictkeys(PyDictKeysObject *keys, PyObject *name, Py_hash_t hash) { assert(PyUnicode_CheckExact(name)); ASSERT_KEYS_LOCKED(keys); - Py_hash_t hash = unicode_get_hash(name); - if (hash == -1) { - hash = PyUnicode_Type.tp_hash(name); - if (hash == -1) { - PyErr_Clear(); - return DKIX_EMPTY; - } - } Py_ssize_t ix = unicodekeys_lookup_unicode(keys, name, hash); if (ix == DKIX_EMPTY) { if (keys->dk_usable <= 0) { @@ -1621,7 +1619,6 @@ insert_into_splitdictkeys(PyDictKeysObject *keys, PyObject *name) return ix; } - static inline int insert_combined_dict(PyInterpreterState *interp, PyDictObject *mp, Py_hash_t hash, PyObject *key, PyObject *value) @@ -1639,18 +1636,18 @@ insert_combined_dict(PyInterpreterState *interp, PyDictObject *mp, if (DK_IS_UNICODE(mp->ma_keys)) { PyDictUnicodeEntry *ep; ep = &DK_UNICODE_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries]; - ep->me_key = key; - ep->me_value = value; + STORE_KEY(ep, key); + STORE_VALUE(ep, value); } else { PyDictKeyEntry *ep; ep = &DK_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries]; - ep->me_key = key; - ep->me_hash = hash; - ep->me_value = value; + STORE_KEY(ep, key); + STORE_VALUE(ep, value); + STORE_HASH(ep, hash); } - mp->ma_keys->dk_usable--; - mp->ma_keys->dk_nentries++; + STORE_KEYS_USABLE(mp->ma_keys, mp->ma_keys->dk_usable - 1); + STORE_KEYS_NENTRIES(mp->ma_keys, mp->ma_keys->dk_nentries + 1); assert(mp->ma_keys->dk_usable >= 0); return 0; } @@ -1682,7 +1679,7 @@ insert_split_dict(PyInterpreterState *interp, PyDictObject *mp, Py_ssize_t index = keys->dk_nentries; _PyDictValues_AddToInsertionOrder(mp->ma_values, index); assert (mp->ma_values->values[index] == NULL); - mp->ma_values->values[index] = value; + STORE_SPLIT_VALUE(mp, index, value); split_keys_entry_added(keys); assert(keys->dk_usable >= 0); @@ -2013,8 +2010,8 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, } } - mp->ma_keys->dk_usable -= numentries; - mp->ma_keys->dk_nentries = numentries; + STORE_KEYS_USABLE(mp->ma_keys, mp->ma_keys->dk_usable - numentries); + STORE_KEYS_NENTRIES(mp->ma_keys, numentries); ASSERT_CONSISTENT(mp); return 0; } @@ -2507,15 +2504,15 @@ delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix, if (DK_IS_UNICODE(mp->ma_keys)) { PyDictUnicodeEntry *ep = &DK_UNICODE_ENTRIES(mp->ma_keys)[ix]; old_key = ep->me_key; - ep->me_key = NULL; - ep->me_value = NULL; + STORE_KEY(ep, NULL); + STORE_VALUE(ep, NULL); } else { PyDictKeyEntry *ep = &DK_ENTRIES(mp->ma_keys)[ix]; old_key = ep->me_key; - ep->me_key = NULL; - ep->me_value = NULL; - ep->me_hash = 0; + STORE_KEY(ep, NULL); + STORE_VALUE(ep, NULL); + STORE_HASH(ep, 0); } Py_DECREF(old_key); } @@ -4393,8 +4390,8 @@ dict_popitem_impl(PyDictObject *self) PyTuple_SET_ITEM(res, 0, key); PyTuple_SET_ITEM(res, 1, value); /* We can't dk_usable++ since there is DKIX_DUMMY in indices */ - self->ma_keys->dk_nentries = i; - self->ma_used--; + STORE_KEYS_NENTRIES(self->ma_keys, i); + STORE_USED(self, self->ma_used - 1); self->ma_version_tag = new_version; ASSERT_CONSISTENT(self); return res; @@ -5029,7 +5026,7 @@ dictiter_iternextkey(PyObject *self) PyObject *value; #ifdef Py_GIL_DISABLED - if (!dictiter_iternext_threadsafe(d, self, &value, NULL) == 0) { + if (dictiter_iternext_threadsafe(d, self, &value, NULL) < 0) { value = NULL; } #else @@ -5152,7 +5149,7 @@ dictiter_iternextvalue(PyObject *self) PyObject *value; #ifdef Py_GIL_DISABLED - if (!dictiter_iternext_threadsafe(d, self, NULL, &value) == 0) { + if (dictiter_iternext_threadsafe(d, self, NULL, &value) < 0) { value = NULL; } #else @@ -6685,8 +6682,25 @@ _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values, assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT); Py_ssize_t ix = DKIX_EMPTY; if (PyUnicode_CheckExact(name)) { - LOCK_KEYS(keys); - ix = insert_into_splitdictkeys(keys, name); + Py_hash_t hash = unicode_get_hash(name); + if (hash == -1) { + hash = PyUnicode_Type.tp_hash(name); + assert(hash != -1); + } + +#ifdef Py_GIL_DISABLED + // Try a thread-safe lookup to see if the index is already allocated + ix = unicodekeys_lookup_unicode_threadsafe(keys, name, hash); + if (ix == DKIX_EMPTY || ix == DKIX_KEY_CHANGED) { + // Lock keys and do insert + LOCK_KEYS(keys); + ix = insert_into_splitdictkeys(keys, name, hash); + UNLOCK_KEYS(keys); + } +#else + ix = insert_into_splitdictkeys(keys, name, hash); +#endif + #ifdef Py_STATS if (ix == DKIX_EMPTY) { if (PyUnicode_CheckExact(name)) { @@ -6702,7 +6716,6 @@ _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values, } } #endif - UNLOCK_KEYS(keys); } if (ix == DKIX_EMPTY) { PyObject *dict = make_dict_from_instance_attributes( diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 37d2d312a6a0b78..96227f2cf7d76fa 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -98,10 +98,18 @@ PyFloat_GetInfo(void) return NULL; } -#define SetIntFlag(flag) \ - PyStructSequence_SET_ITEM(floatinfo, pos++, PyLong_FromLong(flag)) -#define SetDblFlag(flag) \ - PyStructSequence_SET_ITEM(floatinfo, pos++, PyFloat_FromDouble(flag)) +#define SetFlag(CALL) \ + do { \ + PyObject *flag = (CALL); \ + if (flag == NULL) { \ + Py_CLEAR(floatinfo); \ + return NULL; \ + } \ + PyStructSequence_SET_ITEM(floatinfo, pos++, flag); \ + } while (0) + +#define SetIntFlag(FLAG) SetFlag(PyLong_FromLong((FLAG))) +#define SetDblFlag(FLAG) SetFlag(PyFloat_FromDouble((FLAG))) SetDblFlag(DBL_MAX); SetIntFlag(DBL_MAX_EXP); @@ -116,11 +124,8 @@ PyFloat_GetInfo(void) SetIntFlag(FLT_ROUNDS); #undef SetIntFlag #undef SetDblFlag +#undef SetFlag - if (PyErr_Occurred()) { - Py_CLEAR(floatinfo); - return NULL; - } return floatinfo; } diff --git a/Objects/listobject.c b/Objects/listobject.c index 2bb7d4ec3424510..6f919ce02b3ce28 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -3,6 +3,7 @@ #include "Python.h" #include "pycore_abstract.h" // _PyIndex_Check() #include "pycore_ceval.h" // _PyEval_GetBuiltin() +#include "pycore_dict.h" // _PyDictViewObject #include "pycore_pyatomic_ft_wrappers.h" #include "pycore_interp.h" // PyInterpreterState.list #include "pycore_list.h" // struct _Py_list_freelist, _PyListIterObject @@ -10,6 +11,7 @@ #include "pycore_modsupport.h" // _PyArg_NoKwnames() #include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats() #include "pycore_tuple.h" // _PyTuple_FromArray() +#include "pycore_setobject.h" // _PySet_NextEntry() #include /*[clinic input] @@ -31,6 +33,50 @@ get_list_freelist(void) } #endif +#ifdef Py_GIL_DISABLED +typedef struct { + Py_ssize_t allocated; + PyObject *ob_item[]; +} _PyListArray; + +static _PyListArray * +list_allocate_array(size_t capacity) +{ + if (capacity > PY_SSIZE_T_MAX/sizeof(PyObject*) - 1) { + return NULL; + } + _PyListArray *array = PyMem_Malloc(sizeof(_PyListArray) + capacity * sizeof(PyObject *)); + if (array == NULL) { + return NULL; + } + array->allocated = capacity; + return array; +} + +static Py_ssize_t +list_capacity(PyObject **items) +{ + _PyListArray *array = _Py_CONTAINER_OF(items, _PyListArray, ob_item); + return array->allocated; +} +#endif + +static void +free_list_items(PyObject** items, bool use_qsbr) +{ +#ifdef Py_GIL_DISABLED + _PyListArray *array = _Py_CONTAINER_OF(items, _PyListArray, ob_item); + if (use_qsbr) { + _PyMem_FreeDelayed(array); + } + else { + PyMem_Free(array); + } +#else + PyMem_Free(items); +#endif +} + /* Ensure ob_item has room for at least newsize elements, and set * ob_size to newsize. If newsize > ob_size on entry, the content * of the new slots at exit is undefined heap trash; it's the caller's @@ -47,8 +93,7 @@ get_list_freelist(void) static int list_resize(PyListObject *self, Py_ssize_t newsize) { - PyObject **items; - size_t new_allocated, num_allocated_bytes; + size_t new_allocated, target_bytes; Py_ssize_t allocated = self->allocated; /* Bypass realloc() when a previous overallocation is large enough @@ -80,9 +125,34 @@ list_resize(PyListObject *self, Py_ssize_t newsize) if (newsize == 0) new_allocated = 0; + +#ifdef Py_GIL_DISABLED + _PyListArray *array = list_allocate_array(new_allocated); + if (array == NULL) { + PyErr_NoMemory(); + return -1; + } + PyObject **old_items = self->ob_item; + if (self->ob_item) { + if (new_allocated < (size_t)allocated) { + target_bytes = new_allocated * sizeof(PyObject*); + } + else { + target_bytes = allocated * sizeof(PyObject*); + } + memcpy(array->ob_item, self->ob_item, target_bytes); + } + _Py_atomic_store_ptr_release(&self->ob_item, &array->ob_item); + self->allocated = new_allocated; + Py_SET_SIZE(self, newsize); + if (old_items != NULL) { + free_list_items(old_items, _PyObject_GC_IS_SHARED(self)); + } +#else + PyObject **items; if (new_allocated <= (size_t)PY_SSIZE_T_MAX / sizeof(PyObject *)) { - num_allocated_bytes = new_allocated * sizeof(PyObject *); - items = (PyObject **)PyMem_Realloc(self->ob_item, num_allocated_bytes); + target_bytes = new_allocated * sizeof(PyObject *); + items = (PyObject **)PyMem_Realloc(self->ob_item, target_bytes); } else { // integer overflow @@ -95,12 +165,14 @@ list_resize(PyListObject *self, Py_ssize_t newsize) self->ob_item = items; Py_SET_SIZE(self, newsize); self->allocated = new_allocated; +#endif return 0; } static int list_preallocate_exact(PyListObject *self, Py_ssize_t size) { + PyObject **items; assert(self->ob_item == NULL); assert(size > 0); @@ -110,11 +182,20 @@ list_preallocate_exact(PyListObject *self, Py_ssize_t size) * allocated size up to the nearest even number. */ size = (size + 1) & ~(size_t)1; - PyObject **items = PyMem_New(PyObject*, size); +#ifdef Py_GIL_DISABLED + _PyListArray *array = list_allocate_array(size); + if (array == NULL) { + PyErr_NoMemory(); + return -1; + } + items = array->ob_item; +#else + items = PyMem_New(PyObject*, size); if (items == NULL) { PyErr_NoMemory(); return -1; } +#endif self->ob_item = items; self->allocated = size; return 0; @@ -178,7 +259,17 @@ PyList_New(Py_ssize_t size) op->ob_item = NULL; } else { +#ifdef Py_GIL_DISABLED + _PyListArray *array = list_allocate_array(size); + if (array == NULL) { + Py_DECREF(op); + return PyErr_NoMemory(); + } + memset(&array->ob_item, 0, size * sizeof(PyObject *)); + op->ob_item = array->ob_item; +#else op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *)); +#endif if (op->ob_item == NULL) { Py_DECREF(op); return PyErr_NoMemory(); @@ -199,11 +290,20 @@ list_new_prealloc(Py_ssize_t size) return NULL; } assert(op->ob_item == NULL); +#ifdef Py_GIL_DISABLED + _PyListArray *array = list_allocate_array(size); + if (array == NULL) { + Py_DECREF(op); + return PyErr_NoMemory(); + } + op->ob_item = array->ob_item; +#else op->ob_item = PyMem_New(PyObject *, size); if (op->ob_item == NULL) { Py_DECREF(op); return PyErr_NoMemory(); } +#endif op->allocated = size; return (PyObject *) op; } @@ -233,6 +333,63 @@ valid_index(Py_ssize_t i, Py_ssize_t limit) return (size_t) i < (size_t) limit; } +#ifdef Py_GIL_DISABLED + +static PyObject * +list_item_impl(PyListObject *self, Py_ssize_t idx) +{ + PyObject *item = NULL; + Py_BEGIN_CRITICAL_SECTION(self); + if (!_PyObject_GC_IS_SHARED(self)) { + _PyObject_GC_SET_SHARED(self); + } + Py_ssize_t size = Py_SIZE(self); + if (!valid_index(idx, size)) { + goto exit; + } + item = Py_NewRef(self->ob_item[idx]); +exit: + Py_END_CRITICAL_SECTION(); + return item; +} + +static inline PyObject* +list_get_item_ref(PyListObject *op, Py_ssize_t i) +{ + if (!_Py_IsOwnedByCurrentThread((PyObject *)op) && !_PyObject_GC_IS_SHARED(op)) { + return list_item_impl(op, i); + } + // Need atomic operation for the getting size. + Py_ssize_t size = PyList_GET_SIZE(op); + if (!valid_index(i, size)) { + return NULL; + } + PyObject **ob_item = _Py_atomic_load_ptr(&op->ob_item); + if (ob_item == NULL) { + return NULL; + } + Py_ssize_t cap = list_capacity(ob_item); + assert(cap != -1 && cap >= size); + if (!valid_index(i, cap)) { + return NULL; + } + PyObject *item = _Py_TryXGetRef(&ob_item[i]); + if (item == NULL) { + return list_item_impl(op, i); + } + return item; +} +#else +static inline PyObject* +list_get_item_ref(PyListObject *op, Py_ssize_t i) +{ + if (!valid_index(i, Py_SIZE(op))) { + return NULL; + } + return Py_NewRef(PyList_GET_ITEM(op, i)); +} +#endif + PyObject * PyList_GetItem(PyObject *op, Py_ssize_t i) { @@ -255,21 +412,13 @@ PyList_GetItemRef(PyObject *op, Py_ssize_t i) PyErr_SetString(PyExc_TypeError, "expected a list"); return NULL; } - if (!valid_index(i, Py_SIZE(op))) { + PyObject *item = list_get_item_ref((PyListObject *)op, i); + if (item == NULL) { _Py_DECLARE_STR(list_err, "list index out of range"); PyErr_SetObject(PyExc_IndexError, &_Py_STR(list_err)); return NULL; } - return Py_NewRef(PyList_GET_ITEM(op, i)); -} - -static inline PyObject* -list_get_item_ref(PyListObject *op, Py_ssize_t i) -{ - if (!valid_index(i, Py_SIZE(op))) { - return NULL; - } - return Py_NewRef(PyList_GET_ITEM(op, i)); + return item; } int @@ -389,7 +538,7 @@ list_dealloc(PyObject *self) while (--i >= 0) { Py_XDECREF(op->ob_item[i]); } - PyMem_Free(op->ob_item); + free_list_items(op->ob_item, false); } #ifdef WITH_FREELISTS struct _Py_list_freelist *list_freelist = get_list_freelist(); @@ -502,11 +651,20 @@ list_item(PyObject *aa, Py_ssize_t i) PyErr_SetObject(PyExc_IndexError, &_Py_STR(list_err)); return NULL; } - return Py_NewRef(a->ob_item[i]); + PyObject *item; + Py_BEGIN_CRITICAL_SECTION(a); +#ifdef Py_GIL_DISABLED + if (!_Py_IsOwnedByCurrentThread((PyObject *)a) && !_PyObject_GC_IS_SHARED(a)) { + _PyObject_GC_SET_SHARED(a); + } +#endif + item = Py_NewRef(a->ob_item[i]); + Py_END_CRITICAL_SECTION(); + return item; } static PyObject * -list_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh) +list_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh) { PyListObject *np; PyObject **src, **dest; @@ -550,7 +708,7 @@ PyList_GetSlice(PyObject *a, Py_ssize_t ilow, Py_ssize_t ihigh) else if (ihigh > Py_SIZE(a)) { ihigh = Py_SIZE(a); } - ret = list_slice((PyListObject *)a, ilow, ihigh); + ret = list_slice_lock_held((PyListObject *)a, ilow, ihigh); Py_END_CRITICAL_SECTION(); return ret; } @@ -575,13 +733,13 @@ list_concat_lock_held(PyListObject *a, PyListObject *b) dest = np->ob_item; for (i = 0; i < Py_SIZE(a); i++) { PyObject *v = src[i]; - FT_ATOMIC_STORE_PTR_RELAXED(dest[i], Py_NewRef(v)); + dest[i] = Py_NewRef(v); } src = b->ob_item; dest = np->ob_item + Py_SIZE(a); for (i = 0; i < Py_SIZE(b); i++) { PyObject *v = src[i]; - FT_ATOMIC_STORE_PTR_RELAXED(dest[i], Py_NewRef(v)); + dest[i] = Py_NewRef(v); } Py_SET_SIZE(np, size); return (PyObject *)np; @@ -627,7 +785,7 @@ list_repeat_lock_held(PyListObject *a, Py_ssize_t n) _Py_RefcntAdd(elem, n); PyObject **dest_end = dest + output_size; while (dest < dest_end) { - FT_ATOMIC_STORE_PTR_RELAXED(*dest++, elem); + *dest++ = elem; } } else { @@ -635,7 +793,7 @@ list_repeat_lock_held(PyListObject *a, Py_ssize_t n) PyObject **src_end = src + input_size; while (src < src_end) { _Py_RefcntAdd(*src, n); - FT_ATOMIC_STORE_PTR_RELAXED(*dest++, *src++); + *dest++ = *src++; } _Py_memory_repeat((char *)np->ob_item, sizeof(PyObject *)*output_size, @@ -658,7 +816,7 @@ list_repeat(PyObject *aa, Py_ssize_t n) } static void -list_clear(PyListObject *a) +list_clear_impl(PyListObject *a, bool is_resize) { PyObject **items = a->ob_item; if (items == NULL) { @@ -674,17 +832,26 @@ list_clear(PyListObject *a) while (--i >= 0) { Py_XDECREF(items[i]); } - // TODO: Use QSBR technique, if the list is shared between threads, - PyMem_Free(items); - +#ifdef Py_GIL_DISABLED + bool use_qsbr = is_resize && _PyObject_GC_IS_SHARED(a); +#else + bool use_qsbr = false; +#endif + free_list_items(items, use_qsbr); // Note that there is no guarantee that the list is actually empty // at this point, because XDECREF may have populated it indirectly again! } +static void +list_clear(PyListObject *a) +{ + list_clear_impl(a, true); +} + static int list_clear_slot(PyObject *self) { - list_clear((PyListObject *)self); + list_clear_impl((PyListObject *)self, false); return 0; } @@ -695,7 +862,7 @@ list_clear_slot(PyObject *self) * guaranteed the call cannot fail. */ static int -list_ass_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) +list_ass_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) { /* Because [X]DECREF can recursively invoke list operations on this list, we must postpone all [X]DECREF activity until @@ -718,15 +885,6 @@ list_ass_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) if (v == NULL) n = 0; else { - if (a == b) { - /* Special case "a[i:j] = a" -- copy b first */ - v = list_slice(b, 0, Py_SIZE(b)); - if (v == NULL) - return result; - result = list_ass_slice(a, ilow, ihigh, v); - Py_DECREF(v); - return result; - } v_as_SF = PySequence_Fast(v, "can only assign an iterable"); if(v_as_SF == NULL) goto Error; @@ -800,6 +958,34 @@ list_ass_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) #undef b } +static int +list_ass_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) +{ + int ret; + if (a == (PyListObject *)v) { + Py_BEGIN_CRITICAL_SECTION(a); + Py_ssize_t n = PyList_GET_SIZE(a); + PyObject *copy = list_slice_lock_held(a, 0, n); + if (copy == NULL) { + return -1; + } + ret = list_ass_slice_lock_held(a, ilow, ihigh, copy); + Py_DECREF(copy); + Py_END_CRITICAL_SECTION(); + } + else if (v != NULL && PyList_CheckExact(v)) { + Py_BEGIN_CRITICAL_SECTION2(a, v); + ret = list_ass_slice_lock_held(a, ilow, ihigh, v); + Py_END_CRITICAL_SECTION2(); + } + else { + Py_BEGIN_CRITICAL_SECTION(a); + ret = list_ass_slice_lock_held(a, ilow, ihigh, v); + Py_END_CRITICAL_SECTION(); + } + return ret; +} + int PyList_SetSlice(PyObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) { @@ -810,26 +996,28 @@ PyList_SetSlice(PyObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) return list_ass_slice((PyListObject *)a, ilow, ihigh, v); } -static PyObject * +static int list_inplace_repeat_lock_held(PyListObject *self, Py_ssize_t n) { Py_ssize_t input_size = PyList_GET_SIZE(self); if (input_size == 0 || n == 1) { - return Py_NewRef(self); + return 0; } if (n < 1) { list_clear(self); - return Py_NewRef(self); + return 0; } if (input_size > PY_SSIZE_T_MAX / n) { - return PyErr_NoMemory(); + PyErr_NoMemory(); + return -1; } Py_ssize_t output_size = input_size * n; - if (list_resize(self, output_size) < 0) - return NULL; + if (list_resize(self, output_size) < 0) { + return -1; + } PyObject **items = self->ob_item; for (Py_ssize_t j = 0; j < input_size; j++) { @@ -837,8 +1025,7 @@ list_inplace_repeat_lock_held(PyListObject *self, Py_ssize_t n) } _Py_memory_repeat((char *)items, sizeof(PyObject *)*output_size, sizeof(PyObject *)*input_size); - - return Py_NewRef(self); + return 0; } static PyObject * @@ -847,7 +1034,12 @@ list_inplace_repeat(PyObject *_self, Py_ssize_t n) PyObject *ret; PyListObject *self = (PyListObject *) _self; Py_BEGIN_CRITICAL_SECTION(self); - ret = list_inplace_repeat_lock_held(self, n); + if (list_inplace_repeat_lock_held(self, n) < 0) { + ret = NULL; + } + else { + ret = Py_NewRef(self); + } Py_END_CRITICAL_SECTION(); return ret; } @@ -933,7 +1125,7 @@ static PyObject * list_copy_impl(PyListObject *self) /*[clinic end generated code: output=ec6b72d6209d418e input=81c54b0c7bb4f73d]*/ { - return list_slice(self, 0, Py_SIZE(self)); + return list_slice_lock_held(self, 0, Py_SIZE(self)); } /*[clinic input] @@ -995,7 +1187,7 @@ list_extend_fast(PyListObject *self, PyObject *iterable) } static int -list_extend_iter(PyListObject *self, PyObject *iterable) +list_extend_iter_lock_held(PyListObject *self, PyObject *iterable) { PyObject *it = PyObject_GetIter(iterable); if (it == NULL) { @@ -1069,45 +1261,114 @@ list_extend_iter(PyListObject *self, PyObject *iterable) return -1; } - static int -list_extend(PyListObject *self, PyObject *iterable) +list_extend_lock_held(PyListObject *self, PyObject *iterable) { - // Special cases: - // 1) lists and tuples which can use PySequence_Fast ops - // 2) extending self to self requires making a copy first - if (PyList_CheckExact(iterable) - || PyTuple_CheckExact(iterable) - || (PyObject *)self == iterable) - { - iterable = PySequence_Fast(iterable, "argument must be iterable"); - if (!iterable) { - return -1; - } + PyObject *seq = PySequence_Fast(iterable, "argument must be iterable"); + if (!seq) { + return -1; + } + + int res = list_extend_fast(self, seq); + Py_DECREF(seq); + return res; +} - int res = list_extend_fast(self, iterable); - Py_DECREF(iterable); - return res; +static int +list_extend_set(PyListObject *self, PySetObject *other) +{ + Py_ssize_t m = Py_SIZE(self); + Py_ssize_t n = PySet_GET_SIZE(other); + if (list_resize(self, m + n) < 0) { + return -1; } - else { - return list_extend_iter(self, iterable); + /* populate the end of self with iterable's items */ + Py_ssize_t setpos = 0; + Py_hash_t hash; + PyObject *key; + PyObject **dest = self->ob_item + m; + while (_PySet_NextEntry((PyObject *)other, &setpos, &key, &hash)) { + Py_INCREF(key); + *dest = key; + dest++; } + Py_SET_SIZE(self, m + n); + return 0; } - -PyObject * -_PyList_Extend(PyListObject *self, PyObject *iterable) +static int +list_extend_dict(PyListObject *self, PyDictObject *dict, int which_item) { - if (list_extend(self, iterable) < 0) { - return NULL; + // which_item: 0 for keys and 1 for values + Py_ssize_t m = Py_SIZE(self); + Py_ssize_t n = PyDict_GET_SIZE(dict); + if (list_resize(self, m + n) < 0) { + return -1; } - Py_RETURN_NONE; + + PyObject **dest = self->ob_item + m; + Py_ssize_t pos = 0; + PyObject *keyvalue[2]; + while (_PyDict_Next((PyObject *)dict, &pos, &keyvalue[0], &keyvalue[1], NULL)) { + PyObject *obj = keyvalue[which_item]; + Py_INCREF(obj); + *dest = obj; + dest++; + } + + Py_SET_SIZE(self, m + n); + return 0; } +static int +_list_extend(PyListObject *self, PyObject *iterable) +{ + // Special case: + // lists and tuples which can use PySequence_Fast ops + // TODO(@corona10): Add more special cases for other types. + int res = -1; + if ((PyObject *)self == iterable) { + Py_BEGIN_CRITICAL_SECTION(self); + res = list_inplace_repeat_lock_held(self, 2); + Py_END_CRITICAL_SECTION(); + } + else if (PyList_CheckExact(iterable)) { + Py_BEGIN_CRITICAL_SECTION2(self, iterable); + res = list_extend_lock_held(self, iterable); + Py_END_CRITICAL_SECTION2(); + } + else if (PyTuple_CheckExact(iterable)) { + Py_BEGIN_CRITICAL_SECTION(self); + res = list_extend_lock_held(self, iterable); + Py_END_CRITICAL_SECTION(); + } + else if (PyAnySet_CheckExact(iterable)) { + Py_BEGIN_CRITICAL_SECTION2(self, iterable); + res = list_extend_set(self, (PySetObject *)iterable); + Py_END_CRITICAL_SECTION2(); + } + else if (Py_IS_TYPE(iterable, &PyDictKeys_Type)) { + PyDictObject *dict = ((_PyDictViewObject *)iterable)->dv_dict; + Py_BEGIN_CRITICAL_SECTION2(self, dict); + res = list_extend_dict(self, dict, 0 /*keys*/); + Py_END_CRITICAL_SECTION2(); + } + else if (Py_IS_TYPE(iterable, &PyDictValues_Type)) { + PyDictObject *dict = ((_PyDictViewObject *)iterable)->dv_dict; + Py_BEGIN_CRITICAL_SECTION2(self, dict); + res = list_extend_dict(self, dict, 1 /*values*/); + Py_END_CRITICAL_SECTION2(); + } + else { + Py_BEGIN_CRITICAL_SECTION(self); + res = list_extend_iter_lock_held(self, iterable); + Py_END_CRITICAL_SECTION(); + } + return res; +} /*[clinic input] -@critical_section self iterable -list.extend as py_list_extend +list.extend as list_extend iterable: object / @@ -1116,12 +1377,20 @@ Extend list by appending elements from the iterable. [clinic start generated code]*/ static PyObject * -py_list_extend_impl(PyListObject *self, PyObject *iterable) -/*[clinic end generated code: output=a2f115ceace2c845 input=1d42175414e1a5f3]*/ +list_extend(PyListObject *self, PyObject *iterable) +/*[clinic end generated code: output=630fb3bca0c8e789 input=979da7597a515791]*/ { - return _PyList_Extend(self, iterable); + if (_list_extend(self, iterable) < 0) { + return NULL; + } + Py_RETURN_NONE; } +PyObject * +_PyList_Extend(PyListObject *self, PyObject *iterable) +{ + return list_extend(self, iterable); +} int PyList_Extend(PyObject *self, PyObject *iterable) @@ -1130,7 +1399,7 @@ PyList_Extend(PyObject *self, PyObject *iterable) PyErr_BadInternalCall(); return -1; } - return list_extend((PyListObject*)self, iterable); + return _list_extend((PyListObject*)self, iterable); } @@ -1150,7 +1419,7 @@ static PyObject * list_inplace_concat(PyObject *_self, PyObject *other) { PyListObject *self = (PyListObject *)_self; - if (list_extend(self, other) < 0) { + if (_list_extend(self, other) < 0) { return NULL; } return Py_NewRef(self); @@ -1386,10 +1655,11 @@ struct s_MergeState { /* binarysort is the best method for sorting small arrays: it does few compares, but can do data movement quadratic in the number of elements. - [lo, hi) is a contiguous slice of a list, and is sorted via + [lo.keys, hi) is a contiguous slice of a list of keys, and is sorted via binary insertion. This sort is stable. - On entry, must have lo <= start <= hi, and that [lo, start) is already - sorted (pass start == lo if you don't know!). + On entry, must have lo.keys <= start <= hi, and that + [lo.keys, start) is already sorted (pass start == lo.keys if you don't + know!). If islt() complains return -1, else 0. Even in case of error, the output slice will be some permutation of the input (nothing is lost or duplicated). @@ -1402,7 +1672,7 @@ binarysort(MergeState *ms, sortslice lo, PyObject **hi, PyObject **start) PyObject *pivot; assert(lo.keys <= start && start <= hi); - /* assert [lo, start) is sorted */ + /* assert [lo.keys, start) is sorted */ if (lo.keys == start) ++start; for (; start < hi; ++start) { @@ -1411,9 +1681,9 @@ binarysort(MergeState *ms, sortslice lo, PyObject **hi, PyObject **start) r = start; pivot = *r; /* Invariants: - * pivot >= all in [lo, l). + * pivot >= all in [lo.keys, l). * pivot < all in [r, start). - * The second is vacuously true at the start. + * These are vacuously true at the start. */ assert(l < r); do { @@ -1424,7 +1694,7 @@ binarysort(MergeState *ms, sortslice lo, PyObject **hi, PyObject **start) l = p+1; } while (l < r); assert(l == r); - /* The invariants still hold, so pivot >= all in [lo, l) and + /* The invariants still hold, so pivot >= all in [lo.keys, l) and pivot < all in [l, start), so pivot belongs at l. Note that if there are elements equal to pivot, l points to the first slot after them -- that's why this sort is stable. @@ -1439,7 +1709,7 @@ binarysort(MergeState *ms, sortslice lo, PyObject **hi, PyObject **start) p = start + offset; pivot = *p; l += offset; - for (p = start + offset; p > l; --p) + for ( ; p > l; --p) *p = *(p-1); *l = pivot; } @@ -1450,56 +1720,115 @@ binarysort(MergeState *ms, sortslice lo, PyObject **hi, PyObject **start) return -1; } -/* -Return the length of the run beginning at lo, in the slice [lo, hi). lo < hi -is required on entry. "A run" is the longest ascending sequence, with - - lo[0] <= lo[1] <= lo[2] <= ... - -or the longest descending sequence, with - - lo[0] > lo[1] > lo[2] > ... +static void +sortslice_reverse(sortslice *s, Py_ssize_t n) +{ + reverse_slice(s->keys, &s->keys[n]); + if (s->values != NULL) + reverse_slice(s->values, &s->values[n]); +} -Boolean *descending is set to 0 in the former case, or to 1 in the latter. -For its intended use in a stable mergesort, the strictness of the defn of -"descending" is needed so that the caller can safely reverse a descending -sequence without violating stability (strict > ensures there are no equal -elements to get out of order). +/* +Return the length of the run beginning at slo->keys, spanning no more than +nremaining elements. The run beginning there may be ascending or descending, +but the function permutes it in place, if needed, so that it's always ascending +upon return. Returns -1 in case of error. */ static Py_ssize_t -count_run(MergeState *ms, PyObject **lo, PyObject **hi, int *descending) +count_run(MergeState *ms, sortslice *slo, Py_ssize_t nremaining) { - Py_ssize_t k; + Py_ssize_t k; /* used by IFLT macro expansion */ Py_ssize_t n; + PyObject ** const lo = slo->keys; - assert(lo < hi); - *descending = 0; - ++lo; - if (lo == hi) - return 1; - - n = 2; - IFLT(*lo, *(lo-1)) { - *descending = 1; - for (lo = lo+1; lo < hi; ++lo, ++n) { - IFLT(*lo, *(lo-1)) - ; - else - break; - } + /* In general, as things go on we've established that the slice starts + with a monotone run of n elements, starting at lo. */ + + /* We're n elements into the slice, and the most recent neq+1 elments are + * all equal. This reverses them in-place, and resets neq for reuse. + */ +#define REVERSE_LAST_NEQ \ + if (neq) { \ + sortslice slice = *slo; \ + ++neq; \ + sortslice_advance(&slice, n - neq); \ + sortslice_reverse(&slice, neq); \ + neq = 0; \ + } + + /* Sticking to only __lt__ compares is confusing and error-prone. But in + * this routine, almost all uses of IFLT can be captured by tiny macros + * giving mnemonic names to the intent. Note that inline functions don't + * work for this (IFLT expands to code including `goto fail`). + */ +#define IF_NEXT_LARGER IFLT(lo[n-1], lo[n]) +#define IF_NEXT_SMALLER IFLT(lo[n], lo[n-1]) + + assert(nremaining); + /* try ascending run first */ + for (n = 1; n < nremaining; ++n) { + IF_NEXT_SMALLER + break; } - else { - for (lo = lo+1; lo < hi; ++lo, ++n) { - IFLT(*lo, *(lo-1)) + if (n == nremaining) + return n; + /* lo[n] is strictly less */ + /* If n is 1 now, then the first compare established it's a descending + * run, so fall through to the descending case. But if n > 1, there are + * n elements in an ascending run terminated by the strictly less lo[n]. + * If the first key < lo[n-1], *somewhere* along the way the sequence + * increased, so we're done (there is no descending run). + * Else first key >= lo[n-1], which implies that the entire ascending run + * consists of equal elements. In that case, this is a descending run, + * and we reverse the all-equal prefix in-place. + */ + if (n > 1) { + IFLT(lo[0], lo[n-1]) + return n; + sortslice_reverse(slo, n); + } + ++n; /* in all cases it's been established that lo[n] has been resolved */ + + /* Finish descending run. All-squal subruns are reversed in-place on the + * fly. Their original order will be restored at the end by the whole-slice + * reversal. + */ + Py_ssize_t neq = 0; + for ( ; n < nremaining; ++n) { + IF_NEXT_SMALLER { + /* This ends the most recent run of equal elments, but still in + * the "descending" direction. + */ + REVERSE_LAST_NEQ + } + else { + IF_NEXT_LARGER /* descending run is over */ break; + else /* not x < y and not y < x implies x == y */ + ++neq; } } + REVERSE_LAST_NEQ + sortslice_reverse(slo, n); /* transform to ascending run */ + + /* And after reversing, it's possible this can be extended by a + * naturally increasing suffix; e.g., [3, 2, 3, 4, 1] makes an + * ascending run from the first 4 elements. + */ + for ( ; n < nremaining; ++n) { + IF_NEXT_SMALLER + break; + } return n; fail: return -1; + +#undef REVERSE_LAST_NEQ +#undef IF_NEXT_SMALLER +#undef IF_NEXT_LARGER } /* @@ -2217,14 +2546,6 @@ merge_compute_minrun(Py_ssize_t n) return n + r; } -static void -reverse_sortslice(sortslice *s, Py_ssize_t n) -{ - reverse_slice(s->keys, &s->keys[n]); - if (s->values != NULL) - reverse_slice(s->values, &s->values[n]); -} - /* Here we define custom comparison functions to optimize for the cases one commonly * encounters in practice: homogeneous lists, often of one of the basic types. */ @@ -2393,6 +2714,7 @@ unsafe_tuple_compare(PyObject *v, PyObject *w, MergeState *ms) * duplicated). */ /*[clinic input] +@critical_section list.sort * @@ -2412,7 +2734,7 @@ The reverse flag can be set to sort in descending order. static PyObject * list_sort_impl(PyListObject *self, PyObject *keyfunc, int reverse) -/*[clinic end generated code: output=57b9f9c5e23fbe42 input=a74c4cd3ec6b5c08]*/ +/*[clinic end generated code: output=57b9f9c5e23fbe42 input=667bf25d0e3a3676]*/ { MergeState ms; Py_ssize_t nremaining; @@ -2439,7 +2761,7 @@ list_sort_impl(PyListObject *self, PyObject *keyfunc, int reverse) saved_ob_item = self->ob_item; saved_allocated = self->allocated; Py_SET_SIZE(self, 0); - self->ob_item = NULL; + FT_ATOMIC_STORE_PTR_RELEASE(self->ob_item, NULL); self->allocated = -1; /* any operation will reset it to >= 0 */ if (keyfunc == NULL) { @@ -2591,15 +2913,12 @@ list_sort_impl(PyListObject *self, PyObject *keyfunc, int reverse) */ minrun = merge_compute_minrun(nremaining); do { - int descending; Py_ssize_t n; /* Identify next run. */ - n = count_run(&ms, lo.keys, lo.keys + nremaining, &descending); + n = count_run(&ms, &lo, nremaining); if (n < 0) goto fail; - if (descending) - reverse_sortslice(&lo, n); /* If short, extend to min(minrun, nremaining). */ if (n < minrun) { const Py_ssize_t force = nremaining <= minrun ? @@ -2659,15 +2978,20 @@ list_sort_impl(PyListObject *self, PyObject *keyfunc, int reverse) final_ob_item = self->ob_item; i = Py_SIZE(self); Py_SET_SIZE(self, saved_ob_size); - self->ob_item = saved_ob_item; - self->allocated = saved_allocated; + FT_ATOMIC_STORE_PTR_RELEASE(self->ob_item, saved_ob_item); + FT_ATOMIC_STORE_SSIZE_RELAXED(self->allocated, saved_allocated); if (final_ob_item != NULL) { /* we cannot use list_clear() for this because it does not guarantee that the list is really empty when it returns */ while (--i >= 0) { Py_XDECREF(final_ob_item[i]); } - PyMem_Free(final_ob_item); +#ifdef Py_GIL_DISABLED + bool use_qsbr = _PyObject_GC_IS_SHARED(self); +#else + bool use_qsbr = false; +#endif + free_list_items(final_ob_item, use_qsbr); } return Py_XNewRef(result); } @@ -2681,7 +3005,9 @@ PyList_Sort(PyObject *v) PyErr_BadInternalCall(); return -1; } + Py_BEGIN_CRITICAL_SECTION(v); v = list_sort_impl((PyListObject *)v, NULL, 0); + Py_END_CRITICAL_SECTION(); if (v == NULL) return -1; Py_DECREF(v); @@ -2861,8 +3187,7 @@ list_remove_impl(PyListObject *self, PyObject *value) int cmp = PyObject_RichCompareBool(obj, value, Py_EQ); Py_DECREF(obj); if (cmp > 0) { - if (list_ass_slice(self, i, i+1, - (PyObject *)NULL) == 0) + if (list_ass_slice_lock_held(self, i, i+1, NULL) == 0) Py_RETURN_NONE; return NULL; } @@ -2977,7 +3302,7 @@ list___init___impl(PyListObject *self, PyObject *iterable) list_clear(self); } if (iterable != NULL) { - if (list_extend(self, iterable) < 0) { + if (_list_extend(self, iterable) < 0) { return -1; } } @@ -3038,7 +3363,7 @@ static PyMethodDef list_methods[] = { LIST_COPY_METHODDEF LIST_APPEND_METHODDEF LIST_INSERT_METHODDEF - PY_LIST_EXTEND_METHODDEF + LIST_EXTEND_METHODDEF LIST_POP_METHODDEF LIST_REMOVE_METHODDEF LIST_INDEX_METHODDEF @@ -3062,6 +3387,45 @@ static PySequenceMethods list_as_sequence = { list_inplace_repeat, /* sq_inplace_repeat */ }; +static inline PyObject * +list_slice_step_lock_held(PyListObject *a, Py_ssize_t start, Py_ssize_t step, Py_ssize_t len) +{ + PyListObject *np = (PyListObject *)list_new_prealloc(len); + if (np == NULL) { + return NULL; + } + size_t cur; + Py_ssize_t i; + PyObject **src = a->ob_item; + PyObject **dest = np->ob_item; + for (cur = start, i = 0; i < len; + cur += (size_t)step, i++) { + PyObject *v = src[cur]; + dest[i] = Py_NewRef(v); + } + Py_SET_SIZE(np, len); + return (PyObject *)np; +} + +static PyObject * +list_slice_wrap(PyListObject *aa, Py_ssize_t start, Py_ssize_t stop, Py_ssize_t step) +{ + PyObject *res = NULL; + Py_BEGIN_CRITICAL_SECTION(aa); + Py_ssize_t len = PySlice_AdjustIndices(Py_SIZE(aa), &start, &stop, step); + if (len <= 0) { + res = PyList_New(0); + } + else if (step == 1) { + res = list_slice_lock_held(aa, start, stop); + } + else { + res = list_slice_step_lock_held(aa, start, step, len); + } + Py_END_CRITICAL_SECTION(); + return res; +} + static PyObject * list_subscript(PyObject* _self, PyObject* item) { @@ -3076,38 +3440,11 @@ list_subscript(PyObject* _self, PyObject* item) return list_item((PyObject *)self, i); } else if (PySlice_Check(item)) { - Py_ssize_t start, stop, step, slicelength, i; - size_t cur; - PyObject* result; - PyObject* it; - PyObject **src, **dest; - + Py_ssize_t start, stop, step; if (PySlice_Unpack(item, &start, &stop, &step) < 0) { return NULL; } - slicelength = PySlice_AdjustIndices(Py_SIZE(self), &start, &stop, - step); - - if (slicelength <= 0) { - return PyList_New(0); - } - else if (step == 1) { - return list_slice(self, start, stop); - } - else { - result = list_new_prealloc(slicelength); - if (!result) return NULL; - - src = self->ob_item; - dest = ((PyListObject *)result)->ob_item; - for (cur = start, i = 0; i < slicelength; - cur += (size_t)step, i++) { - it = Py_NewRef(src[cur]); - dest[i] = it; - } - Py_SET_SIZE(result, slicelength); - return result; - } + return list_slice_wrap(self, start, stop, step); } else { PyErr_Format(PyExc_TypeError, @@ -3218,8 +3555,10 @@ list_ass_subscript(PyObject* _self, PyObject* item, PyObject* value) /* protect against a[::-1] = a */ if (self == (PyListObject*)value) { - seq = list_slice((PyListObject*)value, 0, - PyList_GET_SIZE(value)); + Py_BEGIN_CRITICAL_SECTION(value); + seq = list_slice_lock_held((PyListObject*)value, 0, + Py_SIZE(value)); + Py_END_CRITICAL_SECTION(); } else { seq = PySequence_Fast(value, diff --git a/Objects/listsort.txt b/Objects/listsort.txt index 32a59e510f07542..4f84e2c87da7f11 100644 --- a/Objects/listsort.txt +++ b/Objects/listsort.txt @@ -212,24 +212,43 @@ A detailed description of timsort follows. Runs ---- -count_run() returns the # of elements in the next run. A run is either -"ascending", which means non-decreasing: +count_run() returns the # of elements in the next run, and, if it's a +descending run, reverses it in-place. A run is either "ascending", which +means non-decreasing: a0 <= a1 <= a2 <= ... -or "descending", which means strictly decreasing: +or "descending", which means non-increasing: - a0 > a1 > a2 > ... + a0 >= a1 >= a2 >= ... Note that a run is always at least 2 long, unless we start at the array's -last element. - -The definition of descending is strict, because the main routine reverses -a descending run in-place, transforming a descending run into an ascending -run. Reversal is done via the obvious fast "swap elements starting at each -end, and converge at the middle" method, and that can violate stability if -the slice contains any equal elements. Using a strict definition of -descending ensures that a descending run contains distinct elements. +last element. If all elements in the array are equal, it can be viewed as +both ascending and descending. Upon return, the run count_run() identifies +is always ascending. + +Reversal is done via the obvious fast "swap elements starting at each +end, and converge at the middle" method. That can violate stability if +the slice contains any equal elements. For that reason, for a long time +the code used strict inequality (">" rather than ">=") in its definition +of descending. + +Removing that restriction required some complication: when processing a +descending run, all-equal sub-runs of elements are reversed in-place, on the +fly. Their original relative order is restored "by magic" via the final +"reverse the entire run" step. + +This makes processing descending runs a little more costly. We only use +`__lt__` comparisons, so that `x == y` has to be deduced from +`not x < y and not y < x`. But so long as a run remains strictly decreasing, +only one of those compares needs to be done per loop iteration. So the primsry +extra cost is paid only when there are equal elements, and they get some +compensating benefit by not needing to end the descending run. + +There's one more trick added since the original: after reversing a descending +run, it's possible that it can be extended by an adjacent ascending run. For +example, given [3, 2, 1, 3, 4, 5, 0], the 3-element descending prefix is +reversed in-place, and then extended by [3, 4, 5]. If an array is random, it's very unlikely we'll see long runs. If a natural run contains less than minrun elements (see next section), the main loop diff --git a/Objects/longobject.c b/Objects/longobject.c index 2d1c6ad788e281e..cc2fe11f31c4305 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -1965,7 +1965,9 @@ long_to_decimal_string_internal(PyObject *aa, digit *pout, *pin, rem, tenpow; int negative; int d; - int kind; + + // writer or bytes_writer can be used, but not both at the same time. + assert(writer == NULL || bytes_writer == NULL); a = (PyLongObject *)aa; if (a == NULL || !PyLong_Check(a)) { @@ -2078,7 +2080,6 @@ long_to_decimal_string_internal(PyObject *aa, Py_DECREF(scratch); return -1; } - kind = writer->kind; } else if (bytes_writer) { *bytes_str = _PyBytesWriter_Prepare(bytes_writer, *bytes_str, strlen); @@ -2093,7 +2094,6 @@ long_to_decimal_string_internal(PyObject *aa, Py_DECREF(scratch); return -1; } - kind = PyUnicode_KIND(str); } #define WRITE_DIGITS(p) \ @@ -2141,19 +2141,23 @@ long_to_decimal_string_internal(PyObject *aa, WRITE_DIGITS(p); assert(p == *bytes_str); } - else if (kind == PyUnicode_1BYTE_KIND) { - Py_UCS1 *p; - WRITE_UNICODE_DIGITS(Py_UCS1); - } - else if (kind == PyUnicode_2BYTE_KIND) { - Py_UCS2 *p; - WRITE_UNICODE_DIGITS(Py_UCS2); - } else { - Py_UCS4 *p; - assert (kind == PyUnicode_4BYTE_KIND); - WRITE_UNICODE_DIGITS(Py_UCS4); + int kind = writer ? writer->kind : PyUnicode_KIND(str); + if (kind == PyUnicode_1BYTE_KIND) { + Py_UCS1 *p; + WRITE_UNICODE_DIGITS(Py_UCS1); + } + else if (kind == PyUnicode_2BYTE_KIND) { + Py_UCS2 *p; + WRITE_UNICODE_DIGITS(Py_UCS2); + } + else { + assert (kind == PyUnicode_4BYTE_KIND); + Py_UCS4 *p; + WRITE_UNICODE_DIGITS(Py_UCS4); + } } + #undef WRITE_DIGITS #undef WRITE_UNICODE_DIGITS @@ -2194,11 +2198,12 @@ long_format_binary(PyObject *aa, int base, int alternate, PyObject *v = NULL; Py_ssize_t sz; Py_ssize_t size_a; - int kind; int negative; int bits; assert(base == 2 || base == 8 || base == 16); + // writer or bytes_writer can be used, but not both at the same time. + assert(writer == NULL || bytes_writer == NULL); if (a == NULL || !PyLong_Check(a)) { PyErr_BadInternalCall(); return -1; @@ -2246,7 +2251,6 @@ long_format_binary(PyObject *aa, int base, int alternate, if (writer) { if (_PyUnicodeWriter_Prepare(writer, sz, 'x') == -1) return -1; - kind = writer->kind; } else if (bytes_writer) { *bytes_str = _PyBytesWriter_Prepare(bytes_writer, *bytes_str, sz); @@ -2257,7 +2261,6 @@ long_format_binary(PyObject *aa, int base, int alternate, v = PyUnicode_New(sz, 'x'); if (v == NULL) return -1; - kind = PyUnicode_KIND(v); } #define WRITE_DIGITS(p) \ @@ -2318,19 +2321,23 @@ long_format_binary(PyObject *aa, int base, int alternate, WRITE_DIGITS(p); assert(p == *bytes_str); } - else if (kind == PyUnicode_1BYTE_KIND) { - Py_UCS1 *p; - WRITE_UNICODE_DIGITS(Py_UCS1); - } - else if (kind == PyUnicode_2BYTE_KIND) { - Py_UCS2 *p; - WRITE_UNICODE_DIGITS(Py_UCS2); - } else { - Py_UCS4 *p; - assert (kind == PyUnicode_4BYTE_KIND); - WRITE_UNICODE_DIGITS(Py_UCS4); + int kind = writer ? writer->kind : PyUnicode_KIND(v); + if (kind == PyUnicode_1BYTE_KIND) { + Py_UCS1 *p; + WRITE_UNICODE_DIGITS(Py_UCS1); + } + else if (kind == PyUnicode_2BYTE_KIND) { + Py_UCS2 *p; + WRITE_UNICODE_DIGITS(Py_UCS2); + } + else { + assert (kind == PyUnicode_4BYTE_KIND); + Py_UCS4 *p; + WRITE_UNICODE_DIGITS(Py_UCS4); + } } + #undef WRITE_DIGITS #undef WRITE_UNICODE_DIGITS diff --git a/Objects/mimalloc/alloc.c b/Objects/mimalloc/alloc.c index b369a5ebcb23a76..c133f23fc9830d8 100644 --- a/Objects/mimalloc/alloc.c +++ b/Objects/mimalloc/alloc.c @@ -27,7 +27,7 @@ terms of the MIT license. A copy of the license can be found in the file // ------------------------------------------------------ #if (MI_DEBUG>0) -static void mi_debug_fill(mi_page_t* page, mi_block_t* block, int c, size_t size) { +static inline void mi_debug_fill(mi_page_t* page, mi_block_t* block, int c, size_t size) { size_t offset = (size_t)page->debug_offset; if (offset < size) { memset((char*)block + offset, c, size - offset); @@ -609,7 +609,10 @@ bool _mi_free_delayed_block(mi_block_t* block) { // get segment and page const mi_segment_t* const segment = _mi_ptr_segment(block); mi_assert_internal(_mi_ptr_cookie(segment) == segment->cookie); +#ifndef Py_GIL_DISABLED + // The GC traverses heaps of other threads, which can trigger this assert. mi_assert_internal(_mi_thread_id() == segment->thread_id); +#endif mi_page_t* const page = _mi_segment_page_of(segment, block); // Clear the no-delayed flag so delayed freeing is used again for this page. diff --git a/Objects/mimalloc/heap.c b/Objects/mimalloc/heap.c index 154dad0b1284805..26777f39fb6aa56 100644 --- a/Objects/mimalloc/heap.c +++ b/Objects/mimalloc/heap.c @@ -98,7 +98,10 @@ static bool mi_heap_page_collect(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t if (mi_page_all_free(page)) { // no more used blocks, free the page. // note: this will free retired pages as well. - _mi_page_free(page, pq, collect >= MI_FORCE); + bool freed = _PyMem_mi_page_maybe_free(page, pq, collect >= MI_FORCE); + if (!freed && collect == MI_ABANDON) { + _mi_page_abandon(page, pq); + } } else if (collect == MI_ABANDON) { // still used blocks but the thread is done; abandon the page @@ -153,6 +156,9 @@ static void mi_heap_collect_ex(mi_heap_t* heap, mi_collect_t collect) // collect retired pages _mi_heap_collect_retired(heap, force); + // free pages that were delayed with QSBR + _PyMem_mi_heap_collect_qsbr(heap); + // collect all pages owned by this thread mi_heap_visit_pages(heap, &mi_heap_page_collect, &collect, NULL); mi_assert_internal( collect != MI_ABANDON || mi_atomic_load_ptr_acquire(mi_block_t,&heap->thread_delayed_free) == NULL ); diff --git a/Objects/mimalloc/page.c b/Objects/mimalloc/page.c index 63db893e49405c7..25ecd6ec7d79834 100644 --- a/Objects/mimalloc/page.c +++ b/Objects/mimalloc/page.c @@ -225,6 +225,9 @@ void _mi_page_free_collect(mi_page_t* page, bool force) { // and the local free list if (page->local_free != NULL) { + // any previous QSBR goals are no longer valid because we reused the page + _PyMem_mi_page_clear_qsbr(page); + if mi_likely(page->free == NULL) { // usual case page->free = page->local_free; @@ -267,6 +270,7 @@ void _mi_page_reclaim(mi_heap_t* heap, mi_page_t* page) { // TODO: push on full queue immediately if it is full? mi_page_queue_t* pq = mi_page_queue(heap, mi_page_block_size(page)); mi_page_queue_push(heap, pq, page); + _PyMem_mi_page_reclaimed(page); mi_assert_expensive(_mi_page_is_valid(page)); } @@ -383,6 +387,13 @@ void _mi_page_abandon(mi_page_t* page, mi_page_queue_t* pq) { mi_heap_t* pheap = mi_page_heap(page); +#ifdef Py_GIL_DISABLED + if (page->qsbr_node.next != NULL) { + // remove from QSBR queue, but keep the goal + llist_remove(&page->qsbr_node); + } +#endif + // remove from our page list mi_segments_tld_t* segments_tld = &pheap->tld->segments; mi_page_queue_remove(pq, page); @@ -417,6 +428,11 @@ void _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force) { mi_heap_t* heap = mi_page_heap(page); +#ifdef Py_GIL_DISABLED + mi_assert_internal(page->qsbr_goal == 0); + mi_assert_internal(page->qsbr_node.next == NULL); +#endif + // remove from the page list // (no need to do _mi_heap_delayed_free first as all blocks are already free) mi_segments_tld_t* segments_tld = &heap->tld->segments; @@ -444,6 +460,9 @@ void _mi_page_retire(mi_page_t* page) mi_attr_noexcept { mi_page_set_has_aligned(page, false); + // any previous QSBR goals are no longer valid because we reused the page + _PyMem_mi_page_clear_qsbr(page); + // don't retire too often.. // (or we end up retiring and re-allocating most of the time) // NOTE: refine this more: we should not retire if this @@ -465,7 +484,7 @@ void _mi_page_retire(mi_page_t* page) mi_attr_noexcept { return; // dont't free after all } } - _mi_page_free(page, pq, false); + _PyMem_mi_page_maybe_free(page, pq, false); } // free retired pages: we don't need to look at the entire queues @@ -480,7 +499,10 @@ void _mi_heap_collect_retired(mi_heap_t* heap, bool force) { if (mi_page_all_free(page)) { page->retire_expire--; if (force || page->retire_expire == 0) { - _mi_page_free(pq->first, pq, force); +#ifdef Py_GIL_DISABLED + mi_assert_internal(page->qsbr_goal == 0); +#endif + _PyMem_mi_page_maybe_free(page, pq, force); } else { // keep retired, update min/max @@ -661,6 +683,7 @@ static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi // set fields mi_page_set_heap(page, heap); page->tag = heap->tag; + page->use_qsbr = heap->page_use_qsbr; page->debug_offset = heap->debug_offset; page->xblock_size = (block_size < MI_HUGE_BLOCK_SIZE ? (uint32_t)block_size : MI_HUGE_BLOCK_SIZE); // initialize before _mi_segment_page_start size_t page_size; @@ -691,6 +714,10 @@ static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi mi_assert_internal(page->xthread_free == 0); mi_assert_internal(page->next == NULL); mi_assert_internal(page->prev == NULL); +#ifdef Py_GIL_DISABLED + mi_assert_internal(page->qsbr_goal == 0); + mi_assert_internal(page->qsbr_node.next == NULL); +#endif mi_assert_internal(page->retire_expire == 0); mi_assert_internal(!mi_page_has_aligned(page)); #if (MI_PADDING || MI_ENCODE_FREELIST) @@ -750,6 +777,7 @@ static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* p mi_heap_stat_counter_increase(heap, searches, count); if (page == NULL) { + _PyMem_mi_heap_collect_qsbr(heap); // some pages might be safe to free now _mi_heap_collect_retired(heap, false); // perhaps make a page available? page = mi_page_fresh(heap, pq); if (page == NULL && first_try) { @@ -760,6 +788,7 @@ static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* p else { mi_assert(pq->first == page); page->retire_expire = 0; + _PyMem_mi_page_clear_qsbr(page); } mi_assert_internal(page == NULL || mi_page_immediate_available(page)); return page; @@ -785,6 +814,7 @@ static inline mi_page_t* mi_find_free_page(mi_heap_t* heap, size_t size) { if (mi_page_immediate_available(page)) { page->retire_expire = 0; + _PyMem_mi_page_clear_qsbr(page); return page; // fast path } } @@ -878,6 +908,7 @@ static mi_page_t* mi_find_page(mi_heap_t* heap, size_t size, size_t huge_alignme return NULL; } else { + _PyMem_mi_heap_collect_qsbr(heap); return mi_large_huge_page_alloc(heap,size,huge_alignment); } } diff --git a/Objects/mimalloc/prim/windows/etw.h b/Objects/mimalloc/prim/windows/etw.h index 4e0a092a10f4ba9..e9ec35fc9ed7ebc 100644 --- a/Objects/mimalloc/prim/windows/etw.h +++ b/Objects/mimalloc/prim/windows/etw.h @@ -136,7 +136,7 @@ extern "C" { // - MCGEN_EVENTSETINFORMATION // - MCGEN_EVENTWRITETRANSFER // -// If the the macro is undefined, the MC implementation will default to the +// If the macro is undefined, the MC implementation will default to the // corresponding ETW APIs. For example, if the MCGEN_EVENTREGISTER macro is // undefined, the EventRegister[MyProviderName] macro will use EventRegister // in user mode and will use EtwRegister in kernel mode. diff --git a/Objects/mimalloc/segment.c b/Objects/mimalloc/segment.c index 584233b8b57bb4e..08b156433653a43 100644 --- a/Objects/mimalloc/segment.c +++ b/Objects/mimalloc/segment.c @@ -982,6 +982,10 @@ static mi_slice_t* mi_segment_page_clear(mi_page_t* page, mi_segments_tld_t* tld mi_assert_internal(mi_page_all_free(page)); mi_segment_t* segment = _mi_ptr_segment(page); mi_assert_internal(segment->used > 0); +#ifdef Py_GIL_DISABLED + mi_assert_internal(page->qsbr_goal == 0); + mi_assert_internal(page->qsbr_node.next == NULL); +#endif size_t inuse = page->capacity * mi_page_block_size(page); _mi_stat_decrease(&tld->stats->page_committed, inuse); @@ -1270,10 +1274,13 @@ static bool mi_segment_check_free(mi_segment_t* segment, size_t slices_needed, s // ensure used count is up to date and collect potential concurrent frees mi_page_t* const page = mi_slice_to_page(slice); _mi_page_free_collect(page, false); - if (mi_page_all_free(page)) { + if (mi_page_all_free(page) && _PyMem_mi_page_is_safe_to_free(page)) { // if this page is all free now, free it without adding to any queues (yet) mi_assert_internal(page->next == NULL && page->prev==NULL); _mi_stat_decrease(&tld->stats->pages_abandoned, 1); +#ifdef Py_GIL_DISABLED + page->qsbr_goal = 0; +#endif segment->abandoned--; slice = mi_segment_page_clear(page, tld); // re-assign slice due to coalesce! mi_assert_internal(!mi_slice_is_used(slice)); @@ -1344,15 +1351,18 @@ static mi_segment_t* mi_segment_reclaim(mi_segment_t* segment, mi_heap_t* heap, mi_page_set_heap(page, target_heap); _mi_page_use_delayed_free(page, MI_USE_DELAYED_FREE, true); // override never (after heap is set) _mi_page_free_collect(page, false); // ensure used count is up to date - if (mi_page_all_free(page)) { + if (mi_page_all_free(page) && _PyMem_mi_page_is_safe_to_free(page)) { // if everything free by now, free the page +#ifdef Py_GIL_DISABLED + page->qsbr_goal = 0; +#endif slice = mi_segment_page_clear(page, tld); // set slice again due to coalesceing } else { // otherwise reclaim it into the heap _mi_page_reclaim(target_heap, page); if (requested_block_size == page->xblock_size && mi_page_has_any_available(page) && - heap == target_heap) { + requested_block_size <= MI_MEDIUM_OBJ_SIZE_MAX && heap == target_heap) { if (right_page_reclaimed != NULL) { *right_page_reclaimed = true; } } } diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index 3a1c516658dce78..9cd98fb4345fdd6 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -1004,9 +1004,13 @@ module_set_annotations(PyModuleObject *m, PyObject *value, void *Py_UNUSED(ignor } else { /* delete */ - ret = PyDict_DelItem(dict, &_Py_ID(__annotations__)); - if (ret < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) { - PyErr_SetString(PyExc_AttributeError, "__annotations__"); + ret = PyDict_Pop(dict, &_Py_ID(__annotations__), NULL); + if (ret == 0) { + PyErr_SetObject(PyExc_AttributeError, &_Py_ID(__annotations__)); + ret = -1; + } + else if (ret > 0) { + ret = 0; } } diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 43427d4449eb1ad..4fe195b63166c17 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -12,6 +12,12 @@ #include // malloc() #include #ifdef WITH_MIMALLOC +// Forward declarations of functions used in our mimalloc modifications +static void _PyMem_mi_page_clear_qsbr(mi_page_t *page); +static bool _PyMem_mi_page_is_safe_to_free(mi_page_t *page); +static bool _PyMem_mi_page_maybe_free(mi_page_t *page, mi_page_queue_t *pq, bool force); +static void _PyMem_mi_page_reclaimed(mi_page_t *page); +static void _PyMem_mi_heap_collect_qsbr(mi_heap_t *heap); # include "pycore_mimalloc.h" # include "mimalloc/static.c" # include "mimalloc/internal.h" // for stats @@ -86,6 +92,113 @@ _PyMem_RawFree(void *Py_UNUSED(ctx), void *ptr) #ifdef WITH_MIMALLOC +static void +_PyMem_mi_page_clear_qsbr(mi_page_t *page) +{ +#ifdef Py_GIL_DISABLED + // Clear the QSBR goal and remove the page from the QSBR linked list. + page->qsbr_goal = 0; + if (page->qsbr_node.next != NULL) { + llist_remove(&page->qsbr_node); + } +#endif +} + +// Check if an empty, newly reclaimed page is safe to free now. +static bool +_PyMem_mi_page_is_safe_to_free(mi_page_t *page) +{ + assert(mi_page_all_free(page)); +#ifdef Py_GIL_DISABLED + assert(page->qsbr_node.next == NULL); + if (page->use_qsbr && page->qsbr_goal != 0) { + _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET(); + if (tstate == NULL) { + return false; + } + return _Py_qbsr_goal_reached(tstate->qsbr, page->qsbr_goal); + } +#endif + return true; + +} + +static bool +_PyMem_mi_page_maybe_free(mi_page_t *page, mi_page_queue_t *pq, bool force) +{ +#ifdef Py_GIL_DISABLED + assert(mi_page_all_free(page)); + if (page->use_qsbr) { + _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)PyThreadState_GET(); + if (page->qsbr_goal != 0 && _Py_qbsr_goal_reached(tstate->qsbr, page->qsbr_goal)) { + _PyMem_mi_page_clear_qsbr(page); + _mi_page_free(page, pq, force); + return true; + } + + _PyMem_mi_page_clear_qsbr(page); + page->retire_expire = 0; + page->qsbr_goal = _Py_qsbr_deferred_advance(tstate->qsbr); + llist_insert_tail(&tstate->mimalloc.page_list, &page->qsbr_node); + return false; + } +#endif + _mi_page_free(page, pq, force); + return true; +} + +static void +_PyMem_mi_page_reclaimed(mi_page_t *page) +{ +#ifdef Py_GIL_DISABLED + assert(page->qsbr_node.next == NULL); + if (page->qsbr_goal != 0) { + if (mi_page_all_free(page)) { + assert(page->qsbr_node.next == NULL); + _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)PyThreadState_GET(); + page->retire_expire = 0; + llist_insert_tail(&tstate->mimalloc.page_list, &page->qsbr_node); + } + else { + page->qsbr_goal = 0; + } + } +#endif +} + +static void +_PyMem_mi_heap_collect_qsbr(mi_heap_t *heap) +{ +#ifdef Py_GIL_DISABLED + if (!heap->page_use_qsbr) { + return; + } + + _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET(); + struct llist_node *head = &tstate->mimalloc.page_list; + if (llist_empty(head)) { + return; + } + + struct llist_node *node; + llist_for_each_safe(node, head) { + mi_page_t *page = llist_data(node, mi_page_t, qsbr_node); + if (!mi_page_all_free(page)) { + // We allocated from this page some point after the delayed free + _PyMem_mi_page_clear_qsbr(page); + continue; + } + + if (!_Py_qsbr_poll(tstate->qsbr, page->qsbr_goal)) { + return; + } + + _PyMem_mi_page_clear_qsbr(page); + _mi_page_free(page, mi_page_queue_of(page), false); + } +#endif +} + void * _PyMem_MiMalloc(void *ctx, size_t size) { @@ -957,7 +1070,7 @@ _PyMem_Strdup(const char *str) // A pointer to be freed once the QSBR read sequence reaches qsbr_goal. struct _mem_work_item { - void *ptr; + uintptr_t ptr; // lowest bit tagged 1 for objects freed with PyObject_Free uint64_t qsbr_goal; }; @@ -971,16 +1084,27 @@ struct _mem_work_chunk { struct _mem_work_item array[WORK_ITEMS_PER_CHUNK]; }; -void -_PyMem_FreeDelayed(void *ptr) +static void +free_work_item(uintptr_t ptr) +{ + if (ptr & 0x01) { + PyObject_Free((char *)(ptr - 1)); + } + else { + PyMem_Free((void *)ptr); + } +} + +static void +free_delayed(uintptr_t ptr) { #ifndef Py_GIL_DISABLED - PyMem_Free(ptr); + free_work_item(ptr); #else if (_PyRuntime.stoptheworld.world_stopped) { // Free immediately if the world is stopped, including during // interpreter shutdown. - PyMem_Free(ptr); + free_work_item(ptr); return; } @@ -1007,7 +1131,7 @@ _PyMem_FreeDelayed(void *ptr) if (buf == NULL) { // failed to allocate a buffer, free immediately _PyEval_StopTheWorld(tstate->base.interp); - PyMem_Free(ptr); + free_work_item(ptr); _PyEval_StartTheWorld(tstate->base.interp); return; } @@ -1024,6 +1148,20 @@ _PyMem_FreeDelayed(void *ptr) #endif } +void +_PyMem_FreeDelayed(void *ptr) +{ + assert(!((uintptr_t)ptr & 0x01)); + free_delayed((uintptr_t)ptr); +} + +void +_PyObject_FreeDelayed(void *ptr) +{ + assert(!((uintptr_t)ptr & 0x01)); + free_delayed(((uintptr_t)ptr)|0x01); +} + static struct _mem_work_chunk * work_queue_first(struct llist_node *head) { @@ -1043,7 +1181,7 @@ process_queue(struct llist_node *head, struct _qsbr_thread_state *qsbr, return; } - PyMem_Free(item->ptr); + free_work_item(item->ptr); buf->rd_idx++; } @@ -1130,7 +1268,7 @@ _PyMem_FiniDelayed(PyInterpreterState *interp) // Free the remaining items immediately. There should be no other // threads accessing the memory at this point during shutdown. struct _mem_work_item *item = &buf->array[buf->rd_idx]; - PyMem_Free(item->ptr); + free_work_item(item->ptr); buf->rd_idx++; } @@ -2460,14 +2598,23 @@ write_size_t(void *p, size_t n) } static void -fill_mem_debug(debug_alloc_api_t *api, void *data, int c, size_t nbytes) +fill_mem_debug(debug_alloc_api_t *api, void *data, int c, size_t nbytes, + bool is_alloc) { #ifdef Py_GIL_DISABLED if (api->api_id == 'o') { // Don't overwrite the first few bytes of a PyObject allocation in the // free-threaded build _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET(); - size_t debug_offset = tstate->mimalloc.current_object_heap->debug_offset; + size_t debug_offset; + if (is_alloc) { + debug_offset = tstate->mimalloc.current_object_heap->debug_offset; + } + else { + char *alloc = (char *)data - 2*SST; // start of the allocation + debug_offset = _mi_ptr_page(alloc)->debug_offset; + } + debug_offset -= 2*SST; // account for pymalloc extra bytes if (debug_offset < nbytes) { memset((char *)data + debug_offset, c, nbytes - debug_offset); } @@ -2553,7 +2700,7 @@ _PyMem_DebugRawAlloc(int use_calloc, void *ctx, size_t nbytes) memset(p + SST + 1, PYMEM_FORBIDDENBYTE, SST-1); if (nbytes > 0 && !use_calloc) { - fill_mem_debug(api, data, PYMEM_CLEANBYTE, nbytes); + fill_mem_debug(api, data, PYMEM_CLEANBYTE, nbytes, true); } /* at tail, write pad (SST bytes) and serialno (SST bytes) */ @@ -2603,7 +2750,7 @@ _PyMem_DebugRawFree(void *ctx, void *p) nbytes = read_size_t(q); nbytes += PYMEM_DEBUG_EXTRA_BYTES - 2*SST; memset(q, PYMEM_DEADBYTE, 2*SST); - fill_mem_debug(api, p, PYMEM_DEADBYTE, nbytes); + fill_mem_debug(api, p, PYMEM_DEADBYTE, nbytes, false); api->alloc.free(api->alloc.ctx, q); } diff --git a/Objects/setobject.c b/Objects/setobject.c index 6a4c8c45f0836d1..592711f305cbaf4 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -37,6 +37,7 @@ #include "pycore_dict.h" // _PyDict_Contains_KnownHash() #include "pycore_modsupport.h" // _PyArg_NoKwnames() #include "pycore_object.h" // _PyObject_GC_UNTRACK() +#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_SSIZE_RELAXED() #include "pycore_pyerrors.h" // _PyErr_SetKeyError() #include "pycore_setobject.h" // _PySet_NextEntry() definition #include // offsetof() @@ -130,6 +131,8 @@ set_add_entry(PySetObject *so, PyObject *key, Py_hash_t hash) int probes; int cmp; + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(so); + /* Pre-increment is necessary to prevent arbitrary code in the rich comparison from deallocating the key just before the insertion. */ Py_INCREF(key); @@ -523,7 +526,7 @@ set_dealloc(PySetObject *so) } static PyObject * -set_repr(PySetObject *so) +set_repr_lock_held(PySetObject *so) { PyObject *result=NULL, *keys, *listrepr, *tmp; int status = Py_ReprEnter((PyObject*)so); @@ -567,14 +570,24 @@ set_repr(PySetObject *so) return result; } +static PyObject * +set_repr(PySetObject *so) +{ + PyObject *result; + Py_BEGIN_CRITICAL_SECTION(so); + result = set_repr_lock_held(so); + Py_END_CRITICAL_SECTION(); + return result; +} + static Py_ssize_t -set_len(PyObject *so) +set_len(PySetObject *so) { - return ((PySetObject *)so)->used; + return FT_ATOMIC_LOAD_SSIZE_RELAXED(so->used); } static int -set_merge(PySetObject *so, PyObject *otherset) +set_merge_lock_held(PySetObject *so, PyObject *otherset) { PySetObject *other; PyObject *key; @@ -584,6 +597,8 @@ set_merge(PySetObject *so, PyObject *otherset) assert (PyAnySet_Check(so)); assert (PyAnySet_Check(otherset)); + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(so); + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(otherset); other = (PySetObject*)otherset; if (other == so || other->used == 0) @@ -645,6 +660,7 @@ set_merge(PySetObject *so, PyObject *otherset) } /*[clinic input] +@critical_section set.pop so: setobject @@ -655,7 +671,7 @@ Raises KeyError if the set is empty. static PyObject * set_pop_impl(PySetObject *so) -/*[clinic end generated code: output=4d65180f1271871b input=4a3f5552e660a260]*/ +/*[clinic end generated code: output=4d65180f1271871b input=9296c84921125060]*/ { /* Make sure the search finger is in bounds */ setentry *entry = so->table + (so->finger & so->mask); @@ -889,58 +905,60 @@ PyTypeObject PySetIter_Type = { static PyObject * set_iter(PySetObject *so) { + Py_ssize_t size = set_len(so); setiterobject *si = PyObject_GC_New(setiterobject, &PySetIter_Type); if (si == NULL) return NULL; si->si_set = (PySetObject*)Py_NewRef(so); - si->si_used = so->used; + si->si_used = size; si->si_pos = 0; - si->len = so->used; + si->len = size; _PyObject_GC_TRACK(si); return (PyObject *)si; } static int -set_update_internal(PySetObject *so, PyObject *other) +set_update_dict_lock_held(PySetObject *so, PyObject *other) { - PyObject *key, *it; + assert(PyDict_CheckExact(other)); - if (PyAnySet_Check(other)) - return set_merge(so, other); - - if (PyDict_CheckExact(other)) { - PyObject *value; - Py_ssize_t pos = 0; - Py_hash_t hash; - Py_ssize_t dictsize = PyDict_GET_SIZE(other); + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(so); + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(other); - /* Do one big resize at the start, rather than - * incrementally resizing as we insert new keys. Expect - * that there will be no (or few) overlapping keys. - */ - if (dictsize < 0) + /* Do one big resize at the start, rather than + * incrementally resizing as we insert new keys. Expect + * that there will be no (or few) overlapping keys. + */ + Py_ssize_t dictsize = PyDict_GET_SIZE(other); + if ((so->fill + dictsize)*5 >= so->mask*3) { + if (set_table_resize(so, (so->used + dictsize)*2) != 0) { return -1; - if ((so->fill + dictsize)*5 >= so->mask*3) { - if (set_table_resize(so, (so->used + dictsize)*2) != 0) - return -1; } - int err = 0; - Py_BEGIN_CRITICAL_SECTION(other); - while (_PyDict_Next(other, &pos, &key, &value, &hash)) { - if (set_add_entry(so, key, hash)) { - err = -1; - goto exit; - } + } + + Py_ssize_t pos = 0; + PyObject *key; + PyObject *value; + Py_hash_t hash; + while (_PyDict_Next(other, &pos, &key, &value, &hash)) { + if (set_add_entry(so, key, hash)) { + return -1; } -exit: - Py_END_CRITICAL_SECTION(); - return err; } + return 0; +} - it = PyObject_GetIter(other); - if (it == NULL) +static int +set_update_iterable_lock_held(PySetObject *so, PyObject *other) +{ + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(so); + + PyObject *it = PyObject_GetIter(other); + if (it == NULL) { return -1; + } + PyObject *key; while ((key = PyIter_Next(it)) != NULL) { if (set_add_key(so, key)) { Py_DECREF(it); @@ -955,6 +973,69 @@ set_update_internal(PySetObject *so, PyObject *other) return 0; } +static int +set_update_lock_held(PySetObject *so, PyObject *other) +{ + if (PyAnySet_Check(other)) { + return set_merge_lock_held(so, other); + } + else if (PyDict_CheckExact(other)) { + return set_update_dict_lock_held(so, other); + } + return set_update_iterable_lock_held(so, other); +} + +// set_update for a `so` that is only visible to the current thread +static int +set_update_local(PySetObject *so, PyObject *other) +{ + assert(Py_REFCNT(so) == 1); + if (PyAnySet_Check(other)) { + int rv; + Py_BEGIN_CRITICAL_SECTION(other); + rv = set_merge_lock_held(so, other); + Py_END_CRITICAL_SECTION(); + return rv; + } + else if (PyDict_CheckExact(other)) { + int rv; + Py_BEGIN_CRITICAL_SECTION(other); + rv = set_update_dict_lock_held(so, other); + Py_END_CRITICAL_SECTION(); + return rv; + } + return set_update_iterable_lock_held(so, other); +} + +static int +set_update_internal(PySetObject *so, PyObject *other) +{ + if (PyAnySet_Check(other)) { + if (Py_Is((PyObject *)so, other)) { + return 0; + } + int rv; + Py_BEGIN_CRITICAL_SECTION2(so, other); + rv = set_merge_lock_held(so, other); + Py_END_CRITICAL_SECTION2(); + return rv; + } + else if (PyDict_CheckExact(other)) { + int rv; + Py_BEGIN_CRITICAL_SECTION2(so, other); + rv = set_update_dict_lock_held(so, other); + Py_END_CRITICAL_SECTION2(); + return rv; + } + else { + int rv; + Py_BEGIN_CRITICAL_SECTION(so); + rv = set_update_iterable_lock_held(so, other); + Py_END_CRITICAL_SECTION(); + return rv; + } +} + /*[clinic input] set.update so: setobject @@ -1003,7 +1084,7 @@ make_new_set(PyTypeObject *type, PyObject *iterable) so->weakreflist = NULL; if (iterable != NULL) { - if (set_update_internal(so, iterable)) { + if (set_update_local(so, iterable)) { Py_DECREF(so); return NULL; } @@ -1126,6 +1207,7 @@ set_swap_bodies(PySetObject *a, PySetObject *b) } /*[clinic input] +@critical_section set.copy so: setobject @@ -1134,12 +1216,22 @@ Return a shallow copy of a set. static PyObject * set_copy_impl(PySetObject *so) -/*[clinic end generated code: output=c9223a1e1cc6b041 input=2b80b288d47b8cf1]*/ +/*[clinic end generated code: output=c9223a1e1cc6b041 input=c169a4fbb8209257]*/ { - return make_new_set_basetype(Py_TYPE(so), (PyObject *)so); + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(so); + PyObject *copy = make_new_set_basetype(Py_TYPE(so), NULL); + if (copy == NULL) { + return NULL; + } + if (set_merge_lock_held((PySetObject *)copy, (PyObject *)so) < 0) { + Py_DECREF(copy); + return NULL; + } + return copy; } /*[clinic input] +@critical_section frozenset.copy so: setobject @@ -1148,15 +1240,16 @@ Return a shallow copy of a set. static PyObject * frozenset_copy_impl(PySetObject *so) -/*[clinic end generated code: output=b356263526af9e70 input=3dc65577d344eff7]*/ +/*[clinic end generated code: output=b356263526af9e70 input=fbf5bef131268dd7]*/ { if (PyFrozenSet_CheckExact(so)) { return Py_NewRef(so); } - return set_copy(so, NULL); + return set_copy_impl(so); } /*[clinic input] +@critical_section set.clear so: setobject @@ -1165,7 +1258,7 @@ Remove all elements from this set. static PyObject * set_clear_impl(PySetObject *so) -/*[clinic end generated code: output=4e71d5a83904161a input=74ac19794da81a39]*/ +/*[clinic end generated code: output=4e71d5a83904161a input=c6f831b366111950]*/ { set_clear_internal(so); Py_RETURN_NONE; @@ -1196,7 +1289,7 @@ set_union_impl(PySetObject *so, PyObject *args) other = PyTuple_GET_ITEM(args, i); if ((PyObject *)so == other) continue; - if (set_update_internal(result, other)) { + if (set_update_local(result, other)) { Py_DECREF(result); return NULL; } @@ -1213,11 +1306,13 @@ set_or(PySetObject *so, PyObject *other) Py_RETURN_NOTIMPLEMENTED; result = (PySetObject *)set_copy(so, NULL); - if (result == NULL) + if (result == NULL) { return NULL; - if ((PyObject *)so == other) + } + if (Py_Is((PyObject *)so, other)) { return (PyObject *)result; - if (set_update_internal(result, other)) { + } + if (set_update_local(result, other)) { Py_DECREF(result); return NULL; } @@ -1230,8 +1325,9 @@ set_ior(PySetObject *so, PyObject *other) if (!PyAnySet_Check(other)) Py_RETURN_NOTIMPLEMENTED; - if (set_update_internal(so, other)) + if (set_update_internal(so, other)) { return NULL; + } return Py_NewRef(so); } @@ -1244,7 +1340,7 @@ set_intersection(PySetObject *so, PyObject *other) int rv; if ((PyObject *)so == other) - return set_copy(so, NULL); + return set_copy_impl(so); result = (PySetObject *)make_new_set_basetype(Py_TYPE(so), NULL); if (result == NULL) @@ -1333,13 +1429,17 @@ set_intersection_multi_impl(PySetObject *so, PyObject *args) { Py_ssize_t i; - if (PyTuple_GET_SIZE(args) == 0) + if (PyTuple_GET_SIZE(args) == 0) { return set_copy(so, NULL); + } PyObject *result = Py_NewRef(so); for (i=0 ; ikey); + Py_hash_t hash = entry->hash; + int rv = set_discard_entry(so, key, hash); + if (rv < 0) { + Py_DECREF(key); + return -1; + } + if (rv == DISCARD_NOTFOUND) { + if (set_add_entry(so, key, hash)) { + Py_DECREF(key); + return -1; + } + } + Py_DECREF(key); + } + return 0; } /*[clinic input] @@ -1739,58 +1905,41 @@ static PyObject * set_symmetric_difference_update(PySetObject *so, PyObject *other) /*[clinic end generated code: output=fbb049c0806028de input=a50acf0365e1f0a5]*/ { - PySetObject *otherset; - PyObject *key; - Py_ssize_t pos = 0; - Py_hash_t hash; - setentry *entry; - int rv; - - if ((PyObject *)so == other) + if (Py_Is((PyObject *)so, other)) { return set_clear(so, NULL); + } + int rv; if (PyDict_CheckExact(other)) { - PyObject *res; - - Py_BEGIN_CRITICAL_SECTION(other); - res = set_symmetric_difference_update_dict(so, other); - Py_END_CRITICAL_SECTION(); - - return res; + Py_BEGIN_CRITICAL_SECTION2(so, other); + rv = set_symmetric_difference_update_dict(so, other); + Py_END_CRITICAL_SECTION2(); } - - if (PyAnySet_Check(other)) { - otherset = (PySetObject *)Py_NewRef(other); - } else { - otherset = (PySetObject *)make_new_set_basetype(Py_TYPE(so), other); - if (otherset == NULL) - return NULL; + else if (PyAnySet_Check(other)) { + Py_BEGIN_CRITICAL_SECTION2(so, other); + rv = set_symmetric_difference_update_set(so, (PySetObject *)other); + Py_END_CRITICAL_SECTION2(); } - - while (set_next(otherset, &pos, &entry)) { - key = entry->key; - hash = entry->hash; - Py_INCREF(key); - rv = set_discard_entry(so, key, hash); - if (rv < 0) { - Py_DECREF(otherset); - Py_DECREF(key); + else { + PySetObject *otherset = (PySetObject *)make_new_set_basetype(Py_TYPE(so), other); + if (otherset == NULL) { return NULL; } - if (rv == DISCARD_NOTFOUND) { - if (set_add_entry(so, key, hash)) { - Py_DECREF(otherset); - Py_DECREF(key); - return NULL; - } - } - Py_DECREF(key); + + Py_BEGIN_CRITICAL_SECTION(so); + rv = set_symmetric_difference_update_set(so, otherset); + Py_END_CRITICAL_SECTION(); + + Py_DECREF(otherset); + } + if (rv < 0) { + return NULL; } - Py_DECREF(otherset); Py_RETURN_NONE; } /*[clinic input] +@critical_section so other set.symmetric_difference so: setobject other: object @@ -1800,22 +1949,22 @@ Return a new set with elements in either the set or other but not both. [clinic start generated code]*/ static PyObject * -set_symmetric_difference(PySetObject *so, PyObject *other) -/*[clinic end generated code: output=f95364211b88775a input=f18af370ad72ebac]*/ +set_symmetric_difference_impl(PySetObject *so, PyObject *other) +/*[clinic end generated code: output=270ee0b5d42b0797 input=624f6e7bbdf70db1]*/ { - PyObject *rv; - PySetObject *otherset; - - otherset = (PySetObject *)make_new_set_basetype(Py_TYPE(so), other); - if (otherset == NULL) + PySetObject *result = (PySetObject *)make_new_set_basetype(Py_TYPE(so), NULL); + if (result == NULL) { return NULL; - rv = set_symmetric_difference_update(otherset, (PyObject *)so); - if (rv == NULL) { - Py_DECREF(otherset); + } + if (set_update_lock_held(result, other) < 0) { + Py_DECREF(result); return NULL; } - Py_DECREF(rv); - return (PyObject *)otherset; + if (set_symmetric_difference_update_set(result, so) < 0) { + Py_DECREF(result); + return NULL; + } + return (PyObject *)result; } static PyObject * @@ -1841,6 +1990,7 @@ set_ixor(PySetObject *so, PyObject *other) } /*[clinic input] +@critical_section so other set.issubset so: setobject other: object @@ -1850,8 +2000,8 @@ Report whether another set contains this set. [clinic start generated code]*/ static PyObject * -set_issubset(PySetObject *so, PyObject *other) -/*[clinic end generated code: output=78aef1f377aedef1 input=37fbc579b609db0c]*/ +set_issubset_impl(PySetObject *so, PyObject *other) +/*[clinic end generated code: output=b2b59d5f314555ce input=f2a4fd0f2537758b]*/ { setentry *entry; Py_ssize_t pos = 0; @@ -1885,6 +2035,7 @@ set_issubset(PySetObject *so, PyObject *other) } /*[clinic input] +@critical_section so other set.issuperset so: setobject other: object @@ -1894,8 +2045,8 @@ Report whether this set contains another set. [clinic start generated code]*/ static PyObject * -set_issuperset(PySetObject *so, PyObject *other) -/*[clinic end generated code: output=7d2b71dd714a7ec7 input=fd5dab052f2e9bb3]*/ +set_issuperset_impl(PySetObject *so, PyObject *other) +/*[clinic end generated code: output=ecf00ce552c09461 input=5f2e1f262e6e4ccc]*/ { if (PyAnySet_Check(other)) { return set_issubset((PySetObject *)other, (PyObject *)so); @@ -1924,6 +2075,7 @@ set_issuperset(PySetObject *so, PyObject *other) Py_RETURN_TRUE; } +// TODO: Make thread-safe in free-threaded builds static PyObject * set_richcompare(PySetObject *v, PyObject *w, int op) { @@ -1968,6 +2120,7 @@ set_richcompare(PySetObject *v, PyObject *w, int op) } /*[clinic input] +@critical_section set.add so: setobject object as key: object @@ -1979,8 +2132,8 @@ This has no effect if the element is already present. [clinic start generated code]*/ static PyObject * -set_add(PySetObject *so, PyObject *key) -/*[clinic end generated code: output=cd9c2d5c2069c2ba input=96f1efe029e47972]*/ +set_add_impl(PySetObject *so, PyObject *key) +/*[clinic end generated code: output=4cc4a937f1425c96 input=03baf62cb0e66514]*/ { if (set_add_key(so, key)) return NULL; @@ -1988,7 +2141,7 @@ set_add(PySetObject *so, PyObject *key) } static int -set_contains(PySetObject *so, PyObject *key) +set_contains_lock_held(PySetObject *so, PyObject *key) { PyObject *tmpkey; int rv; @@ -2007,7 +2160,18 @@ set_contains(PySetObject *so, PyObject *key) return rv; } +int +_PySet_Contains(PySetObject *so, PyObject *key) +{ + int rv; + Py_BEGIN_CRITICAL_SECTION(so); + rv = set_contains_lock_held(so, key); + Py_END_CRITICAL_SECTION(); + return rv; +} + /*[clinic input] +@critical_section @coexist set.__contains__ so: setobject @@ -2018,18 +2182,19 @@ x.__contains__(y) <==> y in x. [clinic start generated code]*/ static PyObject * -set___contains__(PySetObject *so, PyObject *key) -/*[clinic end generated code: output=b5948bc5c590d3ca input=cf4c72db704e4cf0]*/ +set___contains___impl(PySetObject *so, PyObject *key) +/*[clinic end generated code: output=b44863d034b3c70e input=4a7d568459617f24]*/ { long result; - result = set_contains(so, key); + result = set_contains_lock_held(so, key); if (result < 0) return NULL; return PyBool_FromLong(result); } /*[clinic input] +@critical_section set.remove so: setobject object as key: object @@ -2041,8 +2206,8 @@ If the element is not a member, raise a KeyError. [clinic start generated code]*/ static PyObject * -set_remove(PySetObject *so, PyObject *key) -/*[clinic end generated code: output=08ae496d0cd2b8c1 input=10132515dfe8ebd7]*/ +set_remove_impl(PySetObject *so, PyObject *key) +/*[clinic end generated code: output=0b9134a2a2200363 input=893e1cb1df98227a]*/ { PyObject *tmpkey; int rv; @@ -2069,6 +2234,7 @@ set_remove(PySetObject *so, PyObject *key) } /*[clinic input] +@critical_section set.discard so: setobject object as key: object @@ -2081,8 +2247,8 @@ an exception when an element is missing from the set. [clinic start generated code]*/ static PyObject * -set_discard(PySetObject *so, PyObject *key) -/*[clinic end generated code: output=9181b60d7bb7d480 input=82a689eba94d5ad9]*/ +set_discard_impl(PySetObject *so, PyObject *key) +/*[clinic end generated code: output=eec3b687bf32759e input=861cb7fb69b4def0]*/ { PyObject *tmpkey; int rv; @@ -2104,6 +2270,7 @@ set_discard(PySetObject *so, PyObject *key) } /*[clinic input] +@critical_section set.__reduce__ so: setobject @@ -2112,7 +2279,7 @@ Return state information for pickling. static PyObject * set___reduce___impl(PySetObject *so) -/*[clinic end generated code: output=9af7d0e029df87ee input=531375e87a24a449]*/ +/*[clinic end generated code: output=9af7d0e029df87ee input=59405a4249e82f71]*/ { PyObject *keys=NULL, *args=NULL, *result=NULL, *state=NULL; @@ -2134,6 +2301,7 @@ set___reduce___impl(PySetObject *so) } /*[clinic input] +@critical_section set.__sizeof__ so: setobject @@ -2142,7 +2310,7 @@ S.__sizeof__() -> size of S in memory, in bytes. static PyObject * set___sizeof___impl(PySetObject *so) -/*[clinic end generated code: output=4bfa3df7bd38ed88 input=0f214fc2225319fc]*/ +/*[clinic end generated code: output=4bfa3df7bd38ed88 input=09e1a09f168eaa23]*/ { size_t res = _PyObject_SIZE(Py_TYPE(so)); if (so->table != so->smalltable) { @@ -2156,13 +2324,17 @@ set_init(PySetObject *self, PyObject *args, PyObject *kwds) { PyObject *iterable = NULL; - if (!_PyArg_NoKeywords("set", kwds)) + if (!_PyArg_NoKeywords("set", kwds)) return -1; if (!PyArg_UnpackTuple(args, Py_TYPE(self)->tp_name, 0, 1, &iterable)) return -1; + + Py_BEGIN_CRITICAL_SECTION(self); if (self->fill) set_clear_internal(self); self->hash = -1; + Py_END_CRITICAL_SECTION(); + if (iterable == NULL) return 0; return set_update_internal(self, iterable); @@ -2191,14 +2363,14 @@ set_vectorcall(PyObject *type, PyObject * const*args, } static PySequenceMethods set_as_sequence = { - set_len, /* sq_length */ + (lenfunc)set_len, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ 0, /* sq_item */ 0, /* sq_slice */ 0, /* sq_ass_item */ 0, /* sq_ass_slice */ - (objobjproc)set_contains, /* sq_contains */ + (objobjproc)_PySet_Contains, /* sq_contains */ }; /* set object ********************************************************/ @@ -2424,7 +2596,7 @@ PySet_Size(PyObject *anyset) PyErr_BadInternalCall(); return -1; } - return PySet_GET_SIZE(anyset); + return set_len((PySetObject *)anyset); } int @@ -2434,7 +2606,8 @@ PySet_Clear(PyObject *set) PyErr_BadInternalCall(); return -1; } - return set_clear_internal((PySetObject *)set); + (void)set_clear((PySetObject *)set, NULL); + return 0; } int @@ -2444,7 +2617,12 @@ PySet_Contains(PyObject *anyset, PyObject *key) PyErr_BadInternalCall(); return -1; } - return set_contains_key((PySetObject *)anyset, key); + + int rv; + Py_BEGIN_CRITICAL_SECTION(anyset); + rv = set_contains_key((PySetObject *)anyset, key); + Py_END_CRITICAL_SECTION(); + return rv; } int @@ -2454,7 +2632,12 @@ PySet_Discard(PyObject *set, PyObject *key) PyErr_BadInternalCall(); return -1; } - return set_discard_key((PySetObject *)set, key); + + int rv; + Py_BEGIN_CRITICAL_SECTION(set); + rv = set_discard_key((PySetObject *)set, key); + Py_END_CRITICAL_SECTION(); + return rv; } int @@ -2465,9 +2648,15 @@ PySet_Add(PyObject *anyset, PyObject *key) PyErr_BadInternalCall(); return -1; } - return set_add_key((PySetObject *)anyset, key); + + int rv; + Py_BEGIN_CRITICAL_SECTION(anyset); + rv = set_add_key((PySetObject *)anyset, key); + Py_END_CRITICAL_SECTION(); + return rv; } +// TODO: Make thread-safe in free-threaded builds int _PySet_NextEntry(PyObject *set, Py_ssize_t *pos, PyObject **key, Py_hash_t *hash) { diff --git a/Objects/typeobject.c b/Objects/typeobject.c index fe3b7b87c8b4b68..24f314929851649 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -373,12 +373,23 @@ lookup_tp_mro(PyTypeObject *self) PyObject * _PyType_GetMRO(PyTypeObject *self) { - PyObject *mro; +#ifdef Py_GIL_DISABLED + PyObject *mro = _Py_atomic_load_ptr_relaxed(&self->tp_mro); + if (mro == NULL) { + return NULL; + } + if (_Py_TryIncref(&self->tp_mro, mro)) { + return mro; + } + BEGIN_TYPE_LOCK(); mro = lookup_tp_mro(self); - Py_INCREF(mro); + Py_XINCREF(mro); END_TYPE_LOCK() return mro; +#else + return Py_XNewRef(lookup_tp_mro(self)); +#endif } static inline void @@ -911,7 +922,7 @@ PyType_Modified(PyTypeObject *type) } static int -is_subtype_unlocked(PyTypeObject *a, PyTypeObject *b); +is_subtype_with_mro(PyObject *a_mro, PyTypeObject *a, PyTypeObject *b); static void type_mro_modified(PyTypeObject *type, PyObject *bases) { @@ -957,7 +968,7 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) { PyObject *b = PyTuple_GET_ITEM(bases, i); PyTypeObject *cls = _PyType_CAST(b); - if (!is_subtype_unlocked(type, cls)) { + if (!is_subtype_with_mro(lookup_tp_mro(type), type, cls)) { goto clear; } } @@ -1164,10 +1175,9 @@ type_set_qualname(PyTypeObject *type, PyObject *value, void *context) } static PyObject * -type_module(PyTypeObject *type, void *context) +type_module(PyTypeObject *type) { PyObject *mod; - if (type->tp_flags & Py_TPFLAGS_HEAPTYPE) { PyObject *dict = lookup_tp_dict(type); if (PyDict_GetItemRef(dict, &_Py_ID(__module__), &mod) == 0) { @@ -1189,6 +1199,12 @@ type_module(PyTypeObject *type, void *context) return mod; } +static PyObject * +type_get_module(PyTypeObject *type, void *context) +{ + return type_module(type); +} + static int type_set_module(PyTypeObject *type, PyObject *value, void *context) { @@ -1201,6 +1217,47 @@ type_set_module(PyTypeObject *type, PyObject *value, void *context) return PyDict_SetItem(dict, &_Py_ID(__module__), value); } + +PyObject * +_PyType_GetFullyQualifiedName(PyTypeObject *type, char sep) +{ + if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) { + return PyUnicode_FromString(type->tp_name); + } + + PyObject *qualname = type_qualname(type, NULL); + if (qualname == NULL) { + return NULL; + } + + PyObject *module = type_module(type); + if (module == NULL) { + Py_DECREF(qualname); + return NULL; + } + + PyObject *result; + if (PyUnicode_Check(module) + && !_PyUnicode_Equal(module, &_Py_ID(builtins)) + && !_PyUnicode_Equal(module, &_Py_ID(__main__))) + { + result = PyUnicode_FromFormat("%U%c%U", module, sep, qualname); + } + else { + result = Py_NewRef(qualname); + } + Py_DECREF(module); + Py_DECREF(qualname); + return result; +} + +PyObject * +PyType_GetFullyQualifiedName(PyTypeObject *type) +{ + return _PyType_GetFullyQualifiedName(type, '.'); +} + + static PyObject * type_abstractmethods(PyTypeObject *type, void *context) { @@ -1236,20 +1293,22 @@ type_set_abstractmethods(PyTypeObject *type, PyObject *value, void *context) } else { abstract = 0; - res = PyDict_DelItem(dict, &_Py_ID(__abstractmethods__)); - if (res && PyErr_ExceptionMatches(PyExc_KeyError)) { + res = PyDict_Pop(dict, &_Py_ID(__abstractmethods__), NULL); + if (res == 0) { PyErr_SetObject(PyExc_AttributeError, &_Py_ID(__abstractmethods__)); return -1; } } - if (res == 0) { - PyType_Modified(type); - if (abstract) - type->tp_flags |= Py_TPFLAGS_IS_ABSTRACT; - else - type->tp_flags &= ~Py_TPFLAGS_IS_ABSTRACT; + if (res < 0) { + return -1; } - return res; + + PyType_Modified(type); + if (abstract) + type->tp_flags |= Py_TPFLAGS_IS_ABSTRACT; + else + type->tp_flags &= ~Py_TPFLAGS_IS_ABSTRACT; + return 0; } static PyObject * @@ -1394,7 +1453,7 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, void *context) } PyTypeObject *base = (PyTypeObject*)ob; - if (is_subtype_unlocked(base, type) || + if (is_subtype_with_mro(lookup_tp_mro(base), base, type) || /* In case of reentering here again through a custom mro() the above check is not enough since it relies on base->tp_mro which would gonna be updated inside @@ -1606,16 +1665,18 @@ type_set_annotations(PyTypeObject *type, PyObject *value, void *context) result = PyDict_SetItem(dict, &_Py_ID(__annotations__), value); } else { /* delete */ - result = PyDict_DelItem(dict, &_Py_ID(__annotations__)); - if (result < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) { + result = PyDict_Pop(dict, &_Py_ID(__annotations__), NULL); + if (result == 0) { PyErr_SetString(PyExc_AttributeError, "__annotations__"); + return -1; } } - - if (result == 0) { - PyType_Modified(type); + if (result < 0) { + return -1; } - return result; + + PyType_Modified(type); + return 0; } static PyObject * @@ -1683,7 +1744,7 @@ static PyGetSetDef type_getsets[] = { {"__qualname__", (getter)type_qualname, (setter)type_set_qualname, NULL}, {"__bases__", (getter)type_get_bases, (setter)type_set_bases, NULL}, {"__mro__", (getter)type_get_mro, NULL, NULL}, - {"__module__", (getter)type_module, (setter)type_set_module, NULL}, + {"__module__", (getter)type_get_module, (setter)type_set_module, NULL}, {"__abstractmethods__", (getter)type_abstractmethods, (setter)type_set_abstractmethods, NULL}, {"__dict__", (getter)type_dict, NULL, NULL}, @@ -1704,28 +1765,31 @@ type_repr(PyObject *self) return PyUnicode_FromFormat("", type); } - PyObject *mod, *name, *rtn; - - mod = type_module(type, NULL); - if (mod == NULL) + PyObject *mod = type_module(type); + if (mod == NULL) { PyErr_Clear(); + } else if (!PyUnicode_Check(mod)) { - Py_SETREF(mod, NULL); + Py_CLEAR(mod); } - name = type_qualname(type, NULL); + + PyObject *name = type_qualname(type, NULL); if (name == NULL) { Py_XDECREF(mod); return NULL; } - if (mod != NULL && !_PyUnicode_Equal(mod, &_Py_ID(builtins))) - rtn = PyUnicode_FromFormat("", mod, name); - else - rtn = PyUnicode_FromFormat("", type->tp_name); - + PyObject *result; + if (mod != NULL && !_PyUnicode_Equal(mod, &_Py_ID(builtins))) { + result = PyUnicode_FromFormat("", mod, name); + } + else { + result = PyUnicode_FromFormat("", type->tp_name); + } Py_XDECREF(mod); Py_DECREF(name); - return rtn; + + return result; } static PyObject * @@ -2250,37 +2314,41 @@ type_is_subtype_base_chain(PyTypeObject *a, PyTypeObject *b) } static int -is_subtype_unlocked(PyTypeObject *a, PyTypeObject *b) +is_subtype_with_mro(PyObject *a_mro, PyTypeObject *a, PyTypeObject *b) { - PyObject *mro; - - ASSERT_TYPE_LOCK_HELD(); - mro = lookup_tp_mro(a); - if (mro != NULL) { + int res; + if (a_mro != NULL) { /* Deal with multiple inheritance without recursion by walking the MRO tuple */ Py_ssize_t i, n; - assert(PyTuple_Check(mro)); - n = PyTuple_GET_SIZE(mro); + assert(PyTuple_Check(a_mro)); + n = PyTuple_GET_SIZE(a_mro); + res = 0; for (i = 0; i < n; i++) { - if (PyTuple_GET_ITEM(mro, i) == (PyObject *)b) - return 1; + if (PyTuple_GET_ITEM(a_mro, i) == (PyObject *)b) { + res = 1; + break; + } } - return 0; } - else + else { /* a is not completely initialized yet; follow tp_base */ - return type_is_subtype_base_chain(a, b); + res = type_is_subtype_base_chain(a, b); + } + return res; } int PyType_IsSubtype(PyTypeObject *a, PyTypeObject *b) { - int res; - BEGIN_TYPE_LOCK(); - res = is_subtype_unlocked(a, b); - END_TYPE_LOCK() +#ifdef Py_GIL_DISABLED + PyObject *mro = _PyType_GetMRO(a); + int res = is_subtype_with_mro(mro, a, b); + Py_XDECREF(mro); return res; +#else + return is_subtype_with_mro(lookup_tp_mro(a), a, b); +#endif } /* Routines to do a method lookup in the type without looking in the @@ -2773,7 +2841,7 @@ mro_check(PyTypeObject *type, PyObject *mro) } PyTypeObject *base = (PyTypeObject*)obj; - if (!is_subtype_unlocked(solid, solid_base(base))) { + if (!is_subtype_with_mro(lookup_tp_mro(solid), solid, solid_base(base))) { PyErr_Format( PyExc_TypeError, "mro() returned base with unsuitable layout ('%.500s')", @@ -4692,9 +4760,9 @@ PyType_GetQualName(PyTypeObject *type) } PyObject * -_PyType_GetModuleName(PyTypeObject *type) +PyType_GetModuleName(PyTypeObject *type) { - return type_module(type, NULL); + return type_module(type); } void * @@ -5808,7 +5876,7 @@ object_repr(PyObject *self) PyObject *mod, *name, *rtn; type = Py_TYPE(self); - mod = type_module(type, NULL); + mod = type_module(type); if (mod == NULL) PyErr_Clear(); else if (!PyUnicode_Check(mod)) { @@ -6549,6 +6617,7 @@ reduce_newobj(PyObject *obj) } else { /* args == NULL */ + Py_DECREF(copyreg); Py_DECREF(kwargs); PyErr_BadInternalCall(); return NULL; @@ -7028,28 +7097,29 @@ inherit_special(PyTypeObject *type, PyTypeObject *base) #undef COPYVAL /* Setup fast subclass flags */ - if (is_subtype_unlocked(base, (PyTypeObject*)PyExc_BaseException)) { + PyObject *mro = lookup_tp_mro(base); + if (is_subtype_with_mro(mro, base, (PyTypeObject*)PyExc_BaseException)) { type->tp_flags |= Py_TPFLAGS_BASE_EXC_SUBCLASS; } - else if (is_subtype_unlocked(base, &PyType_Type)) { + else if (is_subtype_with_mro(mro, base, &PyType_Type)) { type->tp_flags |= Py_TPFLAGS_TYPE_SUBCLASS; } - else if (is_subtype_unlocked(base, &PyLong_Type)) { + else if (is_subtype_with_mro(mro, base, &PyLong_Type)) { type->tp_flags |= Py_TPFLAGS_LONG_SUBCLASS; } - else if (is_subtype_unlocked(base, &PyBytes_Type)) { + else if (is_subtype_with_mro(mro, base, &PyBytes_Type)) { type->tp_flags |= Py_TPFLAGS_BYTES_SUBCLASS; } - else if (is_subtype_unlocked(base, &PyUnicode_Type)) { + else if (is_subtype_with_mro(mro, base, &PyUnicode_Type)) { type->tp_flags |= Py_TPFLAGS_UNICODE_SUBCLASS; } - else if (is_subtype_unlocked(base, &PyTuple_Type)) { + else if (is_subtype_with_mro(mro, base, &PyTuple_Type)) { type->tp_flags |= Py_TPFLAGS_TUPLE_SUBCLASS; } - else if (is_subtype_unlocked(base, &PyList_Type)) { + else if (is_subtype_with_mro(mro, base, &PyList_Type)) { type->tp_flags |= Py_TPFLAGS_LIST_SUBCLASS; } - else if (is_subtype_unlocked(base, &PyDict_Type)) { + else if (is_subtype_with_mro(mro, base, &PyDict_Type)) { type->tp_flags |= Py_TPFLAGS_DICT_SUBCLASS; } @@ -10150,7 +10220,7 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p) d = (PyWrapperDescrObject *)descr; if ((specific == NULL || specific == d->d_wrapped) && d->d_base->wrapper == p->wrapper && - is_subtype_unlocked(type, PyDescr_TYPE(d))) + is_subtype_with_mro(lookup_tp_mro(type), type, PyDescr_TYPE(d))) { specific = d->d_wrapped; } diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 0a569a950e88e29..c8f647a7a71135b 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -2791,6 +2791,64 @@ unicode_fromformat_arg(_PyUnicodeWriter *writer, break; } + case 'T': + { + PyObject *obj = va_arg(*vargs, PyObject *); + PyTypeObject *type = (PyTypeObject *)Py_NewRef(Py_TYPE(obj)); + + PyObject *type_name; + if (f[1] == '#') { + type_name = _PyType_GetFullyQualifiedName(type, ':'); + f++; + } + else { + type_name = PyType_GetFullyQualifiedName(type); + } + Py_DECREF(type); + if (!type_name) { + return NULL; + } + + if (unicode_fromformat_write_str(writer, type_name, + width, precision, flags) == -1) { + Py_DECREF(type_name); + return NULL; + } + Py_DECREF(type_name); + break; + } + + case 'N': + { + PyObject *type_raw = va_arg(*vargs, PyObject *); + assert(type_raw != NULL); + + if (!PyType_Check(type_raw)) { + PyErr_SetString(PyExc_TypeError, "%N argument must be a type"); + return NULL; + } + PyTypeObject *type = (PyTypeObject*)type_raw; + + PyObject *type_name; + if (f[1] == '#') { + type_name = _PyType_GetFullyQualifiedName(type, ':'); + f++; + } + else { + type_name = PyType_GetFullyQualifiedName(type); + } + if (!type_name) { + return NULL; + } + if (unicode_fromformat_write_str(writer, type_name, + width, precision, flags) == -1) { + Py_DECREF(type_name); + return NULL; + } + Py_DECREF(type_name); + break; + } + default: invalid_format: PyErr_Format(PyExc_SystemError, "invalid format string: %s", p); diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index df74be6aba72445..b7b29064151609f 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -801,24 +801,14 @@ PyWeakref_NewRef(PyObject *ob, PyObject *callback) if (result != NULL) Py_INCREF(result); else { - /* Note: new_weakref() can trigger cyclic GC, so the weakref - list on ob can be mutated. This means that the ref and - proxy pointers we got back earlier may have been collected, - so we need to compute these values again before we use - them. */ + /* We do not need to recompute ref/proxy; new_weakref() cannot + trigger GC. + */ result = new_weakref(ob, callback); if (result != NULL) { - get_basic_refs(*list, &ref, &proxy); if (callback == NULL) { - if (ref == NULL) - insert_head(result, list); - else { - /* Someone else added a ref without a callback - during GC. Return that one instead of this one - to avoid violating the invariants of the list - of weakrefs for ob. */ - Py_SETREF(result, (PyWeakReference*)Py_NewRef(ref)); - } + assert(ref == NULL); + insert_head(result, list); } else { PyWeakReference *prev; @@ -858,11 +848,9 @@ PyWeakref_NewProxy(PyObject *ob, PyObject *callback) if (result != NULL) Py_INCREF(result); else { - /* Note: new_weakref() can trigger cyclic GC, so the weakref - list on ob can be mutated. This means that the ref and - proxy pointers we got back earlier may have been collected, - so we need to compute these values again before we use - them. */ + /* We do not need to recompute ref/proxy; new_weakref cannot + trigger GC. + */ result = new_weakref(ob, callback); if (result != NULL) { PyWeakReference *prev; @@ -873,16 +861,7 @@ PyWeakref_NewProxy(PyObject *ob, PyObject *callback) else { Py_SET_TYPE(result, &_PyWeakref_ProxyType); } - get_basic_refs(*list, &ref, &proxy); if (callback == NULL) { - if (proxy != NULL) { - /* Someone else added a proxy without a callback - during GC. Return that one instead of this one - to avoid violating the invariants of the list - of weakrefs for ob. */ - Py_SETREF(result, (PyWeakReference*)Py_NewRef(proxy)); - goto skip_insert; - } prev = ref; } else @@ -892,8 +871,6 @@ PyWeakref_NewProxy(PyObject *ob, PyObject *callback) insert_head(result, list); else insert_after(result, prev); - skip_insert: - ; } } return (PyObject *) result; diff --git a/PC/pyconfig.h.in b/PC/pyconfig.h.in index 8bbf877a5bb5edc..d72d6282c2806f1 100644 --- a/PC/pyconfig.h.in +++ b/PC/pyconfig.h.in @@ -95,7 +95,12 @@ WIN32 is still required for the locale module. #endif /* Py_BUILD_CORE || Py_BUILD_CORE_BUILTIN || Py_BUILD_CORE_MODULE */ /* Define to 1 if you want to disable the GIL */ -#undef Py_GIL_DISABLED +/* Uncomment the definition for free-threaded builds, or define it manually + * when compiling extension modules. Note that we test with #ifdef, so + * defining as 0 will still disable the GIL. */ +#ifndef Py_GIL_DISABLED +/* #define Py_GIL_DISABLED 1 */ +#endif /* Compiler specific defines */ diff --git a/PC/python3dll.c b/PC/python3dll.c index aa6bfe2c4022db0..dbfa3f23bb586df 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -637,7 +637,9 @@ EXPORT_FUNC(PyType_FromSpecWithBases) EXPORT_FUNC(PyType_GenericAlloc) EXPORT_FUNC(PyType_GenericNew) EXPORT_FUNC(PyType_GetFlags) +EXPORT_FUNC(PyType_GetFullyQualifiedName) EXPORT_FUNC(PyType_GetModule) +EXPORT_FUNC(PyType_GetModuleName) EXPORT_FUNC(PyType_GetModuleState) EXPORT_FUNC(PyType_GetName) EXPORT_FUNC(PyType_GetQualName) diff --git a/PC/winsound.c b/PC/winsound.c index 7e4ebd90f50c2e4..a6b2dac6ac14669 100644 --- a/PC/winsound.c +++ b/PC/winsound.c @@ -35,11 +35,10 @@ winsound.PlaySound(None, 0) */ +// Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED #include "pyconfig.h" // Py_GIL_DISABLED - #ifndef Py_GIL_DISABLED -// Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED -#define Py_LIMITED_API 0x030c0000 +# define Py_LIMITED_API 0x030c0000 #endif #include diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index 00ad3e2472af04c..3a8a417a6bf47a0 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -235,6 +235,7 @@ + diff --git a/PCbuild/_freeze_module.vcxproj.filters b/PCbuild/_freeze_module.vcxproj.filters index aea5f730607658e..5b34440af9322b5 100644 --- a/PCbuild/_freeze_module.vcxproj.filters +++ b/PCbuild/_freeze_module.vcxproj.filters @@ -310,6 +310,9 @@ Source Files + + Python + Source Files diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj index 66df0a61b5b5a66..6522cb1fcf5c63d 100644 --- a/PCbuild/_testcapi.vcxproj +++ b/PCbuild/_testcapi.vcxproj @@ -96,12 +96,8 @@ - - - - @@ -120,10 +116,8 @@ - - diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters index 651eb1d6ba0b7f3..772a9a861517ecd 100644 --- a/PCbuild/_testcapi.vcxproj.filters +++ b/PCbuild/_testcapi.vcxproj.filters @@ -30,12 +30,6 @@ Source Files - - Source Files - - - Source Files - Source Files @@ -90,18 +84,12 @@ Source Files - - Source Files - Source Files Source Files - - Source Files - Source Files diff --git a/PCbuild/_testlimitedcapi.vcxproj b/PCbuild/_testlimitedcapi.vcxproj new file mode 100644 index 000000000000000..1afeacaa93396e0 --- /dev/null +++ b/PCbuild/_testlimitedcapi.vcxproj @@ -0,0 +1,120 @@ + + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + PGInstrument + ARM + + + PGInstrument + ARM64 + + + PGInstrument + Win32 + + + PGInstrument + x64 + + + PGUpdate + ARM + + + PGUpdate + ARM64 + + + PGUpdate + Win32 + + + PGUpdate + x64 + + + Release + ARM + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933} + _testlimitedcapi + Win32Proj + false + + + + + DynamicLibrary + NotSet + + + + $(PyStdlibPydExt) + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + + + + + + + + + + + + + + + + {cf7ac3d1-e2df-41d2-bea6-1e2556cdea26} + false + + + {885d4898-d08d-4091-9c40-c700cfe3fc5a} + false + + + + + + diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters b/PCbuild/_testlimitedcapi.vcxproj.filters new file mode 100644 index 000000000000000..b3eeb86185c3729 --- /dev/null +++ b/PCbuild/_testlimitedcapi.vcxproj.filters @@ -0,0 +1,25 @@ + + + + + {a76a90d8-8e8b-4c36-8f58-8bd46abe9f5e} + + + {071b2ff4-e5a1-4e79-b0c5-cf46b0094a80} + + + + + + + + + + + + + + Resource Files + + + diff --git a/PCbuild/find_python.bat b/PCbuild/find_python.bat index d3f62c93869003c..af85f6d362466ec 100644 --- a/PCbuild/find_python.bat +++ b/PCbuild/find_python.bat @@ -29,6 +29,9 @@ :begin_search @set PYTHON= +@rem If PYTHON_FOR_BUILD is set, use that +@if NOT "%PYTHON_FOR_BUILD%"=="" @(set PYTHON="%PYTHON_FOR_BUILD%") && (set _Py_Python_Source=found as PYTHON_FOR_BUILD) && goto :found + @rem If there is an active virtual env, use that one @if NOT "%VIRTUAL_ENV%"=="" (set PYTHON="%VIRTUAL_ENV%\Scripts\python.exe") & (set _Py_Python_Source=found in virtual env) & goto :found @@ -42,7 +45,9 @@ @if NOT "%HOST_PYTHON%"=="" @%HOST_PYTHON% -Ec "import sys; assert sys.version_info[:2] >= (3, 9)" >nul 2>nul && (set PYTHON="%HOST_PYTHON%") && (set _Py_Python_Source=found as HOST_PYTHON) && goto :found @rem If py.exe finds a recent enough version, use that one -@for %%p in (3.11 3.10 3.9) do @py -%%p -EV >nul 2>&1 && (set PYTHON=py -%%p) && (set _Py_Python_Source=found %%p with py.exe) && goto :found +@rem It is fine to add new versions to this list when they have released, +@rem but we do not use prerelease builds here. +@for %%p in (3.12 3.11 3.10 3.9) do @py -%%p -EV >nul 2>&1 && (set PYTHON=py -%%p) && (set _Py_Python_Source=found %%p with py.exe) && goto :found @if NOT exist "%_Py_EXTERNALS_DIR%" mkdir "%_Py_EXTERNALS_DIR%" @set _Py_NUGET=%NUGET% diff --git a/PCbuild/pcbuild.proj b/PCbuild/pcbuild.proj index b7b78be768d7ecf..a2a637a30443738 100644 --- a/PCbuild/pcbuild.proj +++ b/PCbuild/pcbuild.proj @@ -77,7 +77,7 @@ - + diff --git a/PCbuild/pcbuild.sln b/PCbuild/pcbuild.sln index a0b5fbd1304b418..d10e1c46a914804 100644 --- a/PCbuild/pcbuild.sln +++ b/PCbuild/pcbuild.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30028.174 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34525.116 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{553EC33E-9816-4996-A660-5D6186A0B0B3}" ProjectSection(SolutionItems) = preProject @@ -159,6 +159,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_wmi", "_wmi.vcxproj", "{54 EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testclinic_limited", "_testclinic_limited.vcxproj", "{01FDF29A-40A1-46DF-84F5-85EBBD2A2410}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testlimitedcapi", "_testlimitedcapi.vcxproj", "{7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM = Debug|ARM @@ -1648,6 +1650,38 @@ Global {01FDF29A-40A1-46DF-84F5-85EBBD2A2410}.Release|Win32.Build.0 = Release|Win32 {01FDF29A-40A1-46DF-84F5-85EBBD2A2410}.Release|x64.ActiveCfg = Release|x64 {01FDF29A-40A1-46DF-84F5-85EBBD2A2410}.Release|x64.Build.0 = Release|x64 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.Debug|ARM.ActiveCfg = Debug|ARM + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.Debug|ARM.Build.0 = Debug|ARM + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.Debug|ARM64.Build.0 = Debug|ARM64 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.Debug|Win32.ActiveCfg = Debug|Win32 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.Debug|Win32.Build.0 = Debug|Win32 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.Debug|x64.ActiveCfg = Debug|x64 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.Debug|x64.Build.0 = Debug|x64 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.PGInstrument|ARM.ActiveCfg = PGInstrument|ARM + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.PGInstrument|ARM.Build.0 = PGInstrument|ARM + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.PGInstrument|ARM64.ActiveCfg = PGInstrument|ARM64 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.PGInstrument|ARM64.Build.0 = PGInstrument|ARM64 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.PGInstrument|x64.Build.0 = PGInstrument|x64 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.PGUpdate|ARM.ActiveCfg = PGUpdate|ARM + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.PGUpdate|ARM.Build.0 = PGUpdate|ARM + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.PGUpdate|ARM64.ActiveCfg = PGUpdate|ARM64 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.PGUpdate|ARM64.Build.0 = PGUpdate|ARM64 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.PGUpdate|x64.Build.0 = PGUpdate|x64 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.Release|ARM.ActiveCfg = Release|ARM + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.Release|ARM.Build.0 = Release|ARM + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.Release|ARM64.ActiveCfg = Release|ARM64 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.Release|ARM64.Build.0 = Release|ARM64 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.Release|Win32.ActiveCfg = Release|Win32 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.Release|Win32.Build.0 = Release|Win32 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.Release|x64.ActiveCfg = Release|x64 + {7467D86C-1CEB-4CB9-B65E-E9A54ABDC933}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index c7b698f0e17a396..88a4a7c95643098 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -601,6 +601,7 @@ + @@ -677,7 +678,7 @@ $([System.IO.File]::ReadAllText('$(IntDir)pyconfig.h')) - $(PyConfigHText.Replace('#undef Py_GIL_DISABLED', '#define Py_GIL_DISABLED 1')) + $(PyConfigHText.Replace('/* #define Py_GIL_DISABLED 1 */', '#define Py_GIL_DISABLED 1')) Python + + Python + Python diff --git a/PCbuild/readme.txt b/PCbuild/readme.txt index 387565515fa0b02..1fdd8a4a5ab52bd 100644 --- a/PCbuild/readme.txt +++ b/PCbuild/readme.txt @@ -131,29 +131,31 @@ xxlimited_35 The following sub-projects are for individual modules of the standard library which are implemented in C; each one builds a DLL (renamed to .pyd) of the same name as the project: -_asyncio -_ctypes -_ctypes_test -_zoneinfo -_decimal -_elementtree -_hashlib -_multiprocessing -_overlapped -_socket -_testbuffer -_testcapi -_testclinic -_testclinic_limited -_testconsole -_testimportmultiple -_testmultiphase -_testsinglephase -_tkinter -pyexpat -select -unicodedata -winsound + * _asyncio + * _ctypes + * _ctypes_test + * _zoneinfo + * _decimal + * _elementtree + * _hashlib + * _multiprocessing + * _overlapped + * _socket + * _testbuffer + * _testcapi + * _testlimitedcapi + * _testinternalcapi + * _testclinic + * _testclinic_limited + * _testconsole + * _testimportmultiple + * _testmultiphase + * _testsinglephase + * _tkinter + * pyexpat + * select + * unicodedata + * winsound The following Python-controlled sub-projects wrap external projects. Note that these external libraries are not necessary for a working @@ -226,12 +228,18 @@ directory. This script extracts all the external sub-projects from and https://github.com/python/cpython-bin-deps via a Python script called "get_external.py", located in this directory. -If Python 3.6 or later is not available via the "py.exe" launcher, the -path or command to use for Python can be provided in the PYTHON_FOR_BUILD -environment variable, or get_externals.bat will download the latest -version of NuGet and use it to download the latest "pythonx86" package -for use with get_external.py. Everything downloaded by these scripts is -stored in ..\externals (relative to this directory). +Everything downloaded by these scripts is stored in ..\externals +(relative to this directory), or the path specified by the EXTERNALS_DIR +environment variable. + +The path or command to use for Python can be provided with the +PYTHON_FOR_BUILD environment variable. If this is not set, an active +virtual environment will be used. If none is active, and HOST_PYTHON is +set to a recent enough version or "py.exe" is able to find a recent +enough version, those will be used. If all else fails, a copy of Python +will be downloaded from NuGet and extracted to the externals directory. +This will then be used for later builds (see PCbuild/find_python.bat +for the full logic). It is also possible to download sources from each project's homepage, though you may have to change folder names or pass the names to MSBuild @@ -303,6 +311,8 @@ _testclinic_limited extension, the file Modules/_testclinic_limited.c: * In PCbuild/, copy _testclinic.vcxproj to _testclinic_limited.vcxproj, replace RootNamespace value with `_testclinic_limited`, replace `_asyncio.c` with `_testclinic_limited.c`. +* In PCbuild/, copy _testclinic.vcxproj.filters to + _testclinic_limited.vcxproj.filters, edit the list of files in the new file. * Open Visual Studio, open PCbuild\pcbuild.sln solution, add the PCbuild\_testclinic_limited.vcxproj project to the solution ("add existing project). diff --git a/PCbuild/regen.targets b/PCbuild/regen.targets index 8f31803dbb752a6..b72ef5b5c6055c6 100644 --- a/PCbuild/regen.targets +++ b/PCbuild/regen.targets @@ -31,8 +31,8 @@ <_JITSources Include="$(PySourcePath)Python\executor_cases.c.h;$(GeneratedPyConfigDir)pyconfig.h;$(PySourcePath)Tools\jit\**"/> <_JITOutputs Include="$(GeneratedPyConfigDir)jit_stencils.h"/> - <_CasesSources Include="$(PySourcePath)Python\bytecodes.c;$(PySourcePath)Python\tier2_redundancy_eliminator_bytecodes.c;"/> - <_CasesOutputs Include="$(PySourcePath)Python\generated_cases.c.h;$(PySourcePath)Include\opcode_ids.h;$(PySourcePath)Include\internal\pycore_uop_ids.h;$(PySourcePath)Python\opcode_targets.h;$(PySourcePath)Include\internal\pycore_opcode_metadata.h;$(PySourcePath)Include\internal\pycore_uop_metadata.h;$(PySourcePath)Python\tier2_redundancy_eliminator_cases.c.h;$(PySourcePath)Lib\_opcode_metadata.py"/> + <_CasesSources Include="$(PySourcePath)Python\bytecodes.c;$(PySourcePath)Python\optimizer_bytecodes.c;"/> + <_CasesOutputs Include="$(PySourcePath)Python\generated_cases.c.h;$(PySourcePath)Include\opcode_ids.h;$(PySourcePath)Include\internal\pycore_uop_ids.h;$(PySourcePath)Python\opcode_targets.h;$(PySourcePath)Include\internal\pycore_opcode_metadata.h;$(PySourcePath)Include\internal\pycore_uop_metadata.h;$(PySourcePath)Python\optimizer_cases.c.h;$(PySourcePath)Lib\_opcode_metadata.py"/> @@ -98,7 +98,7 @@ WorkingDirectory="$(PySourcePath)" /> - @@ -150,9 +150,7 @@ Condition="($(Platform) == 'Win32' or $(Platform) == 'x64') and $(Configuration) != 'PGInstrument' and $(Configuration) != 'PGUpdate'"> - diff --git a/Parser/asdl_c.py b/Parser/asdl_c.py index ce92672bf00776a..59cc391881ab866 100755 --- a/Parser/asdl_c.py +++ b/Parser/asdl_c.py @@ -15,6 +15,13 @@ MAX_COL = 80 AUTOGEN_MESSAGE = "// File automatically generated by {}.\n\n" +builtin_type_to_c_type = { + "identifier": "PyUnicode_Type", + "string": "PyUnicode_Type", + "int": "PyLong_Type", + "constant": "PyBaseObject_Type", +} + def get_c_type(name): """Return a string for the C name of the type. @@ -764,6 +771,67 @@ def visitConstructor(self, cons, name): self.emit("};",0) +class AnnotationsVisitor(PickleVisitor): + def visitModule(self, mod): + self.file.write(textwrap.dedent(''' + static int + add_ast_annotations(struct ast_state *state) + { + bool cond; + ''')) + for dfn in mod.dfns: + self.visit(dfn) + self.file.write(textwrap.dedent(''' + return 1; + } + ''')) + + def visitProduct(self, prod, name): + self.emit_annotations(name, prod.fields) + + def visitSum(self, sum, name): + for t in sum.types: + self.visitConstructor(t, name) + + def visitConstructor(self, cons, name): + self.emit_annotations(cons.name, cons.fields) + + def emit_annotations(self, name, fields): + self.emit(f"PyObject *{name}_annotations = PyDict_New();", 1) + self.emit(f"if (!{name}_annotations) return 0;", 1) + for field in fields: + self.emit("{", 1) + if field.type in builtin_type_to_c_type: + self.emit(f"PyObject *type = (PyObject *)&{builtin_type_to_c_type[field.type]};", 2) + else: + self.emit(f"PyObject *type = state->{field.type}_type;", 2) + if field.opt: + self.emit("type = _Py_union_type_or(type, Py_None);", 2) + self.emit("cond = type != NULL;", 2) + self.emit_annotations_error(name, 2) + elif field.seq: + self.emit("type = Py_GenericAlias((PyObject *)&PyList_Type, type);", 2) + self.emit("cond = type != NULL;", 2) + self.emit_annotations_error(name, 2) + else: + self.emit("Py_INCREF(type);", 2) + self.emit(f"cond = PyDict_SetItemString({name}_annotations, \"{field.name}\", type) == 0;", 2) + self.emit("Py_DECREF(type);", 2) + self.emit_annotations_error(name, 2) + self.emit("}", 1) + self.emit(f'cond = PyObject_SetAttrString(state->{name}_type, "_field_types", {name}_annotations) == 0;', 1) + self.emit_annotations_error(name, 1) + self.emit(f'cond = PyObject_SetAttrString(state->{name}_type, "__annotations__", {name}_annotations) == 0;', 1) + self.emit_annotations_error(name, 1) + self.emit(f"Py_DECREF({name}_annotations);", 1) + + def emit_annotations_error(self, name, depth): + self.emit("if (!cond) {", depth) + self.emit(f"Py_DECREF({name}_annotations);", depth + 1) + self.emit("return 0;", depth + 1) + self.emit("}", depth) + + class PyTypesVisitor(PickleVisitor): def visitModule(self, mod): @@ -812,7 +880,7 @@ def visitModule(self, mod): Py_ssize_t i, numfields = 0; int res = -1; - PyObject *key, *value, *fields; + PyObject *key, *value, *fields, *remaining_fields = NULL; if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), state->_fields, &fields) < 0) { goto cleanup; } @@ -821,6 +889,13 @@ def visitModule(self, mod): if (numfields == -1) { goto cleanup; } + remaining_fields = PySet_New(fields); + } + else { + remaining_fields = PySet_New(NULL); + } + if (remaining_fields == NULL) { + goto cleanup; } res = 0; /* if no error occurs, this stays 0 to the end */ @@ -840,6 +915,11 @@ def visitModule(self, mod): goto cleanup; } res = PyObject_SetAttr(self, name, PyTuple_GET_ITEM(args, i)); + if (PySet_Discard(remaining_fields, name) < 0) { + res = -1; + Py_DECREF(name); + goto cleanup; + } Py_DECREF(name); if (res < 0) { goto cleanup; @@ -852,13 +932,14 @@ def visitModule(self, mod): if (contains == -1) { res = -1; goto cleanup; - } else if (contains == 1) { - Py_ssize_t p = PySequence_Index(fields, key); + } + else if (contains == 1) { + int p = PySet_Discard(remaining_fields, key); if (p == -1) { res = -1; goto cleanup; } - if (p < PyTuple_GET_SIZE(args)) { + if (p == 0) { PyErr_Format(PyExc_TypeError, "%.400s got multiple values for argument '%U'", Py_TYPE(self)->tp_name, key); @@ -866,15 +947,91 @@ def visitModule(self, mod): goto cleanup; } } + else if ( + PyUnicode_CompareWithASCIIString(key, "lineno") != 0 && + PyUnicode_CompareWithASCIIString(key, "col_offset") != 0 && + PyUnicode_CompareWithASCIIString(key, "end_lineno") != 0 && + PyUnicode_CompareWithASCIIString(key, "end_col_offset") != 0 + ) { + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, 1, + "%.400s.__init__ got an unexpected keyword argument '%U'. " + "Support for arbitrary keyword arguments is deprecated " + "and will be removed in Python 3.15.", + Py_TYPE(self)->tp_name, key + ) < 0) { + res = -1; + goto cleanup; + } + } res = PyObject_SetAttr(self, key, value); if (res < 0) { goto cleanup; } } } + Py_ssize_t size = PySet_Size(remaining_fields); + PyObject *field_types = NULL, *remaining_list = NULL; + if (size > 0) { + if (!PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), &_Py_ID(_field_types), + &field_types)) { + res = -1; + goto cleanup; + } + remaining_list = PySequence_List(remaining_fields); + if (!remaining_list) { + goto set_remaining_cleanup; + } + for (Py_ssize_t i = 0; i < size; i++) { + PyObject *name = PyList_GET_ITEM(remaining_list, i); + PyObject *type = PyDict_GetItemWithError(field_types, name); + if (!type) { + if (!PyErr_Occurred()) { + PyErr_SetObject(PyExc_KeyError, name); + } + goto set_remaining_cleanup; + } + if (_PyUnion_Check(type)) { + // optional field + // do nothing, we'll have set a None default on the class + } + else if (Py_IS_TYPE(type, &Py_GenericAliasType)) { + // list field + PyObject *empty = PyList_New(0); + if (!empty) { + goto set_remaining_cleanup; + } + res = PyObject_SetAttr(self, name, empty); + Py_DECREF(empty); + if (res < 0) { + goto set_remaining_cleanup; + } + } + else { + // simple field (e.g., identifier) + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, 1, + "%.400s.__init__ missing 1 required positional argument: '%U'. " + "This will become an error in Python 3.15.", + Py_TYPE(self)->tp_name, name + ) < 0) { + res = -1; + goto cleanup; + } + } + } + Py_DECREF(remaining_list); + Py_DECREF(field_types); + } cleanup: Py_XDECREF(fields); + Py_XDECREF(remaining_fields); return res; + set_remaining_cleanup: + Py_XDECREF(remaining_list); + Py_XDECREF(field_types); + res = -1; + goto cleanup; } /* Pickling support */ @@ -886,14 +1043,75 @@ def visitModule(self, mod): return NULL; } - PyObject *dict; + PyObject *dict = NULL, *fields = NULL, *remaining_fields = NULL, + *remaining_dict = NULL, *positional_args = NULL; if (PyObject_GetOptionalAttr(self, state->__dict__, &dict) < 0) { return NULL; } + PyObject *result = NULL; if (dict) { - return Py_BuildValue("O()N", Py_TYPE(self), dict); + // Serialize the fields as positional args if possible, because if we + // serialize them as a dict, during unpickling they are set only *after* + // the object is constructed, which will now trigger a DeprecationWarning + // if the AST type has required fields. + if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), state->_fields, &fields) < 0) { + goto cleanup; + } + if (fields) { + Py_ssize_t numfields = PySequence_Size(fields); + if (numfields == -1) { + Py_DECREF(dict); + goto cleanup; + } + remaining_dict = PyDict_Copy(dict); + Py_DECREF(dict); + if (!remaining_dict) { + goto cleanup; + } + positional_args = PyList_New(0); + if (!positional_args) { + goto cleanup; + } + for (Py_ssize_t i = 0; i < numfields; i++) { + PyObject *name = PySequence_GetItem(fields, i); + if (!name) { + goto cleanup; + } + PyObject *value; + int rc = PyDict_Pop(remaining_dict, name, &value); + Py_DECREF(name); + if (rc < 0) { + goto cleanup; + } + if (!value) { + break; + } + rc = PyList_Append(positional_args, value); + Py_DECREF(value); + if (rc < 0) { + goto cleanup; + } + } + PyObject *args_tuple = PyList_AsTuple(positional_args); + if (!args_tuple) { + goto cleanup; + } + result = Py_BuildValue("ONO", Py_TYPE(self), args_tuple, + remaining_dict); + } + else { + result = Py_BuildValue("O()N", Py_TYPE(self), dict); + } + } + else { + result = Py_BuildValue("O()", Py_TYPE(self)); } - return Py_BuildValue("O()", Py_TYPE(self)); +cleanup: + Py_XDECREF(fields); + Py_XDECREF(remaining_fields); + Py_XDECREF(remaining_dict); + Py_XDECREF(positional_args); + return result; } static PyMemberDef ast_type_members[] = { @@ -1117,6 +1335,9 @@ def visitModule(self, mod): for dfn in mod.dfns: self.visit(dfn) self.file.write(textwrap.dedent(''' + if (!add_ast_annotations(state)) { + return -1; + } return 0; } ''')) @@ -1534,6 +1755,8 @@ def generate_module_def(mod, metadata, f, internal_h): #include "pycore_lock.h" // _PyOnceFlag #include "pycore_interp.h" // _PyInterpreterState.ast #include "pycore_pystate.h" // _PyInterpreterState_GET() + #include "pycore_unionobject.h" // _Py_union_type_or + #include "structmember.h" #include struct validator { @@ -1651,6 +1874,7 @@ def write_source(mod, metadata, f, internal_h_file): v = ChainOfVisitors( SequenceConstructorVisitor(f), PyTypesDeclareVisitor(f), + AnnotationsVisitor(f), PyTypesVisitor(f), Obj2ModPrototypeVisitor(f), FunctionVisitor(f), diff --git a/Parser/parser.c b/Parser/parser.c index 779b18e9650e9f5..f1170c261974520 100644 --- a/Parser/parser.c +++ b/Parser/parser.c @@ -6603,7 +6603,7 @@ with_stmt_rule(Parser *p) UNUSED(_end_lineno); // Only used by EXTRA macro int _end_col_offset = _token->end_col_offset; UNUSED(_end_col_offset); // Only used by EXTRA macro - _res = CHECK_VERSION ( stmt_ty , 9 , "Parenthesized context managers are" , _PyAST_With ( a , b , NEW_TYPE_COMMENT ( p , tc ) , EXTRA ) ); + _res = _PyAST_With ( a , b , NEW_TYPE_COMMENT ( p , tc ) , EXTRA ); if (_res == NULL && PyErr_Occurred()) { p->error_indicator = 1; p->level--; diff --git a/Parser/pegen_errors.c b/Parser/pegen_errors.c index e15673d02dd3b05..e8f11a67e50fa0a 100644 --- a/Parser/pegen_errors.c +++ b/Parser/pegen_errors.c @@ -369,20 +369,18 @@ _PyPegen_raise_error_known_location(Parser *p, PyObject *errtype, Py_ssize_t col_number = col_offset; Py_ssize_t end_col_number = end_col_offset; - if (p->tok->encoding != NULL) { - col_number = _PyPegen_byte_offset_to_character_offset(error_line, col_offset); - if (col_number < 0) { + col_number = _PyPegen_byte_offset_to_character_offset(error_line, col_offset); + if (col_number < 0) { + goto error; + } + + if (end_col_offset > 0) { + end_col_number = _PyPegen_byte_offset_to_character_offset(error_line, end_col_offset); + if (end_col_number < 0) { goto error; } - if (end_col_number > 0) { - Py_ssize_t end_col_offset = _PyPegen_byte_offset_to_character_offset(error_line, end_col_number); - if (end_col_offset < 0) { - goto error; - } else { - end_col_number = end_col_offset; - } - } } + tmp = Py_BuildValue("(OnnNnn)", p->tok->filename, lineno, col_number, error_line, end_lineno, end_col_number); if (!tmp) { goto error; diff --git a/Python/Python-ast.c b/Python/Python-ast.c index d77e986ba067a3e..7b591ddaa298695 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -7,6 +7,8 @@ #include "pycore_lock.h" // _PyOnceFlag #include "pycore_interp.h" // _PyInterpreterState.ast #include "pycore_pystate.h" // _PyInterpreterState_GET() +#include "pycore_unionobject.h" // _Py_union_type_or +#include "structmember.h" #include struct validator { @@ -816,6 +818,4170 @@ static const char * const TypeVarTuple_fields[]={ }; +static int +add_ast_annotations(struct ast_state *state) +{ + bool cond; + PyObject *Module_annotations = PyDict_New(); + if (!Module_annotations) return 0; + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Module_annotations); + return 0; + } + cond = PyDict_SetItemString(Module_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Module_annotations); + return 0; + } + } + { + PyObject *type = state->type_ignore_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Module_annotations); + return 0; + } + cond = PyDict_SetItemString(Module_annotations, "type_ignores", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Module_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Module_type, "_field_types", + Module_annotations) == 0; + if (!cond) { + Py_DECREF(Module_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Module_type, "__annotations__", + Module_annotations) == 0; + if (!cond) { + Py_DECREF(Module_annotations); + return 0; + } + Py_DECREF(Module_annotations); + PyObject *Interactive_annotations = PyDict_New(); + if (!Interactive_annotations) return 0; + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Interactive_annotations); + return 0; + } + cond = PyDict_SetItemString(Interactive_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Interactive_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Interactive_type, "_field_types", + Interactive_annotations) == 0; + if (!cond) { + Py_DECREF(Interactive_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Interactive_type, "__annotations__", + Interactive_annotations) == 0; + if (!cond) { + Py_DECREF(Interactive_annotations); + return 0; + } + Py_DECREF(Interactive_annotations); + PyObject *Expression_annotations = PyDict_New(); + if (!Expression_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Expression_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Expression_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Expression_type, "_field_types", + Expression_annotations) == 0; + if (!cond) { + Py_DECREF(Expression_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Expression_type, "__annotations__", + Expression_annotations) == 0; + if (!cond) { + Py_DECREF(Expression_annotations); + return 0; + } + Py_DECREF(Expression_annotations); + PyObject *FunctionType_annotations = PyDict_New(); + if (!FunctionType_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(FunctionType_annotations); + return 0; + } + cond = PyDict_SetItemString(FunctionType_annotations, "argtypes", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionType_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(FunctionType_annotations, "returns", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionType_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->FunctionType_type, "_field_types", + FunctionType_annotations) == 0; + if (!cond) { + Py_DECREF(FunctionType_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->FunctionType_type, "__annotations__", + FunctionType_annotations) == 0; + if (!cond) { + Py_DECREF(FunctionType_annotations); + return 0; + } + Py_DECREF(FunctionType_annotations); + PyObject *FunctionDef_annotations = PyDict_New(); + if (!FunctionDef_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(FunctionDef_annotations, "name", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->arguments_type; + Py_INCREF(type); + cond = PyDict_SetItemString(FunctionDef_annotations, "args", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(FunctionDef_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(FunctionDef_annotations, "decorator_list", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(FunctionDef_annotations, "returns", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(FunctionDef_annotations, "type_comment", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->type_param_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(FunctionDef_annotations, "type_params", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->FunctionDef_type, "_field_types", + FunctionDef_annotations) == 0; + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->FunctionDef_type, "__annotations__", + FunctionDef_annotations) == 0; + if (!cond) { + Py_DECREF(FunctionDef_annotations); + return 0; + } + Py_DECREF(FunctionDef_annotations); + PyObject *AsyncFunctionDef_annotations = PyDict_New(); + if (!AsyncFunctionDef_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(AsyncFunctionDef_annotations, "name", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->arguments_type; + Py_INCREF(type); + cond = PyDict_SetItemString(AsyncFunctionDef_annotations, "args", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncFunctionDef_annotations, "body", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncFunctionDef_annotations, + "decorator_list", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncFunctionDef_annotations, "returns", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncFunctionDef_annotations, + "type_comment", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + } + { + PyObject *type = state->type_param_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncFunctionDef_annotations, + "type_params", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->AsyncFunctionDef_type, "_field_types", + AsyncFunctionDef_annotations) == 0; + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->AsyncFunctionDef_type, + "__annotations__", + AsyncFunctionDef_annotations) == 0; + if (!cond) { + Py_DECREF(AsyncFunctionDef_annotations); + return 0; + } + Py_DECREF(AsyncFunctionDef_annotations); + PyObject *ClassDef_annotations = PyDict_New(); + if (!ClassDef_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(ClassDef_annotations, "name", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + cond = PyDict_SetItemString(ClassDef_annotations, "bases", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + } + { + PyObject *type = state->keyword_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + cond = PyDict_SetItemString(ClassDef_annotations, "keywords", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + cond = PyDict_SetItemString(ClassDef_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + cond = PyDict_SetItemString(ClassDef_annotations, "decorator_list", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + } + { + PyObject *type = state->type_param_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + cond = PyDict_SetItemString(ClassDef_annotations, "type_params", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->ClassDef_type, "_field_types", + ClassDef_annotations) == 0; + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->ClassDef_type, "__annotations__", + ClassDef_annotations) == 0; + if (!cond) { + Py_DECREF(ClassDef_annotations); + return 0; + } + Py_DECREF(ClassDef_annotations); + PyObject *Return_annotations = PyDict_New(); + if (!Return_annotations) return 0; + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Return_annotations); + return 0; + } + cond = PyDict_SetItemString(Return_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Return_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Return_type, "_field_types", + Return_annotations) == 0; + if (!cond) { + Py_DECREF(Return_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Return_type, "__annotations__", + Return_annotations) == 0; + if (!cond) { + Py_DECREF(Return_annotations); + return 0; + } + Py_DECREF(Return_annotations); + PyObject *Delete_annotations = PyDict_New(); + if (!Delete_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Delete_annotations); + return 0; + } + cond = PyDict_SetItemString(Delete_annotations, "targets", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Delete_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Delete_type, "_field_types", + Delete_annotations) == 0; + if (!cond) { + Py_DECREF(Delete_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Delete_type, "__annotations__", + Delete_annotations) == 0; + if (!cond) { + Py_DECREF(Delete_annotations); + return 0; + } + Py_DECREF(Delete_annotations); + PyObject *Assign_annotations = PyDict_New(); + if (!Assign_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Assign_annotations); + return 0; + } + cond = PyDict_SetItemString(Assign_annotations, "targets", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Assign_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Assign_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Assign_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Assign_annotations); + return 0; + } + cond = PyDict_SetItemString(Assign_annotations, "type_comment", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Assign_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Assign_type, "_field_types", + Assign_annotations) == 0; + if (!cond) { + Py_DECREF(Assign_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Assign_type, "__annotations__", + Assign_annotations) == 0; + if (!cond) { + Py_DECREF(Assign_annotations); + return 0; + } + Py_DECREF(Assign_annotations); + PyObject *TypeAlias_annotations = PyDict_New(); + if (!TypeAlias_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(TypeAlias_annotations, "name", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TypeAlias_annotations); + return 0; + } + } + { + PyObject *type = state->type_param_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(TypeAlias_annotations); + return 0; + } + cond = PyDict_SetItemString(TypeAlias_annotations, "type_params", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TypeAlias_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(TypeAlias_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TypeAlias_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->TypeAlias_type, "_field_types", + TypeAlias_annotations) == 0; + if (!cond) { + Py_DECREF(TypeAlias_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->TypeAlias_type, "__annotations__", + TypeAlias_annotations) == 0; + if (!cond) { + Py_DECREF(TypeAlias_annotations); + return 0; + } + Py_DECREF(TypeAlias_annotations); + PyObject *AugAssign_annotations = PyDict_New(); + if (!AugAssign_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(AugAssign_annotations, "target", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AugAssign_annotations); + return 0; + } + } + { + PyObject *type = state->operator_type; + Py_INCREF(type); + cond = PyDict_SetItemString(AugAssign_annotations, "op", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AugAssign_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(AugAssign_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AugAssign_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->AugAssign_type, "_field_types", + AugAssign_annotations) == 0; + if (!cond) { + Py_DECREF(AugAssign_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->AugAssign_type, "__annotations__", + AugAssign_annotations) == 0; + if (!cond) { + Py_DECREF(AugAssign_annotations); + return 0; + } + Py_DECREF(AugAssign_annotations); + PyObject *AnnAssign_annotations = PyDict_New(); + if (!AnnAssign_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(AnnAssign_annotations, "target", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AnnAssign_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(AnnAssign_annotations, "annotation", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AnnAssign_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(AnnAssign_annotations); + return 0; + } + cond = PyDict_SetItemString(AnnAssign_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AnnAssign_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyLong_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(AnnAssign_annotations, "simple", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AnnAssign_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->AnnAssign_type, "_field_types", + AnnAssign_annotations) == 0; + if (!cond) { + Py_DECREF(AnnAssign_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->AnnAssign_type, "__annotations__", + AnnAssign_annotations) == 0; + if (!cond) { + Py_DECREF(AnnAssign_annotations); + return 0; + } + Py_DECREF(AnnAssign_annotations); + PyObject *For_annotations = PyDict_New(); + if (!For_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(For_annotations, "target", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(For_annotations, "iter", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + cond = PyDict_SetItemString(For_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + cond = PyDict_SetItemString(For_annotations, "orelse", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + cond = PyDict_SetItemString(For_annotations, "type_comment", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->For_type, "_field_types", + For_annotations) == 0; + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->For_type, "__annotations__", + For_annotations) == 0; + if (!cond) { + Py_DECREF(For_annotations); + return 0; + } + Py_DECREF(For_annotations); + PyObject *AsyncFor_annotations = PyDict_New(); + if (!AsyncFor_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(AsyncFor_annotations, "target", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(AsyncFor_annotations, "iter", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncFor_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncFor_annotations, "orelse", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncFor_annotations, "type_comment", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->AsyncFor_type, "_field_types", + AsyncFor_annotations) == 0; + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->AsyncFor_type, "__annotations__", + AsyncFor_annotations) == 0; + if (!cond) { + Py_DECREF(AsyncFor_annotations); + return 0; + } + Py_DECREF(AsyncFor_annotations); + PyObject *While_annotations = PyDict_New(); + if (!While_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(While_annotations, "test", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(While_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(While_annotations); + return 0; + } + cond = PyDict_SetItemString(While_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(While_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(While_annotations); + return 0; + } + cond = PyDict_SetItemString(While_annotations, "orelse", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(While_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->While_type, "_field_types", + While_annotations) == 0; + if (!cond) { + Py_DECREF(While_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->While_type, "__annotations__", + While_annotations) == 0; + if (!cond) { + Py_DECREF(While_annotations); + return 0; + } + Py_DECREF(While_annotations); + PyObject *If_annotations = PyDict_New(); + if (!If_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(If_annotations, "test", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(If_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(If_annotations); + return 0; + } + cond = PyDict_SetItemString(If_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(If_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(If_annotations); + return 0; + } + cond = PyDict_SetItemString(If_annotations, "orelse", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(If_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->If_type, "_field_types", + If_annotations) == 0; + if (!cond) { + Py_DECREF(If_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->If_type, "__annotations__", + If_annotations) == 0; + if (!cond) { + Py_DECREF(If_annotations); + return 0; + } + Py_DECREF(If_annotations); + PyObject *With_annotations = PyDict_New(); + if (!With_annotations) return 0; + { + PyObject *type = state->withitem_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(With_annotations); + return 0; + } + cond = PyDict_SetItemString(With_annotations, "items", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(With_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(With_annotations); + return 0; + } + cond = PyDict_SetItemString(With_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(With_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(With_annotations); + return 0; + } + cond = PyDict_SetItemString(With_annotations, "type_comment", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(With_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->With_type, "_field_types", + With_annotations) == 0; + if (!cond) { + Py_DECREF(With_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->With_type, "__annotations__", + With_annotations) == 0; + if (!cond) { + Py_DECREF(With_annotations); + return 0; + } + Py_DECREF(With_annotations); + PyObject *AsyncWith_annotations = PyDict_New(); + if (!AsyncWith_annotations) return 0; + { + PyObject *type = state->withitem_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncWith_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncWith_annotations, "items", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncWith_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncWith_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncWith_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncWith_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(AsyncWith_annotations); + return 0; + } + cond = PyDict_SetItemString(AsyncWith_annotations, "type_comment", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(AsyncWith_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->AsyncWith_type, "_field_types", + AsyncWith_annotations) == 0; + if (!cond) { + Py_DECREF(AsyncWith_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->AsyncWith_type, "__annotations__", + AsyncWith_annotations) == 0; + if (!cond) { + Py_DECREF(AsyncWith_annotations); + return 0; + } + Py_DECREF(AsyncWith_annotations); + PyObject *Match_annotations = PyDict_New(); + if (!Match_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Match_annotations, "subject", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Match_annotations); + return 0; + } + } + { + PyObject *type = state->match_case_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Match_annotations); + return 0; + } + cond = PyDict_SetItemString(Match_annotations, "cases", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Match_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Match_type, "_field_types", + Match_annotations) == 0; + if (!cond) { + Py_DECREF(Match_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Match_type, "__annotations__", + Match_annotations) == 0; + if (!cond) { + Py_DECREF(Match_annotations); + return 0; + } + Py_DECREF(Match_annotations); + PyObject *Raise_annotations = PyDict_New(); + if (!Raise_annotations) return 0; + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Raise_annotations); + return 0; + } + cond = PyDict_SetItemString(Raise_annotations, "exc", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Raise_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Raise_annotations); + return 0; + } + cond = PyDict_SetItemString(Raise_annotations, "cause", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Raise_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Raise_type, "_field_types", + Raise_annotations) == 0; + if (!cond) { + Py_DECREF(Raise_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Raise_type, "__annotations__", + Raise_annotations) == 0; + if (!cond) { + Py_DECREF(Raise_annotations); + return 0; + } + Py_DECREF(Raise_annotations); + PyObject *Try_annotations = PyDict_New(); + if (!Try_annotations) return 0; + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + cond = PyDict_SetItemString(Try_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + } + { + PyObject *type = state->excepthandler_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + cond = PyDict_SetItemString(Try_annotations, "handlers", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + cond = PyDict_SetItemString(Try_annotations, "orelse", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + cond = PyDict_SetItemString(Try_annotations, "finalbody", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Try_type, "_field_types", + Try_annotations) == 0; + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Try_type, "__annotations__", + Try_annotations) == 0; + if (!cond) { + Py_DECREF(Try_annotations); + return 0; + } + Py_DECREF(Try_annotations); + PyObject *TryStar_annotations = PyDict_New(); + if (!TryStar_annotations) return 0; + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + cond = PyDict_SetItemString(TryStar_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + } + { + PyObject *type = state->excepthandler_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + cond = PyDict_SetItemString(TryStar_annotations, "handlers", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + cond = PyDict_SetItemString(TryStar_annotations, "orelse", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + cond = PyDict_SetItemString(TryStar_annotations, "finalbody", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->TryStar_type, "_field_types", + TryStar_annotations) == 0; + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->TryStar_type, "__annotations__", + TryStar_annotations) == 0; + if (!cond) { + Py_DECREF(TryStar_annotations); + return 0; + } + Py_DECREF(TryStar_annotations); + PyObject *Assert_annotations = PyDict_New(); + if (!Assert_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Assert_annotations, "test", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Assert_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Assert_annotations); + return 0; + } + cond = PyDict_SetItemString(Assert_annotations, "msg", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Assert_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Assert_type, "_field_types", + Assert_annotations) == 0; + if (!cond) { + Py_DECREF(Assert_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Assert_type, "__annotations__", + Assert_annotations) == 0; + if (!cond) { + Py_DECREF(Assert_annotations); + return 0; + } + Py_DECREF(Assert_annotations); + PyObject *Import_annotations = PyDict_New(); + if (!Import_annotations) return 0; + { + PyObject *type = state->alias_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Import_annotations); + return 0; + } + cond = PyDict_SetItemString(Import_annotations, "names", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Import_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Import_type, "_field_types", + Import_annotations) == 0; + if (!cond) { + Py_DECREF(Import_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Import_type, "__annotations__", + Import_annotations) == 0; + if (!cond) { + Py_DECREF(Import_annotations); + return 0; + } + Py_DECREF(Import_annotations); + PyObject *ImportFrom_annotations = PyDict_New(); + if (!ImportFrom_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(ImportFrom_annotations); + return 0; + } + cond = PyDict_SetItemString(ImportFrom_annotations, "module", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ImportFrom_annotations); + return 0; + } + } + { + PyObject *type = state->alias_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(ImportFrom_annotations); + return 0; + } + cond = PyDict_SetItemString(ImportFrom_annotations, "names", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ImportFrom_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyLong_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(ImportFrom_annotations); + return 0; + } + cond = PyDict_SetItemString(ImportFrom_annotations, "level", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ImportFrom_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->ImportFrom_type, "_field_types", + ImportFrom_annotations) == 0; + if (!cond) { + Py_DECREF(ImportFrom_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->ImportFrom_type, "__annotations__", + ImportFrom_annotations) == 0; + if (!cond) { + Py_DECREF(ImportFrom_annotations); + return 0; + } + Py_DECREF(ImportFrom_annotations); + PyObject *Global_annotations = PyDict_New(); + if (!Global_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Global_annotations); + return 0; + } + cond = PyDict_SetItemString(Global_annotations, "names", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Global_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Global_type, "_field_types", + Global_annotations) == 0; + if (!cond) { + Py_DECREF(Global_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Global_type, "__annotations__", + Global_annotations) == 0; + if (!cond) { + Py_DECREF(Global_annotations); + return 0; + } + Py_DECREF(Global_annotations); + PyObject *Nonlocal_annotations = PyDict_New(); + if (!Nonlocal_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Nonlocal_annotations); + return 0; + } + cond = PyDict_SetItemString(Nonlocal_annotations, "names", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Nonlocal_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Nonlocal_type, "_field_types", + Nonlocal_annotations) == 0; + if (!cond) { + Py_DECREF(Nonlocal_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Nonlocal_type, "__annotations__", + Nonlocal_annotations) == 0; + if (!cond) { + Py_DECREF(Nonlocal_annotations); + return 0; + } + Py_DECREF(Nonlocal_annotations); + PyObject *Expr_annotations = PyDict_New(); + if (!Expr_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Expr_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Expr_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Expr_type, "_field_types", + Expr_annotations) == 0; + if (!cond) { + Py_DECREF(Expr_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Expr_type, "__annotations__", + Expr_annotations) == 0; + if (!cond) { + Py_DECREF(Expr_annotations); + return 0; + } + Py_DECREF(Expr_annotations); + PyObject *Pass_annotations = PyDict_New(); + if (!Pass_annotations) return 0; + cond = PyObject_SetAttrString(state->Pass_type, "_field_types", + Pass_annotations) == 0; + if (!cond) { + Py_DECREF(Pass_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Pass_type, "__annotations__", + Pass_annotations) == 0; + if (!cond) { + Py_DECREF(Pass_annotations); + return 0; + } + Py_DECREF(Pass_annotations); + PyObject *Break_annotations = PyDict_New(); + if (!Break_annotations) return 0; + cond = PyObject_SetAttrString(state->Break_type, "_field_types", + Break_annotations) == 0; + if (!cond) { + Py_DECREF(Break_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Break_type, "__annotations__", + Break_annotations) == 0; + if (!cond) { + Py_DECREF(Break_annotations); + return 0; + } + Py_DECREF(Break_annotations); + PyObject *Continue_annotations = PyDict_New(); + if (!Continue_annotations) return 0; + cond = PyObject_SetAttrString(state->Continue_type, "_field_types", + Continue_annotations) == 0; + if (!cond) { + Py_DECREF(Continue_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Continue_type, "__annotations__", + Continue_annotations) == 0; + if (!cond) { + Py_DECREF(Continue_annotations); + return 0; + } + Py_DECREF(Continue_annotations); + PyObject *BoolOp_annotations = PyDict_New(); + if (!BoolOp_annotations) return 0; + { + PyObject *type = state->boolop_type; + Py_INCREF(type); + cond = PyDict_SetItemString(BoolOp_annotations, "op", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(BoolOp_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(BoolOp_annotations); + return 0; + } + cond = PyDict_SetItemString(BoolOp_annotations, "values", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(BoolOp_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->BoolOp_type, "_field_types", + BoolOp_annotations) == 0; + if (!cond) { + Py_DECREF(BoolOp_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->BoolOp_type, "__annotations__", + BoolOp_annotations) == 0; + if (!cond) { + Py_DECREF(BoolOp_annotations); + return 0; + } + Py_DECREF(BoolOp_annotations); + PyObject *NamedExpr_annotations = PyDict_New(); + if (!NamedExpr_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(NamedExpr_annotations, "target", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(NamedExpr_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(NamedExpr_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(NamedExpr_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->NamedExpr_type, "_field_types", + NamedExpr_annotations) == 0; + if (!cond) { + Py_DECREF(NamedExpr_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->NamedExpr_type, "__annotations__", + NamedExpr_annotations) == 0; + if (!cond) { + Py_DECREF(NamedExpr_annotations); + return 0; + } + Py_DECREF(NamedExpr_annotations); + PyObject *BinOp_annotations = PyDict_New(); + if (!BinOp_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(BinOp_annotations, "left", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(BinOp_annotations); + return 0; + } + } + { + PyObject *type = state->operator_type; + Py_INCREF(type); + cond = PyDict_SetItemString(BinOp_annotations, "op", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(BinOp_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(BinOp_annotations, "right", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(BinOp_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->BinOp_type, "_field_types", + BinOp_annotations) == 0; + if (!cond) { + Py_DECREF(BinOp_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->BinOp_type, "__annotations__", + BinOp_annotations) == 0; + if (!cond) { + Py_DECREF(BinOp_annotations); + return 0; + } + Py_DECREF(BinOp_annotations); + PyObject *UnaryOp_annotations = PyDict_New(); + if (!UnaryOp_annotations) return 0; + { + PyObject *type = state->unaryop_type; + Py_INCREF(type); + cond = PyDict_SetItemString(UnaryOp_annotations, "op", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(UnaryOp_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(UnaryOp_annotations, "operand", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(UnaryOp_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->UnaryOp_type, "_field_types", + UnaryOp_annotations) == 0; + if (!cond) { + Py_DECREF(UnaryOp_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->UnaryOp_type, "__annotations__", + UnaryOp_annotations) == 0; + if (!cond) { + Py_DECREF(UnaryOp_annotations); + return 0; + } + Py_DECREF(UnaryOp_annotations); + PyObject *Lambda_annotations = PyDict_New(); + if (!Lambda_annotations) return 0; + { + PyObject *type = state->arguments_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Lambda_annotations, "args", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Lambda_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Lambda_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Lambda_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Lambda_type, "_field_types", + Lambda_annotations) == 0; + if (!cond) { + Py_DECREF(Lambda_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Lambda_type, "__annotations__", + Lambda_annotations) == 0; + if (!cond) { + Py_DECREF(Lambda_annotations); + return 0; + } + Py_DECREF(Lambda_annotations); + PyObject *IfExp_annotations = PyDict_New(); + if (!IfExp_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(IfExp_annotations, "test", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(IfExp_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(IfExp_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(IfExp_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(IfExp_annotations, "orelse", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(IfExp_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->IfExp_type, "_field_types", + IfExp_annotations) == 0; + if (!cond) { + Py_DECREF(IfExp_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->IfExp_type, "__annotations__", + IfExp_annotations) == 0; + if (!cond) { + Py_DECREF(IfExp_annotations); + return 0; + } + Py_DECREF(IfExp_annotations); + PyObject *Dict_annotations = PyDict_New(); + if (!Dict_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Dict_annotations); + return 0; + } + cond = PyDict_SetItemString(Dict_annotations, "keys", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Dict_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Dict_annotations); + return 0; + } + cond = PyDict_SetItemString(Dict_annotations, "values", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Dict_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Dict_type, "_field_types", + Dict_annotations) == 0; + if (!cond) { + Py_DECREF(Dict_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Dict_type, "__annotations__", + Dict_annotations) == 0; + if (!cond) { + Py_DECREF(Dict_annotations); + return 0; + } + Py_DECREF(Dict_annotations); + PyObject *Set_annotations = PyDict_New(); + if (!Set_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Set_annotations); + return 0; + } + cond = PyDict_SetItemString(Set_annotations, "elts", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Set_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Set_type, "_field_types", + Set_annotations) == 0; + if (!cond) { + Py_DECREF(Set_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Set_type, "__annotations__", + Set_annotations) == 0; + if (!cond) { + Py_DECREF(Set_annotations); + return 0; + } + Py_DECREF(Set_annotations); + PyObject *ListComp_annotations = PyDict_New(); + if (!ListComp_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(ListComp_annotations, "elt", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ListComp_annotations); + return 0; + } + } + { + PyObject *type = state->comprehension_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(ListComp_annotations); + return 0; + } + cond = PyDict_SetItemString(ListComp_annotations, "generators", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ListComp_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->ListComp_type, "_field_types", + ListComp_annotations) == 0; + if (!cond) { + Py_DECREF(ListComp_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->ListComp_type, "__annotations__", + ListComp_annotations) == 0; + if (!cond) { + Py_DECREF(ListComp_annotations); + return 0; + } + Py_DECREF(ListComp_annotations); + PyObject *SetComp_annotations = PyDict_New(); + if (!SetComp_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(SetComp_annotations, "elt", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(SetComp_annotations); + return 0; + } + } + { + PyObject *type = state->comprehension_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(SetComp_annotations); + return 0; + } + cond = PyDict_SetItemString(SetComp_annotations, "generators", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(SetComp_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->SetComp_type, "_field_types", + SetComp_annotations) == 0; + if (!cond) { + Py_DECREF(SetComp_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->SetComp_type, "__annotations__", + SetComp_annotations) == 0; + if (!cond) { + Py_DECREF(SetComp_annotations); + return 0; + } + Py_DECREF(SetComp_annotations); + PyObject *DictComp_annotations = PyDict_New(); + if (!DictComp_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(DictComp_annotations, "key", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(DictComp_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(DictComp_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(DictComp_annotations); + return 0; + } + } + { + PyObject *type = state->comprehension_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(DictComp_annotations); + return 0; + } + cond = PyDict_SetItemString(DictComp_annotations, "generators", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(DictComp_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->DictComp_type, "_field_types", + DictComp_annotations) == 0; + if (!cond) { + Py_DECREF(DictComp_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->DictComp_type, "__annotations__", + DictComp_annotations) == 0; + if (!cond) { + Py_DECREF(DictComp_annotations); + return 0; + } + Py_DECREF(DictComp_annotations); + PyObject *GeneratorExp_annotations = PyDict_New(); + if (!GeneratorExp_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(GeneratorExp_annotations, "elt", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(GeneratorExp_annotations); + return 0; + } + } + { + PyObject *type = state->comprehension_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(GeneratorExp_annotations); + return 0; + } + cond = PyDict_SetItemString(GeneratorExp_annotations, "generators", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(GeneratorExp_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->GeneratorExp_type, "_field_types", + GeneratorExp_annotations) == 0; + if (!cond) { + Py_DECREF(GeneratorExp_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->GeneratorExp_type, "__annotations__", + GeneratorExp_annotations) == 0; + if (!cond) { + Py_DECREF(GeneratorExp_annotations); + return 0; + } + Py_DECREF(GeneratorExp_annotations); + PyObject *Await_annotations = PyDict_New(); + if (!Await_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Await_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Await_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Await_type, "_field_types", + Await_annotations) == 0; + if (!cond) { + Py_DECREF(Await_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Await_type, "__annotations__", + Await_annotations) == 0; + if (!cond) { + Py_DECREF(Await_annotations); + return 0; + } + Py_DECREF(Await_annotations); + PyObject *Yield_annotations = PyDict_New(); + if (!Yield_annotations) return 0; + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Yield_annotations); + return 0; + } + cond = PyDict_SetItemString(Yield_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Yield_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Yield_type, "_field_types", + Yield_annotations) == 0; + if (!cond) { + Py_DECREF(Yield_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Yield_type, "__annotations__", + Yield_annotations) == 0; + if (!cond) { + Py_DECREF(Yield_annotations); + return 0; + } + Py_DECREF(Yield_annotations); + PyObject *YieldFrom_annotations = PyDict_New(); + if (!YieldFrom_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(YieldFrom_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(YieldFrom_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->YieldFrom_type, "_field_types", + YieldFrom_annotations) == 0; + if (!cond) { + Py_DECREF(YieldFrom_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->YieldFrom_type, "__annotations__", + YieldFrom_annotations) == 0; + if (!cond) { + Py_DECREF(YieldFrom_annotations); + return 0; + } + Py_DECREF(YieldFrom_annotations); + PyObject *Compare_annotations = PyDict_New(); + if (!Compare_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Compare_annotations, "left", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Compare_annotations); + return 0; + } + } + { + PyObject *type = state->cmpop_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Compare_annotations); + return 0; + } + cond = PyDict_SetItemString(Compare_annotations, "ops", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Compare_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Compare_annotations); + return 0; + } + cond = PyDict_SetItemString(Compare_annotations, "comparators", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Compare_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Compare_type, "_field_types", + Compare_annotations) == 0; + if (!cond) { + Py_DECREF(Compare_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Compare_type, "__annotations__", + Compare_annotations) == 0; + if (!cond) { + Py_DECREF(Compare_annotations); + return 0; + } + Py_DECREF(Compare_annotations); + PyObject *Call_annotations = PyDict_New(); + if (!Call_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Call_annotations, "func", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Call_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Call_annotations); + return 0; + } + cond = PyDict_SetItemString(Call_annotations, "args", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Call_annotations); + return 0; + } + } + { + PyObject *type = state->keyword_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Call_annotations); + return 0; + } + cond = PyDict_SetItemString(Call_annotations, "keywords", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Call_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Call_type, "_field_types", + Call_annotations) == 0; + if (!cond) { + Py_DECREF(Call_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Call_type, "__annotations__", + Call_annotations) == 0; + if (!cond) { + Py_DECREF(Call_annotations); + return 0; + } + Py_DECREF(Call_annotations); + PyObject *FormattedValue_annotations = PyDict_New(); + if (!FormattedValue_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(FormattedValue_annotations, "value", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FormattedValue_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyLong_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(FormattedValue_annotations, "conversion", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FormattedValue_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(FormattedValue_annotations); + return 0; + } + cond = PyDict_SetItemString(FormattedValue_annotations, "format_spec", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(FormattedValue_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->FormattedValue_type, "_field_types", + FormattedValue_annotations) == 0; + if (!cond) { + Py_DECREF(FormattedValue_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->FormattedValue_type, + "__annotations__", + FormattedValue_annotations) == 0; + if (!cond) { + Py_DECREF(FormattedValue_annotations); + return 0; + } + Py_DECREF(FormattedValue_annotations); + PyObject *JoinedStr_annotations = PyDict_New(); + if (!JoinedStr_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(JoinedStr_annotations); + return 0; + } + cond = PyDict_SetItemString(JoinedStr_annotations, "values", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(JoinedStr_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->JoinedStr_type, "_field_types", + JoinedStr_annotations) == 0; + if (!cond) { + Py_DECREF(JoinedStr_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->JoinedStr_type, "__annotations__", + JoinedStr_annotations) == 0; + if (!cond) { + Py_DECREF(JoinedStr_annotations); + return 0; + } + Py_DECREF(JoinedStr_annotations); + PyObject *Constant_annotations = PyDict_New(); + if (!Constant_annotations) return 0; + { + PyObject *type = (PyObject *)&PyBaseObject_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(Constant_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Constant_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Constant_annotations); + return 0; + } + cond = PyDict_SetItemString(Constant_annotations, "kind", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Constant_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Constant_type, "_field_types", + Constant_annotations) == 0; + if (!cond) { + Py_DECREF(Constant_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Constant_type, "__annotations__", + Constant_annotations) == 0; + if (!cond) { + Py_DECREF(Constant_annotations); + return 0; + } + Py_DECREF(Constant_annotations); + PyObject *Attribute_annotations = PyDict_New(); + if (!Attribute_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Attribute_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Attribute_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(Attribute_annotations, "attr", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Attribute_annotations); + return 0; + } + } + { + PyObject *type = state->expr_context_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Attribute_annotations, "ctx", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Attribute_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Attribute_type, "_field_types", + Attribute_annotations) == 0; + if (!cond) { + Py_DECREF(Attribute_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Attribute_type, "__annotations__", + Attribute_annotations) == 0; + if (!cond) { + Py_DECREF(Attribute_annotations); + return 0; + } + Py_DECREF(Attribute_annotations); + PyObject *Subscript_annotations = PyDict_New(); + if (!Subscript_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Subscript_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Subscript_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Subscript_annotations, "slice", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Subscript_annotations); + return 0; + } + } + { + PyObject *type = state->expr_context_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Subscript_annotations, "ctx", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Subscript_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Subscript_type, "_field_types", + Subscript_annotations) == 0; + if (!cond) { + Py_DECREF(Subscript_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Subscript_type, "__annotations__", + Subscript_annotations) == 0; + if (!cond) { + Py_DECREF(Subscript_annotations); + return 0; + } + Py_DECREF(Subscript_annotations); + PyObject *Starred_annotations = PyDict_New(); + if (!Starred_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Starred_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Starred_annotations); + return 0; + } + } + { + PyObject *type = state->expr_context_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Starred_annotations, "ctx", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Starred_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Starred_type, "_field_types", + Starred_annotations) == 0; + if (!cond) { + Py_DECREF(Starred_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Starred_type, "__annotations__", + Starred_annotations) == 0; + if (!cond) { + Py_DECREF(Starred_annotations); + return 0; + } + Py_DECREF(Starred_annotations); + PyObject *Name_annotations = PyDict_New(); + if (!Name_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(Name_annotations, "id", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Name_annotations); + return 0; + } + } + { + PyObject *type = state->expr_context_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Name_annotations, "ctx", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Name_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Name_type, "_field_types", + Name_annotations) == 0; + if (!cond) { + Py_DECREF(Name_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Name_type, "__annotations__", + Name_annotations) == 0; + if (!cond) { + Py_DECREF(Name_annotations); + return 0; + } + Py_DECREF(Name_annotations); + PyObject *List_annotations = PyDict_New(); + if (!List_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(List_annotations); + return 0; + } + cond = PyDict_SetItemString(List_annotations, "elts", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(List_annotations); + return 0; + } + } + { + PyObject *type = state->expr_context_type; + Py_INCREF(type); + cond = PyDict_SetItemString(List_annotations, "ctx", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(List_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->List_type, "_field_types", + List_annotations) == 0; + if (!cond) { + Py_DECREF(List_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->List_type, "__annotations__", + List_annotations) == 0; + if (!cond) { + Py_DECREF(List_annotations); + return 0; + } + Py_DECREF(List_annotations); + PyObject *Tuple_annotations = PyDict_New(); + if (!Tuple_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(Tuple_annotations); + return 0; + } + cond = PyDict_SetItemString(Tuple_annotations, "elts", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Tuple_annotations); + return 0; + } + } + { + PyObject *type = state->expr_context_type; + Py_INCREF(type); + cond = PyDict_SetItemString(Tuple_annotations, "ctx", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Tuple_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Tuple_type, "_field_types", + Tuple_annotations) == 0; + if (!cond) { + Py_DECREF(Tuple_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Tuple_type, "__annotations__", + Tuple_annotations) == 0; + if (!cond) { + Py_DECREF(Tuple_annotations); + return 0; + } + Py_DECREF(Tuple_annotations); + PyObject *Slice_annotations = PyDict_New(); + if (!Slice_annotations) return 0; + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Slice_annotations); + return 0; + } + cond = PyDict_SetItemString(Slice_annotations, "lower", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Slice_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Slice_annotations); + return 0; + } + cond = PyDict_SetItemString(Slice_annotations, "upper", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Slice_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(Slice_annotations); + return 0; + } + cond = PyDict_SetItemString(Slice_annotations, "step", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(Slice_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->Slice_type, "_field_types", + Slice_annotations) == 0; + if (!cond) { + Py_DECREF(Slice_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Slice_type, "__annotations__", + Slice_annotations) == 0; + if (!cond) { + Py_DECREF(Slice_annotations); + return 0; + } + Py_DECREF(Slice_annotations); + PyObject *Load_annotations = PyDict_New(); + if (!Load_annotations) return 0; + cond = PyObject_SetAttrString(state->Load_type, "_field_types", + Load_annotations) == 0; + if (!cond) { + Py_DECREF(Load_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Load_type, "__annotations__", + Load_annotations) == 0; + if (!cond) { + Py_DECREF(Load_annotations); + return 0; + } + Py_DECREF(Load_annotations); + PyObject *Store_annotations = PyDict_New(); + if (!Store_annotations) return 0; + cond = PyObject_SetAttrString(state->Store_type, "_field_types", + Store_annotations) == 0; + if (!cond) { + Py_DECREF(Store_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Store_type, "__annotations__", + Store_annotations) == 0; + if (!cond) { + Py_DECREF(Store_annotations); + return 0; + } + Py_DECREF(Store_annotations); + PyObject *Del_annotations = PyDict_New(); + if (!Del_annotations) return 0; + cond = PyObject_SetAttrString(state->Del_type, "_field_types", + Del_annotations) == 0; + if (!cond) { + Py_DECREF(Del_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Del_type, "__annotations__", + Del_annotations) == 0; + if (!cond) { + Py_DECREF(Del_annotations); + return 0; + } + Py_DECREF(Del_annotations); + PyObject *And_annotations = PyDict_New(); + if (!And_annotations) return 0; + cond = PyObject_SetAttrString(state->And_type, "_field_types", + And_annotations) == 0; + if (!cond) { + Py_DECREF(And_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->And_type, "__annotations__", + And_annotations) == 0; + if (!cond) { + Py_DECREF(And_annotations); + return 0; + } + Py_DECREF(And_annotations); + PyObject *Or_annotations = PyDict_New(); + if (!Or_annotations) return 0; + cond = PyObject_SetAttrString(state->Or_type, "_field_types", + Or_annotations) == 0; + if (!cond) { + Py_DECREF(Or_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Or_type, "__annotations__", + Or_annotations) == 0; + if (!cond) { + Py_DECREF(Or_annotations); + return 0; + } + Py_DECREF(Or_annotations); + PyObject *Add_annotations = PyDict_New(); + if (!Add_annotations) return 0; + cond = PyObject_SetAttrString(state->Add_type, "_field_types", + Add_annotations) == 0; + if (!cond) { + Py_DECREF(Add_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Add_type, "__annotations__", + Add_annotations) == 0; + if (!cond) { + Py_DECREF(Add_annotations); + return 0; + } + Py_DECREF(Add_annotations); + PyObject *Sub_annotations = PyDict_New(); + if (!Sub_annotations) return 0; + cond = PyObject_SetAttrString(state->Sub_type, "_field_types", + Sub_annotations) == 0; + if (!cond) { + Py_DECREF(Sub_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Sub_type, "__annotations__", + Sub_annotations) == 0; + if (!cond) { + Py_DECREF(Sub_annotations); + return 0; + } + Py_DECREF(Sub_annotations); + PyObject *Mult_annotations = PyDict_New(); + if (!Mult_annotations) return 0; + cond = PyObject_SetAttrString(state->Mult_type, "_field_types", + Mult_annotations) == 0; + if (!cond) { + Py_DECREF(Mult_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Mult_type, "__annotations__", + Mult_annotations) == 0; + if (!cond) { + Py_DECREF(Mult_annotations); + return 0; + } + Py_DECREF(Mult_annotations); + PyObject *MatMult_annotations = PyDict_New(); + if (!MatMult_annotations) return 0; + cond = PyObject_SetAttrString(state->MatMult_type, "_field_types", + MatMult_annotations) == 0; + if (!cond) { + Py_DECREF(MatMult_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatMult_type, "__annotations__", + MatMult_annotations) == 0; + if (!cond) { + Py_DECREF(MatMult_annotations); + return 0; + } + Py_DECREF(MatMult_annotations); + PyObject *Div_annotations = PyDict_New(); + if (!Div_annotations) return 0; + cond = PyObject_SetAttrString(state->Div_type, "_field_types", + Div_annotations) == 0; + if (!cond) { + Py_DECREF(Div_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Div_type, "__annotations__", + Div_annotations) == 0; + if (!cond) { + Py_DECREF(Div_annotations); + return 0; + } + Py_DECREF(Div_annotations); + PyObject *Mod_annotations = PyDict_New(); + if (!Mod_annotations) return 0; + cond = PyObject_SetAttrString(state->Mod_type, "_field_types", + Mod_annotations) == 0; + if (!cond) { + Py_DECREF(Mod_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Mod_type, "__annotations__", + Mod_annotations) == 0; + if (!cond) { + Py_DECREF(Mod_annotations); + return 0; + } + Py_DECREF(Mod_annotations); + PyObject *Pow_annotations = PyDict_New(); + if (!Pow_annotations) return 0; + cond = PyObject_SetAttrString(state->Pow_type, "_field_types", + Pow_annotations) == 0; + if (!cond) { + Py_DECREF(Pow_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Pow_type, "__annotations__", + Pow_annotations) == 0; + if (!cond) { + Py_DECREF(Pow_annotations); + return 0; + } + Py_DECREF(Pow_annotations); + PyObject *LShift_annotations = PyDict_New(); + if (!LShift_annotations) return 0; + cond = PyObject_SetAttrString(state->LShift_type, "_field_types", + LShift_annotations) == 0; + if (!cond) { + Py_DECREF(LShift_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->LShift_type, "__annotations__", + LShift_annotations) == 0; + if (!cond) { + Py_DECREF(LShift_annotations); + return 0; + } + Py_DECREF(LShift_annotations); + PyObject *RShift_annotations = PyDict_New(); + if (!RShift_annotations) return 0; + cond = PyObject_SetAttrString(state->RShift_type, "_field_types", + RShift_annotations) == 0; + if (!cond) { + Py_DECREF(RShift_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->RShift_type, "__annotations__", + RShift_annotations) == 0; + if (!cond) { + Py_DECREF(RShift_annotations); + return 0; + } + Py_DECREF(RShift_annotations); + PyObject *BitOr_annotations = PyDict_New(); + if (!BitOr_annotations) return 0; + cond = PyObject_SetAttrString(state->BitOr_type, "_field_types", + BitOr_annotations) == 0; + if (!cond) { + Py_DECREF(BitOr_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->BitOr_type, "__annotations__", + BitOr_annotations) == 0; + if (!cond) { + Py_DECREF(BitOr_annotations); + return 0; + } + Py_DECREF(BitOr_annotations); + PyObject *BitXor_annotations = PyDict_New(); + if (!BitXor_annotations) return 0; + cond = PyObject_SetAttrString(state->BitXor_type, "_field_types", + BitXor_annotations) == 0; + if (!cond) { + Py_DECREF(BitXor_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->BitXor_type, "__annotations__", + BitXor_annotations) == 0; + if (!cond) { + Py_DECREF(BitXor_annotations); + return 0; + } + Py_DECREF(BitXor_annotations); + PyObject *BitAnd_annotations = PyDict_New(); + if (!BitAnd_annotations) return 0; + cond = PyObject_SetAttrString(state->BitAnd_type, "_field_types", + BitAnd_annotations) == 0; + if (!cond) { + Py_DECREF(BitAnd_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->BitAnd_type, "__annotations__", + BitAnd_annotations) == 0; + if (!cond) { + Py_DECREF(BitAnd_annotations); + return 0; + } + Py_DECREF(BitAnd_annotations); + PyObject *FloorDiv_annotations = PyDict_New(); + if (!FloorDiv_annotations) return 0; + cond = PyObject_SetAttrString(state->FloorDiv_type, "_field_types", + FloorDiv_annotations) == 0; + if (!cond) { + Py_DECREF(FloorDiv_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->FloorDiv_type, "__annotations__", + FloorDiv_annotations) == 0; + if (!cond) { + Py_DECREF(FloorDiv_annotations); + return 0; + } + Py_DECREF(FloorDiv_annotations); + PyObject *Invert_annotations = PyDict_New(); + if (!Invert_annotations) return 0; + cond = PyObject_SetAttrString(state->Invert_type, "_field_types", + Invert_annotations) == 0; + if (!cond) { + Py_DECREF(Invert_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Invert_type, "__annotations__", + Invert_annotations) == 0; + if (!cond) { + Py_DECREF(Invert_annotations); + return 0; + } + Py_DECREF(Invert_annotations); + PyObject *Not_annotations = PyDict_New(); + if (!Not_annotations) return 0; + cond = PyObject_SetAttrString(state->Not_type, "_field_types", + Not_annotations) == 0; + if (!cond) { + Py_DECREF(Not_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Not_type, "__annotations__", + Not_annotations) == 0; + if (!cond) { + Py_DECREF(Not_annotations); + return 0; + } + Py_DECREF(Not_annotations); + PyObject *UAdd_annotations = PyDict_New(); + if (!UAdd_annotations) return 0; + cond = PyObject_SetAttrString(state->UAdd_type, "_field_types", + UAdd_annotations) == 0; + if (!cond) { + Py_DECREF(UAdd_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->UAdd_type, "__annotations__", + UAdd_annotations) == 0; + if (!cond) { + Py_DECREF(UAdd_annotations); + return 0; + } + Py_DECREF(UAdd_annotations); + PyObject *USub_annotations = PyDict_New(); + if (!USub_annotations) return 0; + cond = PyObject_SetAttrString(state->USub_type, "_field_types", + USub_annotations) == 0; + if (!cond) { + Py_DECREF(USub_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->USub_type, "__annotations__", + USub_annotations) == 0; + if (!cond) { + Py_DECREF(USub_annotations); + return 0; + } + Py_DECREF(USub_annotations); + PyObject *Eq_annotations = PyDict_New(); + if (!Eq_annotations) return 0; + cond = PyObject_SetAttrString(state->Eq_type, "_field_types", + Eq_annotations) == 0; + if (!cond) { + Py_DECREF(Eq_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Eq_type, "__annotations__", + Eq_annotations) == 0; + if (!cond) { + Py_DECREF(Eq_annotations); + return 0; + } + Py_DECREF(Eq_annotations); + PyObject *NotEq_annotations = PyDict_New(); + if (!NotEq_annotations) return 0; + cond = PyObject_SetAttrString(state->NotEq_type, "_field_types", + NotEq_annotations) == 0; + if (!cond) { + Py_DECREF(NotEq_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->NotEq_type, "__annotations__", + NotEq_annotations) == 0; + if (!cond) { + Py_DECREF(NotEq_annotations); + return 0; + } + Py_DECREF(NotEq_annotations); + PyObject *Lt_annotations = PyDict_New(); + if (!Lt_annotations) return 0; + cond = PyObject_SetAttrString(state->Lt_type, "_field_types", + Lt_annotations) == 0; + if (!cond) { + Py_DECREF(Lt_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Lt_type, "__annotations__", + Lt_annotations) == 0; + if (!cond) { + Py_DECREF(Lt_annotations); + return 0; + } + Py_DECREF(Lt_annotations); + PyObject *LtE_annotations = PyDict_New(); + if (!LtE_annotations) return 0; + cond = PyObject_SetAttrString(state->LtE_type, "_field_types", + LtE_annotations) == 0; + if (!cond) { + Py_DECREF(LtE_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->LtE_type, "__annotations__", + LtE_annotations) == 0; + if (!cond) { + Py_DECREF(LtE_annotations); + return 0; + } + Py_DECREF(LtE_annotations); + PyObject *Gt_annotations = PyDict_New(); + if (!Gt_annotations) return 0; + cond = PyObject_SetAttrString(state->Gt_type, "_field_types", + Gt_annotations) == 0; + if (!cond) { + Py_DECREF(Gt_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Gt_type, "__annotations__", + Gt_annotations) == 0; + if (!cond) { + Py_DECREF(Gt_annotations); + return 0; + } + Py_DECREF(Gt_annotations); + PyObject *GtE_annotations = PyDict_New(); + if (!GtE_annotations) return 0; + cond = PyObject_SetAttrString(state->GtE_type, "_field_types", + GtE_annotations) == 0; + if (!cond) { + Py_DECREF(GtE_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->GtE_type, "__annotations__", + GtE_annotations) == 0; + if (!cond) { + Py_DECREF(GtE_annotations); + return 0; + } + Py_DECREF(GtE_annotations); + PyObject *Is_annotations = PyDict_New(); + if (!Is_annotations) return 0; + cond = PyObject_SetAttrString(state->Is_type, "_field_types", + Is_annotations) == 0; + if (!cond) { + Py_DECREF(Is_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->Is_type, "__annotations__", + Is_annotations) == 0; + if (!cond) { + Py_DECREF(Is_annotations); + return 0; + } + Py_DECREF(Is_annotations); + PyObject *IsNot_annotations = PyDict_New(); + if (!IsNot_annotations) return 0; + cond = PyObject_SetAttrString(state->IsNot_type, "_field_types", + IsNot_annotations) == 0; + if (!cond) { + Py_DECREF(IsNot_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->IsNot_type, "__annotations__", + IsNot_annotations) == 0; + if (!cond) { + Py_DECREF(IsNot_annotations); + return 0; + } + Py_DECREF(IsNot_annotations); + PyObject *In_annotations = PyDict_New(); + if (!In_annotations) return 0; + cond = PyObject_SetAttrString(state->In_type, "_field_types", + In_annotations) == 0; + if (!cond) { + Py_DECREF(In_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->In_type, "__annotations__", + In_annotations) == 0; + if (!cond) { + Py_DECREF(In_annotations); + return 0; + } + Py_DECREF(In_annotations); + PyObject *NotIn_annotations = PyDict_New(); + if (!NotIn_annotations) return 0; + cond = PyObject_SetAttrString(state->NotIn_type, "_field_types", + NotIn_annotations) == 0; + if (!cond) { + Py_DECREF(NotIn_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->NotIn_type, "__annotations__", + NotIn_annotations) == 0; + if (!cond) { + Py_DECREF(NotIn_annotations); + return 0; + } + Py_DECREF(NotIn_annotations); + PyObject *comprehension_annotations = PyDict_New(); + if (!comprehension_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(comprehension_annotations, "target", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(comprehension_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(comprehension_annotations, "iter", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(comprehension_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(comprehension_annotations); + return 0; + } + cond = PyDict_SetItemString(comprehension_annotations, "ifs", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(comprehension_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyLong_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(comprehension_annotations, "is_async", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(comprehension_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->comprehension_type, "_field_types", + comprehension_annotations) == 0; + if (!cond) { + Py_DECREF(comprehension_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->comprehension_type, "__annotations__", + comprehension_annotations) == 0; + if (!cond) { + Py_DECREF(comprehension_annotations); + return 0; + } + Py_DECREF(comprehension_annotations); + PyObject *ExceptHandler_annotations = PyDict_New(); + if (!ExceptHandler_annotations) return 0; + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(ExceptHandler_annotations); + return 0; + } + cond = PyDict_SetItemString(ExceptHandler_annotations, "type", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ExceptHandler_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(ExceptHandler_annotations); + return 0; + } + cond = PyDict_SetItemString(ExceptHandler_annotations, "name", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ExceptHandler_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(ExceptHandler_annotations); + return 0; + } + cond = PyDict_SetItemString(ExceptHandler_annotations, "body", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ExceptHandler_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->ExceptHandler_type, "_field_types", + ExceptHandler_annotations) == 0; + if (!cond) { + Py_DECREF(ExceptHandler_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->ExceptHandler_type, "__annotations__", + ExceptHandler_annotations) == 0; + if (!cond) { + Py_DECREF(ExceptHandler_annotations); + return 0; + } + Py_DECREF(ExceptHandler_annotations); + PyObject *arguments_annotations = PyDict_New(); + if (!arguments_annotations) return 0; + { + PyObject *type = state->arg_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + cond = PyDict_SetItemString(arguments_annotations, "posonlyargs", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + } + { + PyObject *type = state->arg_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + cond = PyDict_SetItemString(arguments_annotations, "args", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + } + { + PyObject *type = state->arg_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + cond = PyDict_SetItemString(arguments_annotations, "vararg", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + } + { + PyObject *type = state->arg_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + cond = PyDict_SetItemString(arguments_annotations, "kwonlyargs", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + cond = PyDict_SetItemString(arguments_annotations, "kw_defaults", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + } + { + PyObject *type = state->arg_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + cond = PyDict_SetItemString(arguments_annotations, "kwarg", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + cond = PyDict_SetItemString(arguments_annotations, "defaults", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->arguments_type, "_field_types", + arguments_annotations) == 0; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->arguments_type, "__annotations__", + arguments_annotations) == 0; + if (!cond) { + Py_DECREF(arguments_annotations); + return 0; + } + Py_DECREF(arguments_annotations); + PyObject *arg_annotations = PyDict_New(); + if (!arg_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(arg_annotations, "arg", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arg_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(arg_annotations); + return 0; + } + cond = PyDict_SetItemString(arg_annotations, "annotation", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arg_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(arg_annotations); + return 0; + } + cond = PyDict_SetItemString(arg_annotations, "type_comment", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(arg_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->arg_type, "_field_types", + arg_annotations) == 0; + if (!cond) { + Py_DECREF(arg_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->arg_type, "__annotations__", + arg_annotations) == 0; + if (!cond) { + Py_DECREF(arg_annotations); + return 0; + } + Py_DECREF(arg_annotations); + PyObject *keyword_annotations = PyDict_New(); + if (!keyword_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(keyword_annotations); + return 0; + } + cond = PyDict_SetItemString(keyword_annotations, "arg", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(keyword_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(keyword_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(keyword_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->keyword_type, "_field_types", + keyword_annotations) == 0; + if (!cond) { + Py_DECREF(keyword_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->keyword_type, "__annotations__", + keyword_annotations) == 0; + if (!cond) { + Py_DECREF(keyword_annotations); + return 0; + } + Py_DECREF(keyword_annotations); + PyObject *alias_annotations = PyDict_New(); + if (!alias_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(alias_annotations, "name", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(alias_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(alias_annotations); + return 0; + } + cond = PyDict_SetItemString(alias_annotations, "asname", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(alias_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->alias_type, "_field_types", + alias_annotations) == 0; + if (!cond) { + Py_DECREF(alias_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->alias_type, "__annotations__", + alias_annotations) == 0; + if (!cond) { + Py_DECREF(alias_annotations); + return 0; + } + Py_DECREF(alias_annotations); + PyObject *withitem_annotations = PyDict_New(); + if (!withitem_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(withitem_annotations, "context_expr", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(withitem_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(withitem_annotations); + return 0; + } + cond = PyDict_SetItemString(withitem_annotations, "optional_vars", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(withitem_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->withitem_type, "_field_types", + withitem_annotations) == 0; + if (!cond) { + Py_DECREF(withitem_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->withitem_type, "__annotations__", + withitem_annotations) == 0; + if (!cond) { + Py_DECREF(withitem_annotations); + return 0; + } + Py_DECREF(withitem_annotations); + PyObject *match_case_annotations = PyDict_New(); + if (!match_case_annotations) return 0; + { + PyObject *type = state->pattern_type; + Py_INCREF(type); + cond = PyDict_SetItemString(match_case_annotations, "pattern", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(match_case_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(match_case_annotations); + return 0; + } + cond = PyDict_SetItemString(match_case_annotations, "guard", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(match_case_annotations); + return 0; + } + } + { + PyObject *type = state->stmt_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(match_case_annotations); + return 0; + } + cond = PyDict_SetItemString(match_case_annotations, "body", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(match_case_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->match_case_type, "_field_types", + match_case_annotations) == 0; + if (!cond) { + Py_DECREF(match_case_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->match_case_type, "__annotations__", + match_case_annotations) == 0; + if (!cond) { + Py_DECREF(match_case_annotations); + return 0; + } + Py_DECREF(match_case_annotations); + PyObject *MatchValue_annotations = PyDict_New(); + if (!MatchValue_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(MatchValue_annotations, "value", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchValue_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->MatchValue_type, "_field_types", + MatchValue_annotations) == 0; + if (!cond) { + Py_DECREF(MatchValue_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatchValue_type, "__annotations__", + MatchValue_annotations) == 0; + if (!cond) { + Py_DECREF(MatchValue_annotations); + return 0; + } + Py_DECREF(MatchValue_annotations); + PyObject *MatchSingleton_annotations = PyDict_New(); + if (!MatchSingleton_annotations) return 0; + { + PyObject *type = (PyObject *)&PyBaseObject_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(MatchSingleton_annotations, "value", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchSingleton_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->MatchSingleton_type, "_field_types", + MatchSingleton_annotations) == 0; + if (!cond) { + Py_DECREF(MatchSingleton_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatchSingleton_type, + "__annotations__", + MatchSingleton_annotations) == 0; + if (!cond) { + Py_DECREF(MatchSingleton_annotations); + return 0; + } + Py_DECREF(MatchSingleton_annotations); + PyObject *MatchSequence_annotations = PyDict_New(); + if (!MatchSequence_annotations) return 0; + { + PyObject *type = state->pattern_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchSequence_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchSequence_annotations, "patterns", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchSequence_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->MatchSequence_type, "_field_types", + MatchSequence_annotations) == 0; + if (!cond) { + Py_DECREF(MatchSequence_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatchSequence_type, "__annotations__", + MatchSequence_annotations) == 0; + if (!cond) { + Py_DECREF(MatchSequence_annotations); + return 0; + } + Py_DECREF(MatchSequence_annotations); + PyObject *MatchMapping_annotations = PyDict_New(); + if (!MatchMapping_annotations) return 0; + { + PyObject *type = state->expr_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchMapping_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchMapping_annotations, "keys", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchMapping_annotations); + return 0; + } + } + { + PyObject *type = state->pattern_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchMapping_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchMapping_annotations, "patterns", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchMapping_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchMapping_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchMapping_annotations, "rest", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchMapping_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->MatchMapping_type, "_field_types", + MatchMapping_annotations) == 0; + if (!cond) { + Py_DECREF(MatchMapping_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatchMapping_type, "__annotations__", + MatchMapping_annotations) == 0; + if (!cond) { + Py_DECREF(MatchMapping_annotations); + return 0; + } + Py_DECREF(MatchMapping_annotations); + PyObject *MatchClass_annotations = PyDict_New(); + if (!MatchClass_annotations) return 0; + { + PyObject *type = state->expr_type; + Py_INCREF(type); + cond = PyDict_SetItemString(MatchClass_annotations, "cls", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + } + { + PyObject *type = state->pattern_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchClass_annotations, "patterns", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchClass_annotations, "kwd_attrs", type) + == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + } + { + PyObject *type = state->pattern_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchClass_annotations, "kwd_patterns", + type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->MatchClass_type, "_field_types", + MatchClass_annotations) == 0; + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatchClass_type, "__annotations__", + MatchClass_annotations) == 0; + if (!cond) { + Py_DECREF(MatchClass_annotations); + return 0; + } + Py_DECREF(MatchClass_annotations); + PyObject *MatchStar_annotations = PyDict_New(); + if (!MatchStar_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchStar_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchStar_annotations, "name", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchStar_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->MatchStar_type, "_field_types", + MatchStar_annotations) == 0; + if (!cond) { + Py_DECREF(MatchStar_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatchStar_type, "__annotations__", + MatchStar_annotations) == 0; + if (!cond) { + Py_DECREF(MatchStar_annotations); + return 0; + } + Py_DECREF(MatchStar_annotations); + PyObject *MatchAs_annotations = PyDict_New(); + if (!MatchAs_annotations) return 0; + { + PyObject *type = state->pattern_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchAs_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchAs_annotations, "pattern", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchAs_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchAs_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchAs_annotations, "name", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchAs_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->MatchAs_type, "_field_types", + MatchAs_annotations) == 0; + if (!cond) { + Py_DECREF(MatchAs_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatchAs_type, "__annotations__", + MatchAs_annotations) == 0; + if (!cond) { + Py_DECREF(MatchAs_annotations); + return 0; + } + Py_DECREF(MatchAs_annotations); + PyObject *MatchOr_annotations = PyDict_New(); + if (!MatchOr_annotations) return 0; + { + PyObject *type = state->pattern_type; + type = Py_GenericAlias((PyObject *)&PyList_Type, type); + cond = type != NULL; + if (!cond) { + Py_DECREF(MatchOr_annotations); + return 0; + } + cond = PyDict_SetItemString(MatchOr_annotations, "patterns", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(MatchOr_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->MatchOr_type, "_field_types", + MatchOr_annotations) == 0; + if (!cond) { + Py_DECREF(MatchOr_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->MatchOr_type, "__annotations__", + MatchOr_annotations) == 0; + if (!cond) { + Py_DECREF(MatchOr_annotations); + return 0; + } + Py_DECREF(MatchOr_annotations); + PyObject *TypeIgnore_annotations = PyDict_New(); + if (!TypeIgnore_annotations) return 0; + { + PyObject *type = (PyObject *)&PyLong_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(TypeIgnore_annotations, "lineno", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TypeIgnore_annotations); + return 0; + } + } + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(TypeIgnore_annotations, "tag", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TypeIgnore_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->TypeIgnore_type, "_field_types", + TypeIgnore_annotations) == 0; + if (!cond) { + Py_DECREF(TypeIgnore_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->TypeIgnore_type, "__annotations__", + TypeIgnore_annotations) == 0; + if (!cond) { + Py_DECREF(TypeIgnore_annotations); + return 0; + } + Py_DECREF(TypeIgnore_annotations); + PyObject *TypeVar_annotations = PyDict_New(); + if (!TypeVar_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(TypeVar_annotations, "name", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TypeVar_annotations); + return 0; + } + } + { + PyObject *type = state->expr_type; + type = _Py_union_type_or(type, Py_None); + cond = type != NULL; + if (!cond) { + Py_DECREF(TypeVar_annotations); + return 0; + } + cond = PyDict_SetItemString(TypeVar_annotations, "bound", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TypeVar_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->TypeVar_type, "_field_types", + TypeVar_annotations) == 0; + if (!cond) { + Py_DECREF(TypeVar_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->TypeVar_type, "__annotations__", + TypeVar_annotations) == 0; + if (!cond) { + Py_DECREF(TypeVar_annotations); + return 0; + } + Py_DECREF(TypeVar_annotations); + PyObject *ParamSpec_annotations = PyDict_New(); + if (!ParamSpec_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(ParamSpec_annotations, "name", type) == 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(ParamSpec_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->ParamSpec_type, "_field_types", + ParamSpec_annotations) == 0; + if (!cond) { + Py_DECREF(ParamSpec_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->ParamSpec_type, "__annotations__", + ParamSpec_annotations) == 0; + if (!cond) { + Py_DECREF(ParamSpec_annotations); + return 0; + } + Py_DECREF(ParamSpec_annotations); + PyObject *TypeVarTuple_annotations = PyDict_New(); + if (!TypeVarTuple_annotations) return 0; + { + PyObject *type = (PyObject *)&PyUnicode_Type; + Py_INCREF(type); + cond = PyDict_SetItemString(TypeVarTuple_annotations, "name", type) == + 0; + Py_DECREF(type); + if (!cond) { + Py_DECREF(TypeVarTuple_annotations); + return 0; + } + } + cond = PyObject_SetAttrString(state->TypeVarTuple_type, "_field_types", + TypeVarTuple_annotations) == 0; + if (!cond) { + Py_DECREF(TypeVarTuple_annotations); + return 0; + } + cond = PyObject_SetAttrString(state->TypeVarTuple_type, "__annotations__", + TypeVarTuple_annotations) == 0; + if (!cond) { + Py_DECREF(TypeVarTuple_annotations); + return 0; + } + Py_DECREF(TypeVarTuple_annotations); + + return 1; +} + + typedef struct { PyObject_HEAD @@ -860,7 +5026,7 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) Py_ssize_t i, numfields = 0; int res = -1; - PyObject *key, *value, *fields; + PyObject *key, *value, *fields, *remaining_fields = NULL; if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), state->_fields, &fields) < 0) { goto cleanup; } @@ -869,6 +5035,13 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) if (numfields == -1) { goto cleanup; } + remaining_fields = PySet_New(fields); + } + else { + remaining_fields = PySet_New(NULL); + } + if (remaining_fields == NULL) { + goto cleanup; } res = 0; /* if no error occurs, this stays 0 to the end */ @@ -888,6 +5061,11 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) goto cleanup; } res = PyObject_SetAttr(self, name, PyTuple_GET_ITEM(args, i)); + if (PySet_Discard(remaining_fields, name) < 0) { + res = -1; + Py_DECREF(name); + goto cleanup; + } Py_DECREF(name); if (res < 0) { goto cleanup; @@ -900,13 +5078,14 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) if (contains == -1) { res = -1; goto cleanup; - } else if (contains == 1) { - Py_ssize_t p = PySequence_Index(fields, key); + } + else if (contains == 1) { + int p = PySet_Discard(remaining_fields, key); if (p == -1) { res = -1; goto cleanup; } - if (p < PyTuple_GET_SIZE(args)) { + if (p == 0) { PyErr_Format(PyExc_TypeError, "%.400s got multiple values for argument '%U'", Py_TYPE(self)->tp_name, key); @@ -914,15 +5093,91 @@ ast_type_init(PyObject *self, PyObject *args, PyObject *kw) goto cleanup; } } + else if ( + PyUnicode_CompareWithASCIIString(key, "lineno") != 0 && + PyUnicode_CompareWithASCIIString(key, "col_offset") != 0 && + PyUnicode_CompareWithASCIIString(key, "end_lineno") != 0 && + PyUnicode_CompareWithASCIIString(key, "end_col_offset") != 0 + ) { + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, 1, + "%.400s.__init__ got an unexpected keyword argument '%U'. " + "Support for arbitrary keyword arguments is deprecated " + "and will be removed in Python 3.15.", + Py_TYPE(self)->tp_name, key + ) < 0) { + res = -1; + goto cleanup; + } + } res = PyObject_SetAttr(self, key, value); if (res < 0) { goto cleanup; } } } + Py_ssize_t size = PySet_Size(remaining_fields); + PyObject *field_types = NULL, *remaining_list = NULL; + if (size > 0) { + if (!PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), &_Py_ID(_field_types), + &field_types)) { + res = -1; + goto cleanup; + } + remaining_list = PySequence_List(remaining_fields); + if (!remaining_list) { + goto set_remaining_cleanup; + } + for (Py_ssize_t i = 0; i < size; i++) { + PyObject *name = PyList_GET_ITEM(remaining_list, i); + PyObject *type = PyDict_GetItemWithError(field_types, name); + if (!type) { + if (!PyErr_Occurred()) { + PyErr_SetObject(PyExc_KeyError, name); + } + goto set_remaining_cleanup; + } + if (_PyUnion_Check(type)) { + // optional field + // do nothing, we'll have set a None default on the class + } + else if (Py_IS_TYPE(type, &Py_GenericAliasType)) { + // list field + PyObject *empty = PyList_New(0); + if (!empty) { + goto set_remaining_cleanup; + } + res = PyObject_SetAttr(self, name, empty); + Py_DECREF(empty); + if (res < 0) { + goto set_remaining_cleanup; + } + } + else { + // simple field (e.g., identifier) + if (PyErr_WarnFormat( + PyExc_DeprecationWarning, 1, + "%.400s.__init__ missing 1 required positional argument: '%U'. " + "This will become an error in Python 3.15.", + Py_TYPE(self)->tp_name, name + ) < 0) { + res = -1; + goto cleanup; + } + } + } + Py_DECREF(remaining_list); + Py_DECREF(field_types); + } cleanup: Py_XDECREF(fields); + Py_XDECREF(remaining_fields); return res; + set_remaining_cleanup: + Py_XDECREF(remaining_list); + Py_XDECREF(field_types); + res = -1; + goto cleanup; } /* Pickling support */ @@ -934,14 +5189,75 @@ ast_type_reduce(PyObject *self, PyObject *unused) return NULL; } - PyObject *dict; + PyObject *dict = NULL, *fields = NULL, *remaining_fields = NULL, + *remaining_dict = NULL, *positional_args = NULL; if (PyObject_GetOptionalAttr(self, state->__dict__, &dict) < 0) { return NULL; } + PyObject *result = NULL; if (dict) { - return Py_BuildValue("O()N", Py_TYPE(self), dict); + // Serialize the fields as positional args if possible, because if we + // serialize them as a dict, during unpickling they are set only *after* + // the object is constructed, which will now trigger a DeprecationWarning + // if the AST type has required fields. + if (PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), state->_fields, &fields) < 0) { + goto cleanup; + } + if (fields) { + Py_ssize_t numfields = PySequence_Size(fields); + if (numfields == -1) { + Py_DECREF(dict); + goto cleanup; + } + remaining_dict = PyDict_Copy(dict); + Py_DECREF(dict); + if (!remaining_dict) { + goto cleanup; + } + positional_args = PyList_New(0); + if (!positional_args) { + goto cleanup; + } + for (Py_ssize_t i = 0; i < numfields; i++) { + PyObject *name = PySequence_GetItem(fields, i); + if (!name) { + goto cleanup; + } + PyObject *value; + int rc = PyDict_Pop(remaining_dict, name, &value); + Py_DECREF(name); + if (rc < 0) { + goto cleanup; + } + if (!value) { + break; + } + rc = PyList_Append(positional_args, value); + Py_DECREF(value); + if (rc < 0) { + goto cleanup; + } + } + PyObject *args_tuple = PyList_AsTuple(positional_args); + if (!args_tuple) { + goto cleanup; + } + result = Py_BuildValue("ONO", Py_TYPE(self), args_tuple, + remaining_dict); + } + else { + result = Py_BuildValue("O()N", Py_TYPE(self), dict); + } + } + else { + result = Py_BuildValue("O()", Py_TYPE(self)); } - return Py_BuildValue("O()", Py_TYPE(self)); +cleanup: + Py_XDECREF(fields); + Py_XDECREF(remaining_fields); + Py_XDECREF(remaining_dict); + Py_XDECREF(positional_args); + return result; } static PyMemberDef ast_type_members[] = { @@ -1939,6 +6255,9 @@ init_types(struct ast_state *state) "TypeVarTuple(identifier name)"); if (!state->TypeVarTuple_type) return -1; + if (!add_ast_annotations(state)) { + return -1; + } return 0; } diff --git a/Python/_warnings.c b/Python/_warnings.c index d4765032824e566..dfa82c569e13838 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -1,5 +1,4 @@ #include "Python.h" -#include "pycore_dict.h" // _PyDict_GetItemWithError() #include "pycore_interp.h" // PyInterpreterState.warnings #include "pycore_long.h" // _PyLong_GetZero() #include "pycore_pyerrors.h" // _PyErr_Occurred() @@ -8,6 +7,8 @@ #include "pycore_sysmodule.h" // _PySys_GetAttr() #include "pycore_traceback.h" // _Py_DisplaySourceLine() +#include + #include "clinic/_warnings.c.h" #define MODULE_NAME "_warnings" @@ -397,7 +398,7 @@ static int already_warned(PyInterpreterState *interp, PyObject *registry, PyObject *key, int should_set) { - PyObject *version_obj, *already_warned; + PyObject *already_warned; if (key == NULL) return -1; @@ -406,14 +407,17 @@ already_warned(PyInterpreterState *interp, PyObject *registry, PyObject *key, if (st == NULL) { return -1; } - version_obj = _PyDict_GetItemWithError(registry, &_Py_ID(version)); - if (version_obj == NULL + PyObject *version_obj; + if (PyDict_GetItemRef(registry, &_Py_ID(version), &version_obj) < 0) { + return -1; + } + bool should_update_version = ( + version_obj == NULL || !PyLong_CheckExact(version_obj) - || PyLong_AsLong(version_obj) != st->filters_version) - { - if (PyErr_Occurred()) { - return -1; - } + || PyLong_AsLong(version_obj) != st->filters_version + ); + Py_XDECREF(version_obj); + if (should_update_version) { PyDict_Clear(registry); version_obj = PyLong_FromLong(st->filters_version); if (version_obj == NULL) @@ -911,13 +915,12 @@ setup_context(Py_ssize_t stack_level, /* Setup registry. */ assert(globals != NULL); assert(PyDict_Check(globals)); - *registry = _PyDict_GetItemWithError(globals, &_Py_ID(__warningregistry__)); + int rc = PyDict_GetItemRef(globals, &_Py_ID(__warningregistry__), + registry); + if (rc < 0) { + goto handle_error; + } if (*registry == NULL) { - int rc; - - if (_PyErr_Occurred(tstate)) { - goto handle_error; - } *registry = PyDict_New(); if (*registry == NULL) goto handle_error; @@ -926,21 +929,21 @@ setup_context(Py_ssize_t stack_level, if (rc < 0) goto handle_error; } - else - Py_INCREF(*registry); /* Setup module. */ - *module = _PyDict_GetItemWithError(globals, &_Py_ID(__name__)); - if (*module == Py_None || (*module != NULL && PyUnicode_Check(*module))) { - Py_INCREF(*module); - } - else if (_PyErr_Occurred(tstate)) { + rc = PyDict_GetItemRef(globals, &_Py_ID(__name__), module); + if (rc < 0) { goto handle_error; } - else { - *module = PyUnicode_FromString(""); - if (*module == NULL) - goto handle_error; + if (rc > 0) { + if (Py_IsNone(*module) || PyUnicode_Check(*module)) { + return 1; + } + Py_DECREF(*module); + } + *module = PyUnicode_FromString(""); + if (*module == NULL) { + goto handle_error; } return 1; @@ -1063,12 +1066,12 @@ get_source_line(PyInterpreterState *interp, PyObject *module_globals, int lineno return NULL; } - module_name = _PyDict_GetItemWithError(module_globals, &_Py_ID(__name__)); - if (!module_name) { + int rc = PyDict_GetItemRef(module_globals, &_Py_ID(__name__), + &module_name); + if (rc < 0 || rc == 0) { Py_DECREF(loader); return NULL; } - Py_INCREF(module_name); /* Make sure the loader implements the optional get_source() method. */ (void)PyObject_GetOptionalAttr(loader, &_Py_ID(get_source), &get_source); diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index b0074962b737992..f66a8c07c6f872b 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -140,13 +140,10 @@ builtin___build_class__(PyObject *self, PyObject *const *args, Py_ssize_t nargs, goto error; } - if (PyDict_GetItemRef(mkw, &_Py_ID(metaclass), &meta) < 0) { + if (PyDict_Pop(mkw, &_Py_ID(metaclass), &meta) < 0) { goto error; } if (meta != NULL) { - if (PyDict_DelItem(mkw, &_Py_ID(metaclass)) < 0) { - goto error; - } /* metaclass is explicitly given, check if it's indeed a class */ isclass = PyType_Check(meta); } diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 7e2c9c4d6a6db46..476975d2fbc3c2d 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -129,6 +129,8 @@ dummy_func( PyObject *top; PyObject *type; PyObject *typevars; + PyObject *val0; + PyObject *val1; int values_or_none; switch (opcode) { @@ -141,8 +143,7 @@ dummy_func( RESUME_CHECK, }; - inst(RESUME, (--)) { - TIER_ONE_ONLY + tier1 inst(RESUME, (--)) { assert(frame == tstate->current_frame); uintptr_t global_version = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & @@ -268,8 +269,7 @@ dummy_func( macro(END_FOR) = POP_TOP; - inst(INSTRUMENTED_END_FOR, (receiver, value -- receiver)) { - TIER_ONE_ONLY + tier1 inst(INSTRUMENTED_END_FOR, (receiver, value -- receiver)) { /* Need to create a fake StopIteration error here, * to conform to PEP 380 */ if (PyGen_Check(receiver)) { @@ -286,8 +286,7 @@ dummy_func( Py_DECREF(receiver); } - inst(INSTRUMENTED_END_SEND, (receiver, value -- value)) { - TIER_ONE_ONLY + tier1 inst(INSTRUMENTED_END_SEND, (receiver, value -- value)) { if (PyGen_Check(receiver) || PyCoro_CheckExact(receiver)) { PyErr_SetObject(PyExc_StopIteration, value); if (monitor_stop_iteration(tstate, frame, this_instr)) { @@ -319,7 +318,6 @@ dummy_func( }; specializing op(_SPECIALIZE_TO_BOOL, (counter/1, value -- value)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -386,15 +384,16 @@ dummy_func( } } - inst(TO_BOOL_ALWAYS_TRUE, (unused/1, version/2, value -- res)) { - // This one is a bit weird, because we expect *some* failures: - assert(version); - EXIT_IF(Py_TYPE(value)->tp_version_tag != version); - STAT_INC(TO_BOOL, hit); - DECREF_INPUTS(); + op(_REPLACE_WITH_TRUE, (value -- res)) { + Py_DECREF(value); res = Py_True; } + macro(TO_BOOL_ALWAYS_TRUE) = + unused/1 + + _GUARD_TYPE_VERSION + + _REPLACE_WITH_TRUE; + inst(UNARY_INVERT, (value -- res)) { res = PyNumber_Invert(value); DECREF_INPUTS(); @@ -506,8 +505,7 @@ dummy_func( // So the inputs are the same as for all BINARY_OP // specializations, but there is no output. // At the end we just skip over the STORE_FAST. - op(_BINARY_OP_INPLACE_ADD_UNICODE, (left, right --)) { - TIER_ONE_ONLY + tier1 op(_BINARY_OP_INPLACE_ADD_UNICODE, (left, right --)) { assert(next_instr->op.code == STORE_FAST); PyObject **target_local = &GETLOCAL(next_instr->op.arg); DEOPT_IF(*target_local != left); @@ -545,7 +543,6 @@ dummy_func( }; specializing op(_SPECIALIZE_BINARY_SUBSCR, (counter/1, container, sub -- container, sub)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -693,7 +690,6 @@ dummy_func( }; specializing op(_SPECIALIZE_STORE_SUBSCR, (counter/1, container, sub -- container, sub)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -762,8 +758,7 @@ dummy_func( ERROR_IF(res == NULL, error); } - inst(RAISE_VARARGS, (args[oparg] -- )) { - TIER_ONE_ONLY + tier1 inst(RAISE_VARARGS, (args[oparg] -- )) { PyObject *cause = NULL, *exc = NULL; switch (oparg) { case 2: @@ -787,8 +782,7 @@ dummy_func( ERROR_IF(true, error); } - inst(INTERPRETER_EXIT, (retval --)) { - TIER_ONE_ONLY + tier1 inst(INTERPRETER_EXIT, (retval --)) { assert(frame == &entry_frame); assert(_PyFrame_IsIncomplete(frame)); /* Restore previous frame and return. */ @@ -980,7 +974,6 @@ dummy_func( }; specializing op(_SPECIALIZE_SEND, (counter/1, receiver, unused -- receiver, unused)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -1075,8 +1068,7 @@ dummy_func( goto resume_frame; } - inst(YIELD_VALUE, (retval -- unused)) { - TIER_ONE_ONLY + tier1 inst(YIELD_VALUE, (retval -- unused)) { // NOTE: It's important that YIELD_VALUE never raises an exception! // The compiler treats any exception raised here as a failed close() // or throw() call. @@ -1105,8 +1097,7 @@ dummy_func( Py_XSETREF(exc_info->exc_value, exc_value == Py_None ? NULL : exc_value); } - inst(RERAISE, (values[oparg], exc -- values[oparg])) { - TIER_ONE_ONLY + tier1 inst(RERAISE, (values[oparg], exc -- values[oparg])) { assert(oparg >= 0 && oparg <= 2); if (oparg) { PyObject *lasti = values[0]; @@ -1127,8 +1118,7 @@ dummy_func( goto exception_unwind; } - inst(END_ASYNC_FOR, (awaitable, exc -- )) { - TIER_ONE_ONLY + tier1 inst(END_ASYNC_FOR, (awaitable, exc -- )) { assert(exc && PyExceptionInstance_Check(exc)); if (PyErr_GivenExceptionMatches(exc, PyExc_StopAsyncIteration)) { DECREF_INPUTS(); @@ -1141,8 +1131,7 @@ dummy_func( } } - inst(CLEANUP_THROW, (sub_iter, last_sent_val, exc_value -- none, value)) { - TIER_ONE_ONLY + tier1 inst(CLEANUP_THROW, (sub_iter, last_sent_val, exc_value -- none, value)) { assert(throwflag); assert(exc_value && PyExceptionInstance_Check(exc_value)); if (PyErr_GivenExceptionMatches(exc_value, PyExc_StopIteration)) { @@ -1214,7 +1203,6 @@ dummy_func( }; specializing op(_SPECIALIZE_UNPACK_SEQUENCE, (counter/1, seq -- seq)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -1237,13 +1225,13 @@ dummy_func( macro(UNPACK_SEQUENCE) = _SPECIALIZE_UNPACK_SEQUENCE + _UNPACK_SEQUENCE; - inst(UNPACK_SEQUENCE_TWO_TUPLE, (unused/1, seq -- values[oparg])) { + inst(UNPACK_SEQUENCE_TWO_TUPLE, (unused/1, seq -- val1, val0)) { + assert(oparg == 2); DEOPT_IF(!PyTuple_CheckExact(seq)); DEOPT_IF(PyTuple_GET_SIZE(seq) != 2); - assert(oparg == 2); STAT_INC(UNPACK_SEQUENCE, hit); - values[0] = Py_NewRef(PyTuple_GET_ITEM(seq, 1)); - values[1] = Py_NewRef(PyTuple_GET_ITEM(seq, 0)); + val0 = Py_NewRef(PyTuple_GET_ITEM(seq, 0)); + val1 = Py_NewRef(PyTuple_GET_ITEM(seq, 1)); DECREF_INPUTS(); } @@ -1284,7 +1272,6 @@ dummy_func( }; specializing op(_SPECIALIZE_STORE_ATTR, (counter/1, owner -- owner)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); @@ -1322,14 +1309,14 @@ dummy_func( inst(DELETE_GLOBAL, (--)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); - int err; - err = PyDict_DelItem(GLOBALS(), name); + int err = PyDict_Pop(GLOBALS(), name, NULL); // Can't use ERROR_IF here. - if (err != 0) { - if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) { - _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, - NAME_ERROR_MSG, name); - } + if (err < 0) { + GOTO_ERROR(error); + } + if (err == 0) { + _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, + NAME_ERROR_MSG, name); GOTO_ERROR(error); } } @@ -1403,7 +1390,6 @@ dummy_func( }; specializing op(_SPECIALIZE_LOAD_GLOBAL, (counter/1 -- )) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg>>1); @@ -1731,7 +1717,6 @@ dummy_func( }; specializing op(_SPECIALIZE_LOAD_SUPER_ATTR, (counter/1, global_super, class, unused -- global_super, class, unused)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION int load_method = oparg & 1; if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { @@ -1744,8 +1729,7 @@ dummy_func( #endif /* ENABLE_SPECIALIZATION */ } - op(_LOAD_SUPER_ATTR, (global_super, class, self -- attr, null if (oparg & 1))) { - TIER_ONE_ONLY + tier1 op(_LOAD_SUPER_ATTR, (global_super, class, self -- attr, null if (oparg & 1))) { if (opcode == INSTRUMENTED_LOAD_SUPER_ATTR) { PyObject *arg = oparg & 2 ? class : &_PyInstrumentation_MISSING; int err = _Py_call_instrumentation_2args( @@ -1847,7 +1831,6 @@ dummy_func( }; specializing op(_SPECIALIZE_LOAD_ATTR, (counter/1, owner -- owner)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg>>1); @@ -2172,7 +2155,6 @@ dummy_func( }; specializing op(_SPECIALIZE_COMPARE_OP, (counter/1, left, right -- left, right)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -2257,13 +2239,51 @@ dummy_func( b = res ? Py_True : Py_False; } - inst(CONTAINS_OP, (left, right -- b)) { + family(CONTAINS_OP, INLINE_CACHE_ENTRIES_CONTAINS_OP) = { + CONTAINS_OP_SET, + CONTAINS_OP_DICT, + }; + + op(_CONTAINS_OP, (left, right -- b)) { int res = PySequence_Contains(right, left); DECREF_INPUTS(); ERROR_IF(res < 0, error); b = (res ^ oparg) ? Py_True : Py_False; } + specializing op(_SPECIALIZE_CONTAINS_OP, (counter/1, left, right -- left, right)) { + #if ENABLE_SPECIALIZATION + if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { + next_instr = this_instr; + _Py_Specialize_ContainsOp(right, next_instr); + DISPATCH_SAME_OPARG(); + } + STAT_INC(CONTAINS_OP, deferred); + DECREMENT_ADAPTIVE_COUNTER(this_instr[1].cache); + #endif /* ENABLE_SPECIALIZATION */ + } + + macro(CONTAINS_OP) = _SPECIALIZE_CONTAINS_OP + _CONTAINS_OP; + + inst(CONTAINS_OP_SET, (unused/1, left, right -- b)) { + DEOPT_IF(!(PySet_CheckExact(right) || PyFrozenSet_CheckExact(right))); + STAT_INC(CONTAINS_OP, hit); + // Note: both set and frozenset use the same seq_contains method! + int res = _PySet_Contains((PySetObject *)right, left); + DECREF_INPUTS(); + ERROR_IF(res < 0, error); + b = (res ^ oparg) ? Py_True : Py_False; + } + + inst(CONTAINS_OP_DICT, (unused/1, left, right -- b)) { + DEOPT_IF(!PyDict_CheckExact(right)); + STAT_INC(CONTAINS_OP, hit); + int res = PyDict_Contains(right, left); + DECREF_INPUTS(); + ERROR_IF(res < 0, error); + b = (res ^ oparg) ? Py_True : Py_False; + } + inst(CHECK_EG_MATCH, (exc_value, match_type -- rest, match)) { if (_PyEval_CheckExceptStarTypeValid(tstate, match_type) < 0) { DECREF_INPUTS(); @@ -2297,28 +2317,24 @@ dummy_func( b = res ? Py_True : Py_False; } - inst(IMPORT_NAME, (level, fromlist -- res)) { - TIER_ONE_ONLY + tier1 inst(IMPORT_NAME, (level, fromlist -- res)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); res = import_name(tstate, frame, name, fromlist, level); DECREF_INPUTS(); ERROR_IF(res == NULL, error); } - inst(IMPORT_FROM, (from -- from, res)) { - TIER_ONE_ONLY + tier1 inst(IMPORT_FROM, (from -- from, res)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); res = import_from(tstate, from, name); ERROR_IF(res == NULL, error); } - inst(JUMP_FORWARD, (--)) { - TIER_ONE_ONLY + tier1 inst(JUMP_FORWARD, (--)) { JUMPBY(oparg); } - inst(JUMP_BACKWARD, (unused/1 --)) { - TIER_ONE_ONLY + tier1 inst(JUMP_BACKWARD, (unused/1 --)) { CHECK_EVAL_BREAKER(); assert(oparg <= INSTR_OFFSET()); JUMPBY(-oparg); @@ -2333,7 +2349,6 @@ dummy_func( // Use '>=' not '>' so that the optimizer/backoff bits do not effect the result. // Double-check that the opcode isn't instrumented or something: if (offset_counter >= threshold && this_instr->op.code == JUMP_BACKWARD) { - OPT_STAT_INC(attempts); _Py_CODEUNIT *start = this_instr; /* Back up over EXTENDED_ARGs so optimizer sees the whole instruction */ while (oparg > 255) { @@ -2373,8 +2388,7 @@ dummy_func( JUMP_BACKWARD_NO_INTERRUPT, }; - inst(ENTER_EXECUTOR, (--)) { - TIER_ONE_ONLY + tier1 inst(ENTER_EXECUTOR, (--)) { CHECK_EVAL_BREAKER(); PyCodeObject *code = _PyFrame_GetCode(frame); _PyExecutorObject *executor = code->co_executors->executors[oparg & 255]; @@ -2423,8 +2437,7 @@ dummy_func( macro(POP_JUMP_IF_NOT_NONE) = unused/1 + _IS_NONE + _POP_JUMP_IF_FALSE; - inst(JUMP_BACKWARD_NO_INTERRUPT, (--)) { - TIER_ONE_ONLY + tier1 inst(JUMP_BACKWARD_NO_INTERRUPT, (--)) { /* This bytecode is used in the `yield from` or `await` loop. * If there is an interrupt, we want it handled in the innermost * generator or coroutine, so we deliberately do not check it here. @@ -2520,7 +2533,6 @@ dummy_func( }; specializing op(_SPECIALIZE_FOR_ITER, (counter/1, iter -- iter)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -2782,7 +2794,7 @@ dummy_func( GOTO_ERROR(error); } DECREF_INPUTS(); - res = _PyObject_CallNoArgsTstate(tstate, enter); + res = PyObject_CallNoArgs(enter); Py_DECREF(enter); if (res == NULL) { Py_DECREF(exit); @@ -2817,7 +2829,7 @@ dummy_func( GOTO_ERROR(error); } DECREF_INPUTS(); - res = _PyObject_CallNoArgsTstate(tstate, enter); + res = PyObject_CallNoArgs(enter); Py_DECREF(enter); if (res == NULL) { Py_DECREF(exit); @@ -3018,7 +3030,6 @@ dummy_func( }; specializing op(_SPECIALIZE_CALL, (counter/1, callable, self_or_null, args[oparg] -- callable, self_or_null, args[oparg])) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -3097,10 +3108,13 @@ dummy_func( Py_DECREF(args[i]); } ERROR_IF(res == NULL, error); + } + + op(_CHECK_PERIODIC, (--)) { CHECK_EVAL_BREAKER(); } - macro(CALL) = _SPECIALIZE_CALL + unused/2 + _CALL; + macro(CALL) = _SPECIALIZE_CALL + unused/2 + _CALL + _CHECK_PERIODIC; op(_CHECK_CALL_BOUND_METHOD_EXACT_ARGS, (callable, null, unused[oparg] -- callable, null, unused[oparg])) { DEOPT_IF(null != NULL); @@ -3136,16 +3150,14 @@ dummy_func( } replicate(5) pure op(_INIT_CALL_PY_EXACT_ARGS, (callable, self_or_null, args[oparg] -- new_frame: _PyInterpreterFrame*)) { - int argcount = oparg; - if (self_or_null != NULL) { - args--; - argcount++; - } + int has_self = (self_or_null != NULL); STAT_INC(CALL, hit); PyFunctionObject *func = (PyFunctionObject *)callable; - new_frame = _PyFrame_PushUnchecked(tstate, func, argcount); - for (int i = 0; i < argcount; i++) { - new_frame->localsplus[i] = args[i]; + new_frame = _PyFrame_PushUnchecked(tstate, func, oparg + has_self); + PyObject **first_non_self_local = new_frame->localsplus + has_self; + new_frame->localsplus[0] = self_or_null; + for (int i = 0; i < oparg; i++) { + first_non_self_local[i] = args[i]; } } @@ -3226,43 +3238,47 @@ dummy_func( DISPATCH_INLINED(new_frame); } - inst(CALL_TYPE_1, (unused/1, unused/2, callable, null, args[oparg] -- res)) { + inst(CALL_TYPE_1, (unused/1, unused/2, callable, null, arg -- res)) { assert(oparg == 1); DEOPT_IF(null != NULL); - PyObject *obj = args[0]; DEOPT_IF(callable != (PyObject *)&PyType_Type); STAT_INC(CALL, hit); - res = Py_NewRef(Py_TYPE(obj)); - Py_DECREF(obj); - Py_DECREF(&PyType_Type); // I.e., callable + res = Py_NewRef(Py_TYPE(arg)); + Py_DECREF(arg); } - inst(CALL_STR_1, (unused/1, unused/2, callable, null, args[oparg] -- res)) { + op(_CALL_STR_1, (callable, null, arg -- res)) { assert(oparg == 1); DEOPT_IF(null != NULL); DEOPT_IF(callable != (PyObject *)&PyUnicode_Type); STAT_INC(CALL, hit); - PyObject *arg = args[0]; res = PyObject_Str(arg); Py_DECREF(arg); - Py_DECREF(&PyUnicode_Type); // I.e., callable ERROR_IF(res == NULL, error); - CHECK_EVAL_BREAKER(); } - inst(CALL_TUPLE_1, (unused/1, unused/2, callable, null, args[oparg] -- res)) { + macro(CALL_STR_1) = + unused/1 + + unused/2 + + _CALL_STR_1 + + _CHECK_PERIODIC; + + op(_CALL_TUPLE_1, (callable, null, arg -- res)) { assert(oparg == 1); DEOPT_IF(null != NULL); DEOPT_IF(callable != (PyObject *)&PyTuple_Type); STAT_INC(CALL, hit); - PyObject *arg = args[0]; res = PySequence_Tuple(arg); Py_DECREF(arg); - Py_DECREF(&PyTuple_Type); // I.e., tuple ERROR_IF(res == NULL, error); - CHECK_EVAL_BREAKER(); } + macro(CALL_TUPLE_1) = + unused/1 + + unused/2 + + _CALL_TUPLE_1 + + _CHECK_PERIODIC; + inst(CALL_ALLOC_AND_ENTER_INIT, (unused/1, unused/2, callable, null, args[oparg] -- unused)) { /* This instruction does the following: * 1. Creates the object (by calling ``object.__new__``) @@ -3323,7 +3339,7 @@ dummy_func( } } - inst(CALL_BUILTIN_CLASS, (unused/1, unused/2, callable, self_or_null, args[oparg] -- res)) { + op(_CALL_BUILTIN_CLASS, (callable, self_or_null, args[oparg] -- res)) { int total_args = oparg; if (self_or_null != NULL) { args--; @@ -3340,10 +3356,15 @@ dummy_func( } Py_DECREF(tp); ERROR_IF(res == NULL, error); - CHECK_EVAL_BREAKER(); } - inst(CALL_BUILTIN_O, (unused/1, unused/2, callable, self_or_null, args[oparg] -- res)) { + macro(CALL_BUILTIN_CLASS) = + unused/1 + + unused/2 + + _CALL_BUILTIN_CLASS + + _CHECK_PERIODIC; + + op(_CALL_BUILTIN_O, (callable, self_or_null, args[oparg] -- res)) { /* Builtin METH_O functions */ int total_args = oparg; if (self_or_null != NULL) { @@ -3353,14 +3374,12 @@ dummy_func( DEOPT_IF(total_args != 1); DEOPT_IF(!PyCFunction_CheckExact(callable)); DEOPT_IF(PyCFunction_GET_FLAGS(callable) != METH_O); + // CPython promises to check all non-vectorcall function calls. + DEOPT_IF(tstate->c_recursion_remaining <= 0); STAT_INC(CALL, hit); PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable); - // This is slower but CPython promises to check all non-vectorcall - // function calls. - if (_Py_EnterRecursiveCallTstate(tstate, " while calling a Python object")) { - GOTO_ERROR(error); - } PyObject *arg = args[0]; + _Py_EnterRecursiveCallTstateUnchecked(tstate); res = _PyCFunction_TrampolineCall(cfunc, PyCFunction_GET_SELF(callable), arg); _Py_LeaveRecursiveCallTstate(tstate); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); @@ -3368,10 +3387,15 @@ dummy_func( Py_DECREF(arg); Py_DECREF(callable); ERROR_IF(res == NULL, error); - CHECK_EVAL_BREAKER(); } - inst(CALL_BUILTIN_FAST, (unused/1, unused/2, callable, self_or_null, args[oparg] -- res)) { + macro(CALL_BUILTIN_O) = + unused/1 + + unused/2 + + _CALL_BUILTIN_O + + _CHECK_PERIODIC; + + op(_CALL_BUILTIN_FAST, (callable, self_or_null, args[oparg] -- res)) { /* Builtin METH_FASTCALL functions, without keywords */ int total_args = oparg; if (self_or_null != NULL) { @@ -3395,15 +3419,15 @@ dummy_func( } Py_DECREF(callable); ERROR_IF(res == NULL, error); - /* Not deopting because this doesn't mean our optimization was - wrong. `res` can be NULL for valid reasons. Eg. getattr(x, - 'invalid'). In those cases an exception is set, so we must - handle it. - */ - CHECK_EVAL_BREAKER(); } - inst(CALL_BUILTIN_FAST_WITH_KEYWORDS, (unused/1, unused/2, callable, self_or_null, args[oparg] -- res)) { + macro(CALL_BUILTIN_FAST) = + unused/1 + + unused/2 + + _CALL_BUILTIN_FAST + + _CHECK_PERIODIC; + + op(_CALL_BUILTIN_FAST_WITH_KEYWORDS, (callable, self_or_null, args[oparg] -- res)) { /* Builtin METH_FASTCALL | METH_KEYWORDS functions */ int total_args = oparg; if (self_or_null != NULL) { @@ -3426,9 +3450,14 @@ dummy_func( } Py_DECREF(callable); ERROR_IF(res == NULL, error); - CHECK_EVAL_BREAKER(); } + macro(CALL_BUILTIN_FAST_WITH_KEYWORDS) = + unused/1 + + unused/2 + + _CALL_BUILTIN_FAST_WITH_KEYWORDS + + _CHECK_PERIODIC; + inst(CALL_LEN, (unused/1, unused/2, callable, self_or_null, args[oparg] -- res)) { /* len(o) */ int total_args = oparg; @@ -3447,10 +3476,11 @@ dummy_func( } res = PyLong_FromSsize_t(len_i); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - + if (res == NULL) { + GOTO_ERROR(error); + } Py_DECREF(callable); Py_DECREF(arg); - ERROR_IF(res == NULL, error); } inst(CALL_ISINSTANCE, (unused/1, unused/2, callable, self_or_null, args[oparg] -- res)) { @@ -3472,23 +3502,23 @@ dummy_func( } res = PyBool_FromLong(retval); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - + if (res == NULL) { + GOTO_ERROR(error); + } Py_DECREF(inst); Py_DECREF(cls); Py_DECREF(callable); - ERROR_IF(res == NULL, error); } // This is secretly a super-instruction - inst(CALL_LIST_APPEND, (unused/1, unused/2, callable, self, args[oparg] -- unused)) { - TIER_ONE_ONLY + tier1 inst(CALL_LIST_APPEND, (unused/1, unused/2, callable, self, arg -- unused)) { assert(oparg == 1); PyInterpreterState *interp = tstate->interp; DEOPT_IF(callable != interp->callable_cache.list_append); assert(self != NULL); DEOPT_IF(!PyList_Check(self)); STAT_INC(CALL, hit); - if (_PyList_AppendTakeRef((PyListObject *)self, args[0]) < 0) { + if (_PyList_AppendTakeRef((PyListObject *)self, arg) < 0) { goto pop_1_error; // Since arg is DECREF'ed already } Py_DECREF(self); @@ -3500,7 +3530,7 @@ dummy_func( DISPATCH(); } - inst(CALL_METHOD_DESCRIPTOR_O, (unused/1, unused/2, callable, self_or_null, args[oparg] -- res)) { + op(_CALL_METHOD_DESCRIPTOR_O, (callable, self_or_null, args[oparg] -- res)) { int total_args = oparg; if (self_or_null != NULL) { args--; @@ -3511,16 +3541,14 @@ dummy_func( DEOPT_IF(!Py_IS_TYPE(method, &PyMethodDescr_Type)); PyMethodDef *meth = method->d_method; DEOPT_IF(meth->ml_flags != METH_O); + // CPython promises to check all non-vectorcall function calls. + DEOPT_IF(tstate->c_recursion_remaining <= 0); PyObject *arg = args[1]; PyObject *self = args[0]; DEOPT_IF(!Py_IS_TYPE(self, method->d_common.d_type)); STAT_INC(CALL, hit); PyCFunction cfunc = meth->ml_meth; - // This is slower but CPython promises to check all non-vectorcall - // function calls. - if (_Py_EnterRecursiveCallTstate(tstate, " while calling a Python object")) { - GOTO_ERROR(error); - } + _Py_EnterRecursiveCallTstateUnchecked(tstate); res = _PyCFunction_TrampolineCall(cfunc, self, arg); _Py_LeaveRecursiveCallTstate(tstate); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); @@ -3528,10 +3556,15 @@ dummy_func( Py_DECREF(arg); Py_DECREF(callable); ERROR_IF(res == NULL, error); - CHECK_EVAL_BREAKER(); } - inst(CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS, (unused/1, unused/2, callable, self_or_null, args[oparg] -- res)) { + macro(CALL_METHOD_DESCRIPTOR_O) = + unused/1 + + unused/2 + + _CALL_METHOD_DESCRIPTOR_O + + _CHECK_PERIODIC; + + op(_CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS, (callable, self_or_null, args[oparg] -- res)) { int total_args = oparg; if (self_or_null != NULL) { args--; @@ -3557,10 +3590,15 @@ dummy_func( } Py_DECREF(callable); ERROR_IF(res == NULL, error); - CHECK_EVAL_BREAKER(); } - inst(CALL_METHOD_DESCRIPTOR_NOARGS, (unused/1, unused/2, callable, self_or_null, args[oparg] -- res)) { + macro(CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS) = + unused/1 + + unused/2 + + _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS + + _CHECK_PERIODIC; + + op(_CALL_METHOD_DESCRIPTOR_NOARGS, (callable, self_or_null, args[oparg] -- res)) { assert(oparg == 0 || oparg == 1); int total_args = oparg; if (self_or_null != NULL) { @@ -3574,23 +3612,26 @@ dummy_func( PyObject *self = args[0]; DEOPT_IF(!Py_IS_TYPE(self, method->d_common.d_type)); DEOPT_IF(meth->ml_flags != METH_NOARGS); + // CPython promises to check all non-vectorcall function calls. + DEOPT_IF(tstate->c_recursion_remaining <= 0); STAT_INC(CALL, hit); PyCFunction cfunc = meth->ml_meth; - // This is slower but CPython promises to check all non-vectorcall - // function calls. - if (_Py_EnterRecursiveCallTstate(tstate, " while calling a Python object")) { - GOTO_ERROR(error); - } + _Py_EnterRecursiveCallTstateUnchecked(tstate); res = _PyCFunction_TrampolineCall(cfunc, self, NULL); _Py_LeaveRecursiveCallTstate(tstate); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); Py_DECREF(self); Py_DECREF(callable); ERROR_IF(res == NULL, error); - CHECK_EVAL_BREAKER(); } - inst(CALL_METHOD_DESCRIPTOR_FAST, (unused/1, unused/2, callable, self_or_null, args[oparg] -- res)) { + macro(CALL_METHOD_DESCRIPTOR_NOARGS) = + unused/1 + + unused/2 + + _CALL_METHOD_DESCRIPTOR_NOARGS + + _CHECK_PERIODIC; + + op(_CALL_METHOD_DESCRIPTOR_FAST, (callable, self_or_null, args[oparg] -- res)) { int total_args = oparg; if (self_or_null != NULL) { args--; @@ -3615,9 +3656,14 @@ dummy_func( } Py_DECREF(callable); ERROR_IF(res == NULL, error); - CHECK_EVAL_BREAKER(); } + macro(CALL_METHOD_DESCRIPTOR_FAST) = + unused/1 + + unused/2 + + _CALL_METHOD_DESCRIPTOR_FAST + + _CHECK_PERIODIC; + inst(INSTRUMENTED_CALL_KW, ( -- )) { int is_meth = PEEK(oparg + 2) != NULL; int total_args = oparg + is_meth; @@ -3724,27 +3770,28 @@ dummy_func( } assert(PyTuple_CheckExact(callargs)); EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_FUNCTION_EX, func); - if (opcode == INSTRUMENTED_CALL_FUNCTION_EX && - !PyFunction_Check(func) && !PyMethod_Check(func) - ) { + if (opcode == INSTRUMENTED_CALL_FUNCTION_EX) { PyObject *arg = PyTuple_GET_SIZE(callargs) > 0 ? - PyTuple_GET_ITEM(callargs, 0) : Py_None; + PyTuple_GET_ITEM(callargs, 0) : &_PyInstrumentation_MISSING; int err = _Py_call_instrumentation_2args( tstate, PY_MONITORING_EVENT_CALL, frame, this_instr, func, arg); if (err) GOTO_ERROR(error); result = PyObject_Call(func, callargs, kwargs); - if (result == NULL) { - _Py_call_instrumentation_exc2( - tstate, PY_MONITORING_EVENT_C_RAISE, - frame, this_instr, func, arg); - } - else { - int err = _Py_call_instrumentation_2args( - tstate, PY_MONITORING_EVENT_C_RETURN, - frame, this_instr, func, arg); - if (err < 0) { - Py_CLEAR(result); + + if (!PyFunction_Check(func) && !PyMethod_Check(func)) { + if (result == NULL) { + _Py_call_instrumentation_exc2( + tstate, PY_MONITORING_EVENT_C_RAISE, + frame, this_instr, func, arg); + } + else { + int err = _Py_call_instrumentation_2args( + tstate, PY_MONITORING_EVENT_C_RETURN, + frame, this_instr, func, arg); + if (err < 0) { + Py_CLEAR(result); + } } } } @@ -3819,8 +3866,7 @@ dummy_func( } } - inst(RETURN_GENERATOR, (--)) { - TIER_ONE_ONLY + tier1 inst(RETURN_GENERATOR, (--)) { assert(PyFunction_Check(frame->f_funcobj)); PyFunctionObject *func = (PyFunctionObject *)frame->f_funcobj; PyGenObject *gen = (PyGenObject *)_Py_MakeCoro(func); @@ -3852,9 +3898,9 @@ dummy_func( } inst(CONVERT_VALUE, (value -- result)) { - convertion_func_ptr conv_fn; + conversion_func conv_fn; assert(oparg >= FVC_STR && oparg <= FVC_ASCII); - conv_fn = CONVERSION_FUNCTIONS[oparg]; + conv_fn = _PyEval_ConversionFuncs[oparg]; result = conv_fn(value); Py_DECREF(value); ERROR_IF(result == NULL, error); @@ -3886,7 +3932,6 @@ dummy_func( } specializing op(_SPECIALIZE_BINARY_OP, (counter/1, lhs, rhs -- lhs, rhs)) { - TIER_ONE_ONLY #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -3992,8 +4037,7 @@ dummy_func( INSTRUMENTED_JUMP(this_instr, next_instr + offset, PY_MONITORING_EVENT_BRANCH); } - inst(EXTENDED_ARG, ( -- )) { - TIER_ONE_ONLY + tier1 inst(EXTENDED_ARG, ( -- )) { assert(oparg); opcode = next_instr->op.code; oparg = oparg << 8 | next_instr->op.arg; @@ -4001,14 +4045,12 @@ dummy_func( DISPATCH_GOTO(); } - inst(CACHE, (--)) { - TIER_ONE_ONLY + tier1 inst(CACHE, (--)) { assert(0 && "Executing a cache."); Py_FatalError("Executing a cache."); } - inst(RESERVED, (--)) { - TIER_ONE_ONLY + tier1 inst(RESERVED, (--)) { assert(0 && "Executing RESERVED instruction."); Py_FatalError("Executing RESERVED instruction."); } @@ -4048,8 +4090,7 @@ dummy_func( CHECK_EVAL_BREAKER(); } - op(_SET_IP, (instr_ptr/4 --)) { - TIER_TWO_ONLY + tier2 op(_SET_IP, (instr_ptr/4 --)) { frame->instr_ptr = (_Py_CODEUNIT *)instr_ptr; } @@ -4062,46 +4103,40 @@ dummy_func( #endif } - op(_EXIT_TRACE, (--)) { - TIER_TWO_ONLY + tier2 op(_EXIT_TRACE, (--)) { EXIT_IF(1); } - op(_CHECK_VALIDITY, (--)) { - TIER_TWO_ONLY + tier2 op(_CHECK_VALIDITY, (--)) { DEOPT_IF(!current_executor->vm_data.valid); } - pure op(_LOAD_CONST_INLINE, (ptr/4 -- value)) { - TIER_TWO_ONLY + tier2 pure op(_LOAD_CONST_INLINE, (ptr/4 -- value)) { value = Py_NewRef(ptr); } - pure op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) { - TIER_TWO_ONLY + tier2 pure op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) { + value = ptr; + } + + tier2 pure op (_POP_TOP_LOAD_CONST_INLINE_BORROW, (ptr/4, pop -- value)) { + Py_DECREF(pop); value = ptr; } - pure op(_LOAD_CONST_INLINE_WITH_NULL, (ptr/4 -- value, null)) { - TIER_TWO_ONLY + tier2 pure op(_LOAD_CONST_INLINE_WITH_NULL, (ptr/4 -- value, null)) { value = Py_NewRef(ptr); null = NULL; } - pure op(_LOAD_CONST_INLINE_BORROW_WITH_NULL, (ptr/4 -- value, null)) { - TIER_TWO_ONLY + tier2 pure op(_LOAD_CONST_INLINE_BORROW_WITH_NULL, (ptr/4 -- value, null)) { value = ptr; null = NULL; } - op(_CHECK_GLOBALS, (dict/4 -- )) { - TIER_TWO_ONLY - DEOPT_IF(GLOBALS() != dict); - } - - op(_CHECK_BUILTINS, (dict/4 -- )) { - TIER_TWO_ONLY - DEOPT_IF(BUILTINS() != dict); + tier2 op(_CHECK_FUNCTION, (func_version/2 -- )) { + assert(PyFunction_Check(frame->f_funcobj)); + DEOPT_IF(((PyFunctionObject *)frame->f_funcobj)->func_version != func_version); } /* Internal -- for testing executors */ @@ -4113,8 +4148,7 @@ dummy_func( /* Only used for handling cold side exits, should never appear in * a normal trace or as part of an instruction. */ - op(_COLD_EXIT, (--)) { - TIER_TWO_ONLY + tier2 op(_COLD_EXIT, (--)) { _PyExecutorObject *previous = (_PyExecutorObject *)tstate->previous_executor; _PyExitData *exit = &previous->exits[oparg]; exit->temperature++; @@ -4147,8 +4181,7 @@ dummy_func( GOTO_TIER_TWO(executor); } - op(_START_EXECUTOR, (executor/4 --)) { - TIER_TWO_ONLY + tier2 op(_START_EXECUTOR, (executor/4 --)) { Py_DECREF(tstate->previous_executor); tstate->previous_executor = NULL; #ifndef _Py_JIT @@ -4156,14 +4189,12 @@ dummy_func( #endif } - op(_FATAL_ERROR, (--)) { - TIER_TWO_ONLY + tier2 op(_FATAL_ERROR, (--)) { assert(0); Py_FatalError("Fatal error uop executed."); } - op(_CHECK_VALIDITY_AND_SET_IP, (instr_ptr/4 --)) { - TIER_TWO_ONLY + tier2 op(_CHECK_VALIDITY_AND_SET_IP, (instr_ptr/4 --)) { DEOPT_IF(!current_executor->vm_data.valid); frame->instr_ptr = (_Py_CODEUNIT *)instr_ptr; } diff --git a/Python/ceval.c b/Python/ceval.c index 06c136aeb252c9a..f817f2889036940 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -337,6 +337,12 @@ const binaryfunc _PyEval_BinaryOps[] = { [NB_INPLACE_XOR] = PyNumber_InPlaceXor, }; +const conversion_func _PyEval_ConversionFuncs[4] = { + [FVC_STR] = PyObject_Str, + [FVC_REPR] = PyObject_Repr, + [FVC_ASCII] = PyObject_ASCII +}; + // PEP 634: Structural Pattern Matching diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c index f5c44307a513f83..d2cd35dfa86833e 100644 --- a/Python/ceval_gil.c +++ b/Python/ceval_gil.c @@ -219,6 +219,11 @@ drop_gil(PyInterpreterState *interp, PyThreadState *tstate) // XXX assert(tstate == NULL || !tstate->_status.cleared); struct _gil_runtime_state *gil = ceval->gil; +#ifdef Py_GIL_DISABLED + if (!gil->enabled) { + return; + } +#endif if (!_Py_atomic_load_ptr_relaxed(&gil->locked)) { Py_FatalError("drop_gil: GIL is not locked"); } @@ -294,6 +299,11 @@ take_gil(PyThreadState *tstate) assert(_PyThreadState_CheckConsistency(tstate)); PyInterpreterState *interp = tstate->interp; struct _gil_runtime_state *gil = interp->ceval.gil; +#ifdef Py_GIL_DISABLED + if (!gil->enabled) { + return; + } +#endif /* Check that _PyEval_InitThreads() was called to create the lock */ assert(gil_created(gil)); @@ -417,6 +427,7 @@ PyEval_ThreadsInitialized(void) return _PyEval_ThreadsInitialized(); } +#ifndef NDEBUG static inline int current_thread_holds_gil(struct _gil_runtime_state *gil, PyThreadState *tstate) { @@ -425,6 +436,7 @@ current_thread_holds_gil(struct _gil_runtime_state *gil, PyThreadState *tstate) } return _Py_atomic_load_int_relaxed(&gil->locked); } +#endif static void init_shared_gil(PyInterpreterState *interp, struct _gil_runtime_state *gil) @@ -438,6 +450,11 @@ static void init_own_gil(PyInterpreterState *interp, struct _gil_runtime_state *gil) { assert(!gil_created(gil)); +#ifdef Py_GIL_DISABLED + // gh-116329: Once it is safe to do so, change this condition to + // (enable_gil == _PyConfig_GIL_ENABLE), so the GIL is disabled by default. + gil->enabled = _PyInterpreterState_GetConfig(interp)->enable_gil != _PyConfig_GIL_DISABLE; +#endif create_gil(gil); assert(gil_created(gil)); interp->ceval.gil = gil; diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index 01a9b32229d8a52..22992aa09e1f389 100644 --- a/Python/ceval_macros.h +++ b/Python/ceval_macros.h @@ -296,11 +296,19 @@ GETITEM(PyObject *v, Py_ssize_t i) { #define ADAPTIVE_COUNTER_IS_MAX(COUNTER) \ (((COUNTER) >> ADAPTIVE_BACKOFF_BITS) == ((1 << MAX_BACKOFF_VALUE) - 1)) +#ifdef Py_GIL_DISABLED +#define DECREMENT_ADAPTIVE_COUNTER(COUNTER) \ + do { \ + /* gh-115999 tracks progress on addressing this. */ \ + static_assert(0, "The specializing interpreter is not yet thread-safe"); \ + } while (0); +#else #define DECREMENT_ADAPTIVE_COUNTER(COUNTER) \ do { \ assert(!ADAPTIVE_COUNTER_IS_ZERO((COUNTER))); \ (COUNTER) -= (1 << ADAPTIVE_BACKOFF_BITS); \ } while (0); +#endif #define INCREMENT_ADAPTIVE_COUNTER(COUNTER) \ do { \ @@ -348,13 +356,6 @@ do { \ } \ } while (0); -typedef PyObject *(*convertion_func_ptr)(PyObject *); - -static const convertion_func_ptr CONVERSION_FUNCTIONS[4] = { - [FVC_STR] = PyObject_Str, - [FVC_REPR] = PyObject_Repr, - [FVC_ASCII] = PyObject_ASCII -}; // GH-89279: Force inlining by using a macro. #if defined(_MSC_VER) && SIZEOF_INT == 4 @@ -372,12 +373,6 @@ static inline void _Py_LeaveRecursiveCallPy(PyThreadState *tstate) { tstate->py_recursion_remaining++; } -/* Marker to specify tier 1 only instructions */ -#define TIER_ONE_ONLY - -/* Marker to specify tier 2 only instructions */ -#define TIER_TWO_ONLY - /* Implementation of "macros" that modify the instruction pointer, * stack pointer, or frame pointer. * These need to treated differently by tier 1 and 2. diff --git a/Python/compile.c b/Python/compile.c index d857239690e7b54..6b17f3bcaf22644 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -921,11 +921,10 @@ dict_add_o(PyObject *dict, PyObject *o) PyObject *v; Py_ssize_t arg; - v = PyDict_GetItemWithError(dict, o); + if (PyDict_GetItemRef(dict, o, &v) < 0) { + return ERROR; + } if (!v) { - if (PyErr_Occurred()) { - return ERROR; - } arg = PyDict_GET_SIZE(dict); v = PyLong_FromSsize_t(arg); if (!v) { @@ -935,10 +934,10 @@ dict_add_o(PyObject *dict, PyObject *o) Py_DECREF(v); return ERROR; } - Py_DECREF(v); } else arg = PyLong_AsLong(v); + Py_DECREF(v); return arg; } diff --git a/Python/crossinterp.c b/Python/crossinterp.c index 143b261f9a53960..18dec4dd9590443 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -7,7 +7,6 @@ #include "pycore_initconfig.h" // _PyStatus_OK() #include "pycore_namespace.h" //_PyNamespace_New() #include "pycore_pyerrors.h" // _PyErr_Clear() -#include "pycore_typeobject.h" // _PyType_GetModuleName() #include "pycore_weakref.h" // _PyWeakref_GET_REF() @@ -510,7 +509,7 @@ _excinfo_init_type(struct _excinfo_type *info, PyObject *exc) } // __module__ - strobj = _PyType_GetModuleName(type); + strobj = PyType_GetModuleName(type); if (strobj == NULL) { return -1; } diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 3054058cf44d31b..a55daa2c3449449 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -383,15 +383,10 @@ break; } - case _TO_BOOL_ALWAYS_TRUE: { + case _REPLACE_WITH_TRUE: { PyObject *value; PyObject *res; value = stack_pointer[-1]; - uint32_t version = (uint32_t)CURRENT_OPERAND(); - // This one is a bit weird, because we expect *some* failures: - assert(version); - if (Py_TYPE(value)->tp_version_tag != version) goto side_exit; - STAT_INC(TO_BOOL, hit); Py_DECREF(value); res = Py_True; stack_pointer[-1] = res; @@ -1066,18 +1061,20 @@ case _UNPACK_SEQUENCE_TWO_TUPLE: { PyObject *seq; - PyObject **values; + PyObject *val1; + PyObject *val0; oparg = CURRENT_OPARG(); seq = stack_pointer[-1]; - values = &stack_pointer[-1]; + assert(oparg == 2); if (!PyTuple_CheckExact(seq)) goto deoptimize; if (PyTuple_GET_SIZE(seq) != 2) goto deoptimize; - assert(oparg == 2); STAT_INC(UNPACK_SEQUENCE, hit); - values[0] = Py_NewRef(PyTuple_GET_ITEM(seq, 1)); - values[1] = Py_NewRef(PyTuple_GET_ITEM(seq, 0)); + val0 = Py_NewRef(PyTuple_GET_ITEM(seq, 0)); + val1 = Py_NewRef(PyTuple_GET_ITEM(seq, 1)); Py_DECREF(seq); - stack_pointer += -1 + oparg; + stack_pointer[-1] = val1; + stack_pointer[0] = val0; + stack_pointer += 1; break; } @@ -1172,14 +1169,14 @@ case _DELETE_GLOBAL: { oparg = CURRENT_OPARG(); PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); - int err; - err = PyDict_DelItem(GLOBALS(), name); + int err = PyDict_Pop(GLOBALS(), name, NULL); // Can't use ERROR_IF here. - if (err != 0) { - if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) { - _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, - NAME_ERROR_MSG, name); - } + if (err < 0) { + GOTO_ERROR(error); + } + if (err == 0) { + _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, + NAME_ERROR_MSG, name); GOTO_ERROR(error); } break; @@ -2194,6 +2191,45 @@ break; } + case _CONTAINS_OP_SET: { + PyObject *right; + PyObject *left; + PyObject *b; + oparg = CURRENT_OPARG(); + right = stack_pointer[-1]; + left = stack_pointer[-2]; + if (!(PySet_CheckExact(right) || PyFrozenSet_CheckExact(right))) goto deoptimize; + STAT_INC(CONTAINS_OP, hit); + // Note: both set and frozenset use the same seq_contains method! + int res = _PySet_Contains((PySetObject *)right, left); + Py_DECREF(left); + Py_DECREF(right); + if (res < 0) goto pop_2_error_tier_two; + b = (res ^ oparg) ? Py_True : Py_False; + stack_pointer[-2] = b; + stack_pointer += -1; + break; + } + + case _CONTAINS_OP_DICT: { + PyObject *right; + PyObject *left; + PyObject *b; + oparg = CURRENT_OPARG(); + right = stack_pointer[-1]; + left = stack_pointer[-2]; + if (!PyDict_CheckExact(right)) goto deoptimize; + STAT_INC(CONTAINS_OP, hit); + int res = PyDict_Contains(right, left); + Py_DECREF(left); + Py_DECREF(right); + if (res < 0) goto pop_2_error_tier_two; + b = (res ^ oparg) ? Py_True : Py_False; + stack_pointer[-2] = b; + stack_pointer += -1; + break; + } + case _CHECK_EG_MATCH: { PyObject *match_type; PyObject *exc_value; @@ -2548,7 +2584,7 @@ GOTO_ERROR(error); } Py_DECREF(mgr); - res = _PyObject_CallNoArgsTstate(tstate, enter); + res = PyObject_CallNoArgs(enter); Py_DECREF(enter); if (res == NULL) { Py_DECREF(exit); @@ -2591,7 +2627,7 @@ GOTO_ERROR(error); } Py_DECREF(mgr); - res = _PyObject_CallNoArgsTstate(tstate, enter); + res = PyObject_CallNoArgs(enter); Py_DECREF(enter); if (res == NULL) { Py_DECREF(exit); @@ -2783,6 +2819,11 @@ /* _CALL is not a viable micro-op for tier 2 */ + case _CHECK_PERIODIC: { + CHECK_EVAL_BREAKER(); + break; + } + case _CHECK_CALL_BOUND_METHOD_EXACT_ARGS: { PyObject *null; PyObject *callable; @@ -2852,16 +2893,14 @@ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - int argcount = oparg; - if (self_or_null != NULL) { - args--; - argcount++; - } + int has_self = (self_or_null != NULL); STAT_INC(CALL, hit); PyFunctionObject *func = (PyFunctionObject *)callable; - new_frame = _PyFrame_PushUnchecked(tstate, func, argcount); - for (int i = 0; i < argcount; i++) { - new_frame->localsplus[i] = args[i]; + new_frame = _PyFrame_PushUnchecked(tstate, func, oparg + has_self); + PyObject **first_non_self_local = new_frame->localsplus + has_self; + new_frame->localsplus[0] = self_or_null; + for (int i = 0; i < oparg; i++) { + first_non_self_local[i] = args[i]; } stack_pointer[-2 - oparg] = (PyObject *)new_frame; stack_pointer += -1 - oparg; @@ -2878,16 +2917,14 @@ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - int argcount = oparg; - if (self_or_null != NULL) { - args--; - argcount++; - } + int has_self = (self_or_null != NULL); STAT_INC(CALL, hit); PyFunctionObject *func = (PyFunctionObject *)callable; - new_frame = _PyFrame_PushUnchecked(tstate, func, argcount); - for (int i = 0; i < argcount; i++) { - new_frame->localsplus[i] = args[i]; + new_frame = _PyFrame_PushUnchecked(tstate, func, oparg + has_self); + PyObject **first_non_self_local = new_frame->localsplus + has_self; + new_frame->localsplus[0] = self_or_null; + for (int i = 0; i < oparg; i++) { + first_non_self_local[i] = args[i]; } stack_pointer[-2 - oparg] = (PyObject *)new_frame; stack_pointer += -1 - oparg; @@ -2904,16 +2941,14 @@ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - int argcount = oparg; - if (self_or_null != NULL) { - args--; - argcount++; - } + int has_self = (self_or_null != NULL); STAT_INC(CALL, hit); PyFunctionObject *func = (PyFunctionObject *)callable; - new_frame = _PyFrame_PushUnchecked(tstate, func, argcount); - for (int i = 0; i < argcount; i++) { - new_frame->localsplus[i] = args[i]; + new_frame = _PyFrame_PushUnchecked(tstate, func, oparg + has_self); + PyObject **first_non_self_local = new_frame->localsplus + has_self; + new_frame->localsplus[0] = self_or_null; + for (int i = 0; i < oparg; i++) { + first_non_self_local[i] = args[i]; } stack_pointer[-2 - oparg] = (PyObject *)new_frame; stack_pointer += -1 - oparg; @@ -2930,16 +2965,14 @@ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - int argcount = oparg; - if (self_or_null != NULL) { - args--; - argcount++; - } + int has_self = (self_or_null != NULL); STAT_INC(CALL, hit); PyFunctionObject *func = (PyFunctionObject *)callable; - new_frame = _PyFrame_PushUnchecked(tstate, func, argcount); - for (int i = 0; i < argcount; i++) { - new_frame->localsplus[i] = args[i]; + new_frame = _PyFrame_PushUnchecked(tstate, func, oparg + has_self); + PyObject **first_non_self_local = new_frame->localsplus + has_self; + new_frame->localsplus[0] = self_or_null; + for (int i = 0; i < oparg; i++) { + first_non_self_local[i] = args[i]; } stack_pointer[-2 - oparg] = (PyObject *)new_frame; stack_pointer += -1 - oparg; @@ -2956,16 +2989,14 @@ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - int argcount = oparg; - if (self_or_null != NULL) { - args--; - argcount++; - } + int has_self = (self_or_null != NULL); STAT_INC(CALL, hit); PyFunctionObject *func = (PyFunctionObject *)callable; - new_frame = _PyFrame_PushUnchecked(tstate, func, argcount); - for (int i = 0; i < argcount; i++) { - new_frame->localsplus[i] = args[i]; + new_frame = _PyFrame_PushUnchecked(tstate, func, oparg + has_self); + PyObject **first_non_self_local = new_frame->localsplus + has_self; + new_frame->localsplus[0] = self_or_null; + for (int i = 0; i < oparg; i++) { + first_non_self_local[i] = args[i]; } stack_pointer[-2 - oparg] = (PyObject *)new_frame; stack_pointer += -1 - oparg; @@ -2981,16 +3012,14 @@ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - int argcount = oparg; - if (self_or_null != NULL) { - args--; - argcount++; - } + int has_self = (self_or_null != NULL); STAT_INC(CALL, hit); PyFunctionObject *func = (PyFunctionObject *)callable; - new_frame = _PyFrame_PushUnchecked(tstate, func, argcount); - for (int i = 0; i < argcount; i++) { - new_frame->localsplus[i] = args[i]; + new_frame = _PyFrame_PushUnchecked(tstate, func, oparg + has_self); + PyObject **first_non_self_local = new_frame->localsplus + has_self; + new_frame->localsplus[0] = self_or_null; + for (int i = 0; i < oparg; i++) { + first_non_self_local[i] = args[i]; } stack_pointer[-2 - oparg] = (PyObject *)new_frame; stack_pointer += -1 - oparg; @@ -3023,72 +3052,64 @@ /* _CALL_PY_WITH_DEFAULTS is not a viable micro-op for tier 2 */ case _CALL_TYPE_1: { - PyObject **args; + PyObject *arg; PyObject *null; PyObject *callable; PyObject *res; oparg = CURRENT_OPARG(); - args = &stack_pointer[-oparg]; - null = stack_pointer[-1 - oparg]; - callable = stack_pointer[-2 - oparg]; + arg = stack_pointer[-1]; + null = stack_pointer[-2]; + callable = stack_pointer[-3]; assert(oparg == 1); if (null != NULL) goto deoptimize; - PyObject *obj = args[0]; if (callable != (PyObject *)&PyType_Type) goto deoptimize; STAT_INC(CALL, hit); - res = Py_NewRef(Py_TYPE(obj)); - Py_DECREF(obj); - Py_DECREF(&PyType_Type); // I.e., callable - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; + res = Py_NewRef(Py_TYPE(arg)); + Py_DECREF(arg); + stack_pointer[-3] = res; + stack_pointer += -2; break; } case _CALL_STR_1: { - PyObject **args; + PyObject *arg; PyObject *null; PyObject *callable; PyObject *res; oparg = CURRENT_OPARG(); - args = &stack_pointer[-oparg]; - null = stack_pointer[-1 - oparg]; - callable = stack_pointer[-2 - oparg]; + arg = stack_pointer[-1]; + null = stack_pointer[-2]; + callable = stack_pointer[-3]; assert(oparg == 1); if (null != NULL) goto deoptimize; if (callable != (PyObject *)&PyUnicode_Type) goto deoptimize; STAT_INC(CALL, hit); - PyObject *arg = args[0]; res = PyObject_Str(arg); Py_DECREF(arg); - Py_DECREF(&PyUnicode_Type); // I.e., callable - if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; - CHECK_EVAL_BREAKER(); + if (res == NULL) goto pop_3_error_tier_two; + stack_pointer[-3] = res; + stack_pointer += -2; break; } case _CALL_TUPLE_1: { - PyObject **args; + PyObject *arg; PyObject *null; PyObject *callable; PyObject *res; oparg = CURRENT_OPARG(); - args = &stack_pointer[-oparg]; - null = stack_pointer[-1 - oparg]; - callable = stack_pointer[-2 - oparg]; + arg = stack_pointer[-1]; + null = stack_pointer[-2]; + callable = stack_pointer[-3]; assert(oparg == 1); if (null != NULL) goto deoptimize; if (callable != (PyObject *)&PyTuple_Type) goto deoptimize; STAT_INC(CALL, hit); - PyObject *arg = args[0]; res = PySequence_Tuple(arg); Py_DECREF(arg); - Py_DECREF(&PyTuple_Type); // I.e., tuple - if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; - CHECK_EVAL_BREAKER(); + if (res == NULL) goto pop_3_error_tier_two; + stack_pointer[-3] = res; + stack_pointer += -2; break; } @@ -3135,7 +3156,6 @@ if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; - CHECK_EVAL_BREAKER(); break; } @@ -3157,14 +3177,12 @@ if (total_args != 1) goto deoptimize; if (!PyCFunction_CheckExact(callable)) goto deoptimize; if (PyCFunction_GET_FLAGS(callable) != METH_O) goto deoptimize; + // CPython promises to check all non-vectorcall function calls. + if (tstate->c_recursion_remaining <= 0) goto deoptimize; STAT_INC(CALL, hit); PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable); - // This is slower but CPython promises to check all non-vectorcall - // function calls. - if (_Py_EnterRecursiveCallTstate(tstate, " while calling a Python object")) { - GOTO_ERROR(error); - } PyObject *arg = args[0]; + _Py_EnterRecursiveCallTstateUnchecked(tstate); res = _PyCFunction_TrampolineCall(cfunc, PyCFunction_GET_SELF(callable), arg); _Py_LeaveRecursiveCallTstate(tstate); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); @@ -3173,7 +3191,6 @@ if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; - CHECK_EVAL_BREAKER(); break; } @@ -3208,14 +3225,8 @@ } Py_DECREF(callable); if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } - /* Not deopting because this doesn't mean our optimization was - wrong. `res` can be NULL for valid reasons. Eg. getattr(x, - 'invalid'). In those cases an exception is set, so we must - handle it. - */ stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; - CHECK_EVAL_BREAKER(); break; } @@ -3251,7 +3262,6 @@ if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; - CHECK_EVAL_BREAKER(); break; } @@ -3281,9 +3291,11 @@ } res = PyLong_FromSsize_t(len_i); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); + if (res == NULL) { + GOTO_ERROR(error); + } Py_DECREF(callable); Py_DECREF(arg); - if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; break; @@ -3316,10 +3328,12 @@ } res = PyBool_FromLong(retval); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); + if (res == NULL) { + GOTO_ERROR(error); + } Py_DECREF(inst); Py_DECREF(cls); Py_DECREF(callable); - if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; break; @@ -3344,16 +3358,14 @@ if (!Py_IS_TYPE(method, &PyMethodDescr_Type)) goto deoptimize; PyMethodDef *meth = method->d_method; if (meth->ml_flags != METH_O) goto deoptimize; + // CPython promises to check all non-vectorcall function calls. + if (tstate->c_recursion_remaining <= 0) goto deoptimize; PyObject *arg = args[1]; PyObject *self = args[0]; if (!Py_IS_TYPE(self, method->d_common.d_type)) goto deoptimize; STAT_INC(CALL, hit); PyCFunction cfunc = meth->ml_meth; - // This is slower but CPython promises to check all non-vectorcall - // function calls. - if (_Py_EnterRecursiveCallTstate(tstate, " while calling a Python object")) { - GOTO_ERROR(error); - } + _Py_EnterRecursiveCallTstateUnchecked(tstate); res = _PyCFunction_TrampolineCall(cfunc, self, arg); _Py_LeaveRecursiveCallTstate(tstate); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); @@ -3363,7 +3375,6 @@ if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; - CHECK_EVAL_BREAKER(); break; } @@ -3402,7 +3413,6 @@ if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; - CHECK_EVAL_BREAKER(); break; } @@ -3428,13 +3438,11 @@ PyObject *self = args[0]; if (!Py_IS_TYPE(self, method->d_common.d_type)) goto deoptimize; if (meth->ml_flags != METH_NOARGS) goto deoptimize; + // CPython promises to check all non-vectorcall function calls. + if (tstate->c_recursion_remaining <= 0) goto deoptimize; STAT_INC(CALL, hit); PyCFunction cfunc = meth->ml_meth; - // This is slower but CPython promises to check all non-vectorcall - // function calls. - if (_Py_EnterRecursiveCallTstate(tstate, " while calling a Python object")) { - GOTO_ERROR(error); - } + _Py_EnterRecursiveCallTstateUnchecked(tstate); res = _PyCFunction_TrampolineCall(cfunc, self, NULL); _Py_LeaveRecursiveCallTstate(tstate); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); @@ -3443,7 +3451,6 @@ if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; - CHECK_EVAL_BREAKER(); break; } @@ -3482,7 +3489,6 @@ if (res == NULL) { stack_pointer += -2 - oparg; goto error_tier_two; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; - CHECK_EVAL_BREAKER(); break; } @@ -3570,9 +3576,9 @@ PyObject *result; oparg = CURRENT_OPARG(); value = stack_pointer[-1]; - convertion_func_ptr conv_fn; + conversion_func conv_fn; assert(oparg >= FVC_STR && oparg <= FVC_ASCII); - conv_fn = CONVERSION_FUNCTIONS[oparg]; + conv_fn = _PyEval_ConversionFuncs[oparg]; result = conv_fn(value); Py_DECREF(value); if (result == NULL) goto pop_1_error_tier_two; @@ -3716,7 +3722,6 @@ case _SET_IP: { PyObject *instr_ptr = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY frame->instr_ptr = (_Py_CODEUNIT *)instr_ptr; break; } @@ -3733,13 +3738,11 @@ } case _EXIT_TRACE: { - TIER_TWO_ONLY if (1) goto side_exit; break; } case _CHECK_VALIDITY: { - TIER_TWO_ONLY if (!current_executor->vm_data.valid) goto deoptimize; break; } @@ -3747,7 +3750,6 @@ case _LOAD_CONST_INLINE: { PyObject *value; PyObject *ptr = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY value = Py_NewRef(ptr); stack_pointer[0] = value; stack_pointer += 1; @@ -3757,18 +3759,27 @@ case _LOAD_CONST_INLINE_BORROW: { PyObject *value; PyObject *ptr = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY value = ptr; stack_pointer[0] = value; stack_pointer += 1; break; } + case _POP_TOP_LOAD_CONST_INLINE_BORROW: { + PyObject *pop; + PyObject *value; + pop = stack_pointer[-1]; + PyObject *ptr = (PyObject *)CURRENT_OPERAND(); + Py_DECREF(pop); + value = ptr; + stack_pointer[-1] = value; + break; + } + case _LOAD_CONST_INLINE_WITH_NULL: { PyObject *value; PyObject *null; PyObject *ptr = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY value = Py_NewRef(ptr); null = NULL; stack_pointer[0] = value; @@ -3781,7 +3792,6 @@ PyObject *value; PyObject *null; PyObject *ptr = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY value = ptr; null = NULL; stack_pointer[0] = value; @@ -3790,17 +3800,10 @@ break; } - case _CHECK_GLOBALS: { - PyObject *dict = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY - if (GLOBALS() != dict) goto deoptimize; - break; - } - - case _CHECK_BUILTINS: { - PyObject *dict = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY - if (BUILTINS() != dict) goto deoptimize; + case _CHECK_FUNCTION: { + uint32_t func_version = (uint32_t)CURRENT_OPERAND(); + assert(PyFunction_Check(frame->f_funcobj)); + if (((PyFunctionObject *)frame->f_funcobj)->func_version != func_version) goto deoptimize; break; } @@ -3815,7 +3818,6 @@ case _COLD_EXIT: { oparg = CURRENT_OPARG(); - TIER_TWO_ONLY _PyExecutorObject *previous = (_PyExecutorObject *)tstate->previous_executor; _PyExitData *exit = &previous->exits[oparg]; exit->temperature++; @@ -3851,7 +3853,6 @@ case _START_EXECUTOR: { PyObject *executor = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY Py_DECREF(tstate->previous_executor); tstate->previous_executor = NULL; #ifndef _Py_JIT @@ -3861,7 +3862,6 @@ } case _FATAL_ERROR: { - TIER_TWO_ONLY assert(0); Py_FatalError("Fatal error uop executed."); break; @@ -3869,7 +3869,6 @@ case _CHECK_VALIDITY_AND_SET_IP: { PyObject *instr_ptr = (PyObject *)CURRENT_OPERAND(); - TIER_TWO_ONLY if (!current_executor->vm_data.valid) goto deoptimize; frame->instr_ptr = (_Py_CODEUNIT *)instr_ptr; break; diff --git a/Python/frame.c b/Python/frame.c index ddf6ef6ba5465cf..f88a8f0d73d3f87 100644 --- a/Python/frame.c +++ b/Python/frame.c @@ -37,24 +37,15 @@ _PyFrame_MakeAndSetFrameObject(_PyInterpreterFrame *frame) return NULL; } PyErr_SetRaisedException(exc); - if (frame->frame_obj) { - // GH-97002: How did we get into this horrible situation? Most likely, - // allocating f triggered a GC collection, which ran some code that - // *also* created the same frame... while we were in the middle of - // creating it! See test_sneaky_frame_object in test_frame.py for a - // concrete example. - // - // Regardless, just throw f away and use that frame instead, since it's - // already been exposed to user code. It's actually a bit tricky to do - // this, since we aren't backed by a real _PyInterpreterFrame anymore. - // Just pretend that we have an owned, cleared frame so frame_dealloc - // doesn't make the situation worse: - f->f_frame = (_PyInterpreterFrame *)f->_f_frame_data; - f->f_frame->owner = FRAME_CLEARED; - f->f_frame->frame_obj = f; - Py_DECREF(f); - return frame->frame_obj; - } + + // GH-97002: There was a time when a frame object could be created when we + // are allocating the new frame object f above, so frame->frame_obj would + // be assigned already. That path does not exist anymore. We won't call any + // Python code in this function and garbage collection will not run. + // Notice that _PyFrame_New_NoTrack() can potentially raise a MemoryError, + // but it won't allocate a traceback until the frame unwinds, so we are safe + // here. + assert(frame->frame_obj == NULL); assert(frame->owner != FRAME_OWNED_BY_FRAME_OBJECT); assert(frame->owner != FRAME_CLEARED); f->f_frame = frame; diff --git a/Python/gc.c b/Python/gc.c index ea3b596d1713dfc..6b3316b642ea9e6 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -1805,6 +1805,10 @@ _PyObject_GC_Link(PyObject *op) void _Py_RunGC(PyThreadState *tstate) { + GCState *gcstate = get_gc_state(); + if (!gcstate->enabled) { + return; + } gc_collect_main(tstate, GENERATION_AUTO, _Py_GC_REASON_HEAP); } diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 14790899825de1a..2b13d1f005dd972 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -324,6 +324,23 @@ merge_all_queued_objects(PyInterpreterState *interp, struct collection_state *st HEAD_UNLOCK(&_PyRuntime); } +static void +process_delayed_frees(PyInterpreterState *interp) +{ + // In STW status, we can observe the latest write sequence by + // advancing the write sequence immediately. + _Py_qsbr_advance(&interp->qsbr); + _PyThreadStateImpl *current_tstate = (_PyThreadStateImpl *)_PyThreadState_GET(); + _Py_qsbr_quiescent_state(current_tstate->qsbr); + HEAD_LOCK(&_PyRuntime); + PyThreadState *tstate = interp->threads.head; + while (tstate != NULL) { + _PyMem_ProcessDelayed(tstate); + tstate = (PyThreadState *)tstate->next; + } + HEAD_UNLOCK(&_PyRuntime); +} + // Subtract an incoming reference from the computed "gc_refs" refcount. static int visit_decref(PyObject *op, void *arg) @@ -1006,6 +1023,7 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state) _PyEval_StopTheWorld(interp); // merge refcounts for all queued objects merge_all_queued_objects(interp, state); + process_delayed_frees(interp); // Find unreachable objects int err = deduce_unreachable_heap(interp, state); @@ -1040,6 +1058,8 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state) // Handle any objects that may have resurrected after the finalization. _PyEval_StopTheWorld(interp); err = handle_resurrected_objects(state); + // Clear free lists in all threads + _PyGC_ClearAllFreeLists(interp); _PyEval_StartTheWorld(interp); if (err < 0) { @@ -1142,8 +1162,9 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) n+m, n, d); } - // Clear free lists in all threads - _PyGC_ClearAllFreeLists(interp); + // Clear the current thread's free-list again. + _PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate; + _PyObject_ClearFreeLists(&tstate_impl->freelists, 0); if (_PyErr_Occurred(tstate)) { if (reason == _Py_GC_REASON_SHUTDOWN) { @@ -1582,6 +1603,10 @@ _PyObject_GC_Link(PyObject *op) void _Py_RunGC(PyThreadState *tstate) { + GCState *gcstate = get_gc_state(); + if (!gcstate->enabled) { + return; + } gc_collect_main(tstate, 0, _Py_GC_REASON_HEAP); } @@ -1674,6 +1699,7 @@ PyObject_GC_Del(void *op) { size_t presize = _PyType_PreHeaderSize(((PyObject *)op)->ob_type); if (_PyObject_GC_IS_TRACKED(op)) { + _PyObject_GC_UNTRACK(op); #ifdef Py_DEBUG PyObject *exc = PyErr_GetRaisedException(); if (PyErr_WarnExplicitFormat(PyExc_ResourceWarning, "gc", 0, @@ -1686,26 +1712,25 @@ PyObject_GC_Del(void *op) } record_deallocation(_PyThreadState_GET()); - - PyObject_Free(((char *)op)-presize); + PyObject *self = (PyObject *)op; + if (_PyObject_GC_IS_SHARED_INLINE(self)) { + _PyObject_FreeDelayed(((char *)op)-presize); + } + else { + PyObject_Free(((char *)op)-presize); + } } int PyObject_GC_IsTracked(PyObject* obj) { - if (_PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj)) { - return 1; - } - return 0; + return _PyObject_GC_IS_TRACKED(obj); } int PyObject_GC_IsFinalized(PyObject *obj) { - if (_PyObject_IS_GC(obj) && _PyGC_FINALIZED(obj)) { - return 1; - } - return 0; + return _PyGC_FINALIZED(obj); } struct custom_visitor_args { diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 87579134146c857..2996ee72e7f2c6b 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -40,7 +40,7 @@ GOTO_ERROR(error); } Py_DECREF(mgr); - res = _PyObject_CallNoArgsTstate(tstate, enter); + res = PyObject_CallNoArgs(enter); Py_DECREF(enter); if (res == NULL) { Py_DECREF(exit); @@ -86,7 +86,7 @@ GOTO_ERROR(error); } Py_DECREF(mgr); - res = _PyObject_CallNoArgsTstate(tstate, enter); + res = PyObject_CallNoArgs(enter); Py_DECREF(enter); if (res == NULL) { Py_DECREF(exit); @@ -104,6 +104,7 @@ INSTRUCTION_STATS(BINARY_OP); PREDICTED(BINARY_OP); _Py_CODEUNIT *this_instr = next_instr - 2; + (void)this_instr; PyObject *rhs; PyObject *lhs; PyObject *res; @@ -112,7 +113,7 @@ lhs = stack_pointer[-2]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY + (void)counter; #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -242,7 +243,6 @@ /* Skip 1 cache entry */ // _BINARY_OP_INPLACE_ADD_UNICODE { - TIER_ONE_ONLY assert(next_instr->op.code == STORE_FAST); PyObject **target_local = &GETLOCAL(next_instr->op.arg); DEOPT_IF(*target_local != left, BINARY_OP); @@ -421,6 +421,7 @@ INSTRUCTION_STATS(BINARY_SUBSCR); PREDICTED(BINARY_SUBSCR); _Py_CODEUNIT *this_instr = next_instr - 2; + (void)this_instr; PyObject *sub; PyObject *container; PyObject *res; @@ -429,7 +430,7 @@ container = stack_pointer[-2]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY + (void)counter; #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -739,7 +740,6 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(CACHE); - TIER_ONE_ONLY assert(0 && "Executing a cache."); Py_FatalError("Executing a cache."); DISPATCH(); @@ -751,6 +751,7 @@ INSTRUCTION_STATS(CALL); PREDICTED(CALL); _Py_CODEUNIT *this_instr = next_instr - 4; + (void)this_instr; PyObject **args; PyObject *self_or_null; PyObject *callable; @@ -761,7 +762,7 @@ callable = stack_pointer[-2 - oparg]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY + (void)counter; #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -841,6 +842,9 @@ } if (res == NULL) { stack_pointer += -2 - oparg; goto error; } } + // _CHECK_PERIODIC + { + } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); @@ -964,16 +968,14 @@ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; { - int argcount = oparg; - if (self_or_null != NULL) { - args--; - argcount++; - } + int has_self = (self_or_null != NULL); STAT_INC(CALL, hit); PyFunctionObject *func = (PyFunctionObject *)callable; - new_frame = _PyFrame_PushUnchecked(tstate, func, argcount); - for (int i = 0; i < argcount; i++) { - new_frame->localsplus[i] = args[i]; + new_frame = _PyFrame_PushUnchecked(tstate, func, oparg + has_self); + PyObject **first_non_self_local = new_frame->localsplus + has_self; + new_frame->localsplus[0] = self_or_null; + for (int i = 0; i < oparg; i++) { + first_non_self_local[i] = args[i]; } } // _SAVE_RETURN_OFFSET @@ -1019,25 +1021,31 @@ PyObject *res; /* Skip 1 cache entry */ /* Skip 2 cache entries */ + // _CALL_BUILTIN_CLASS args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - int total_args = oparg; - if (self_or_null != NULL) { - args--; - total_args++; + { + int total_args = oparg; + if (self_or_null != NULL) { + args--; + total_args++; + } + DEOPT_IF(!PyType_Check(callable), CALL); + PyTypeObject *tp = (PyTypeObject *)callable; + DEOPT_IF(tp->tp_vectorcall == NULL, CALL); + STAT_INC(CALL, hit); + res = tp->tp_vectorcall((PyObject *)tp, args, total_args, NULL); + /* Free the arguments. */ + for (int i = 0; i < total_args; i++) { + Py_DECREF(args[i]); + } + Py_DECREF(tp); + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } } - DEOPT_IF(!PyType_Check(callable), CALL); - PyTypeObject *tp = (PyTypeObject *)callable; - DEOPT_IF(tp->tp_vectorcall == NULL, CALL); - STAT_INC(CALL, hit); - res = tp->tp_vectorcall((PyObject *)tp, args, total_args, NULL); - /* Free the arguments. */ - for (int i = 0; i < total_args; i++) { - Py_DECREF(args[i]); + // _CHECK_PERIODIC + { } - Py_DECREF(tp); - if (res == NULL) { stack_pointer += -2 - oparg; goto error; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); @@ -1055,36 +1063,37 @@ PyObject *res; /* Skip 1 cache entry */ /* Skip 2 cache entries */ + // _CALL_BUILTIN_FAST args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - /* Builtin METH_FASTCALL functions, without keywords */ - int total_args = oparg; - if (self_or_null != NULL) { - args--; - total_args++; + { + /* Builtin METH_FASTCALL functions, without keywords */ + int total_args = oparg; + if (self_or_null != NULL) { + args--; + total_args++; + } + DEOPT_IF(!PyCFunction_CheckExact(callable), CALL); + DEOPT_IF(PyCFunction_GET_FLAGS(callable) != METH_FASTCALL, CALL); + STAT_INC(CALL, hit); + PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable); + /* res = func(self, args, nargs) */ + res = ((PyCFunctionFast)(void(*)(void))cfunc)( + PyCFunction_GET_SELF(callable), + args, + total_args); + assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); + /* Free the arguments. */ + for (int i = 0; i < total_args; i++) { + Py_DECREF(args[i]); + } + Py_DECREF(callable); + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } } - DEOPT_IF(!PyCFunction_CheckExact(callable), CALL); - DEOPT_IF(PyCFunction_GET_FLAGS(callable) != METH_FASTCALL, CALL); - STAT_INC(CALL, hit); - PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable); - /* res = func(self, args, nargs) */ - res = ((PyCFunctionFast)(void(*)(void))cfunc)( - PyCFunction_GET_SELF(callable), - args, - total_args); - assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - /* Free the arguments. */ - for (int i = 0; i < total_args; i++) { - Py_DECREF(args[i]); + // _CHECK_PERIODIC + { } - Py_DECREF(callable); - if (res == NULL) { stack_pointer += -2 - oparg; goto error; } - /* Not deopting because this doesn't mean our optimization was - wrong. `res` can be NULL for valid reasons. Eg. getattr(x, - 'invalid'). In those cases an exception is set, so we must - handle it. - */ stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); @@ -1102,30 +1111,36 @@ PyObject *res; /* Skip 1 cache entry */ /* Skip 2 cache entries */ + // _CALL_BUILTIN_FAST_WITH_KEYWORDS args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - /* Builtin METH_FASTCALL | METH_KEYWORDS functions */ - int total_args = oparg; - if (self_or_null != NULL) { - args--; - total_args++; + { + /* Builtin METH_FASTCALL | METH_KEYWORDS functions */ + int total_args = oparg; + if (self_or_null != NULL) { + args--; + total_args++; + } + DEOPT_IF(!PyCFunction_CheckExact(callable), CALL); + DEOPT_IF(PyCFunction_GET_FLAGS(callable) != (METH_FASTCALL | METH_KEYWORDS), CALL); + STAT_INC(CALL, hit); + /* res = func(self, args, nargs, kwnames) */ + PyCFunctionFastWithKeywords cfunc = + (PyCFunctionFastWithKeywords)(void(*)(void)) + PyCFunction_GET_FUNCTION(callable); + res = cfunc(PyCFunction_GET_SELF(callable), args, total_args, NULL); + assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); + /* Free the arguments. */ + for (int i = 0; i < total_args; i++) { + Py_DECREF(args[i]); + } + Py_DECREF(callable); + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } } - DEOPT_IF(!PyCFunction_CheckExact(callable), CALL); - DEOPT_IF(PyCFunction_GET_FLAGS(callable) != (METH_FASTCALL | METH_KEYWORDS), CALL); - STAT_INC(CALL, hit); - /* res = func(self, args, nargs, kwnames) */ - PyCFunctionFastWithKeywords cfunc = - (PyCFunctionFastWithKeywords)(void(*)(void)) - PyCFunction_GET_FUNCTION(callable); - res = cfunc(PyCFunction_GET_SELF(callable), args, total_args, NULL); - assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - /* Free the arguments. */ - for (int i = 0; i < total_args; i++) { - Py_DECREF(args[i]); + // _CHECK_PERIODIC + { } - Py_DECREF(callable); - if (res == NULL) { stack_pointer += -2 - oparg; goto error; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); @@ -1143,32 +1158,36 @@ PyObject *res; /* Skip 1 cache entry */ /* Skip 2 cache entries */ + // _CALL_BUILTIN_O args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - /* Builtin METH_O functions */ - int total_args = oparg; - if (self_or_null != NULL) { - args--; - total_args++; + { + /* Builtin METH_O functions */ + int total_args = oparg; + if (self_or_null != NULL) { + args--; + total_args++; + } + DEOPT_IF(total_args != 1, CALL); + DEOPT_IF(!PyCFunction_CheckExact(callable), CALL); + DEOPT_IF(PyCFunction_GET_FLAGS(callable) != METH_O, CALL); + // CPython promises to check all non-vectorcall function calls. + DEOPT_IF(tstate->c_recursion_remaining <= 0, CALL); + STAT_INC(CALL, hit); + PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable); + PyObject *arg = args[0]; + _Py_EnterRecursiveCallTstateUnchecked(tstate); + res = _PyCFunction_TrampolineCall(cfunc, PyCFunction_GET_SELF(callable), arg); + _Py_LeaveRecursiveCallTstate(tstate); + assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); + Py_DECREF(arg); + Py_DECREF(callable); + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } } - DEOPT_IF(total_args != 1, CALL); - DEOPT_IF(!PyCFunction_CheckExact(callable), CALL); - DEOPT_IF(PyCFunction_GET_FLAGS(callable) != METH_O, CALL); - STAT_INC(CALL, hit); - PyCFunction cfunc = PyCFunction_GET_FUNCTION(callable); - // This is slower but CPython promises to check all non-vectorcall - // function calls. - if (_Py_EnterRecursiveCallTstate(tstate, " while calling a Python object")) { - GOTO_ERROR(error); + // _CHECK_PERIODIC + { } - PyObject *arg = args[0]; - res = _PyCFunction_TrampolineCall(cfunc, PyCFunction_GET_SELF(callable), arg); - _Py_LeaveRecursiveCallTstate(tstate); - assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - Py_DECREF(arg); - Py_DECREF(callable); - if (res == NULL) { stack_pointer += -2 - oparg; goto error; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); @@ -1181,6 +1200,7 @@ INSTRUCTION_STATS(CALL_FUNCTION_EX); PREDICTED(CALL_FUNCTION_EX); _Py_CODEUNIT *this_instr = next_instr - 1; + (void)this_instr; PyObject *kwargs = NULL; PyObject *callargs; PyObject *func; @@ -1203,27 +1223,27 @@ } assert(PyTuple_CheckExact(callargs)); EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_FUNCTION_EX, func); - if (opcode == INSTRUMENTED_CALL_FUNCTION_EX && - !PyFunction_Check(func) && !PyMethod_Check(func) - ) { + if (opcode == INSTRUMENTED_CALL_FUNCTION_EX) { PyObject *arg = PyTuple_GET_SIZE(callargs) > 0 ? - PyTuple_GET_ITEM(callargs, 0) : Py_None; + PyTuple_GET_ITEM(callargs, 0) : &_PyInstrumentation_MISSING; int err = _Py_call_instrumentation_2args( tstate, PY_MONITORING_EVENT_CALL, frame, this_instr, func, arg); if (err) GOTO_ERROR(error); result = PyObject_Call(func, callargs, kwargs); - if (result == NULL) { - _Py_call_instrumentation_exc2( - tstate, PY_MONITORING_EVENT_C_RAISE, - frame, this_instr, func, arg); - } - else { - int err = _Py_call_instrumentation_2args( - tstate, PY_MONITORING_EVENT_C_RETURN, - frame, this_instr, func, arg); - if (err < 0) { - Py_CLEAR(result); + if (!PyFunction_Check(func) && !PyMethod_Check(func)) { + if (result == NULL) { + _Py_call_instrumentation_exc2( + tstate, PY_MONITORING_EVENT_C_RAISE, + frame, this_instr, func, arg); + } + else { + int err = _Py_call_instrumentation_2args( + tstate, PY_MONITORING_EVENT_C_RETURN, + frame, this_instr, func, arg); + if (err < 0) { + Py_CLEAR(result); + } } } } @@ -1326,10 +1346,12 @@ } res = PyBool_FromLong(retval); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); + if (res == NULL) { + GOTO_ERROR(error); + } Py_DECREF(inst); Py_DECREF(cls); Py_DECREF(callable); - if (res == NULL) { stack_pointer += -2 - oparg; goto error; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; DISPATCH(); @@ -1341,6 +1363,7 @@ INSTRUCTION_STATS(CALL_KW); PREDICTED(CALL_KW); _Py_CODEUNIT *this_instr = next_instr - 1; + (void)this_instr; PyObject *kwnames; PyObject **args; PyObject *self_or_null; @@ -1456,9 +1479,11 @@ } res = PyLong_FromSsize_t(len_i); assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); + if (res == NULL) { + GOTO_ERROR(error); + } Py_DECREF(callable); Py_DECREF(arg); - if (res == NULL) { stack_pointer += -2 - oparg; goto error; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; DISPATCH(); @@ -1469,22 +1494,21 @@ next_instr += 4; INSTRUCTION_STATS(CALL_LIST_APPEND); static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); - PyObject **args; + PyObject *arg; PyObject *self; PyObject *callable; /* Skip 1 cache entry */ /* Skip 2 cache entries */ - args = &stack_pointer[-oparg]; - self = stack_pointer[-1 - oparg]; - callable = stack_pointer[-2 - oparg]; - TIER_ONE_ONLY + arg = stack_pointer[-1]; + self = stack_pointer[-2]; + callable = stack_pointer[-3]; assert(oparg == 1); PyInterpreterState *interp = tstate->interp; DEOPT_IF(callable != interp->callable_cache.list_append, CALL); assert(self != NULL); DEOPT_IF(!PyList_Check(self), CALL); STAT_INC(CALL, hit); - if (_PyList_AppendTakeRef((PyListObject *)self, args[0]) < 0) { + if (_PyList_AppendTakeRef((PyListObject *)self, arg) < 0) { goto pop_1_error; // Since arg is DECREF'ed already } Py_DECREF(self); @@ -1507,33 +1531,39 @@ PyObject *res; /* Skip 1 cache entry */ /* Skip 2 cache entries */ + // _CALL_METHOD_DESCRIPTOR_FAST args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - int total_args = oparg; - if (self_or_null != NULL) { - args--; - total_args++; + { + int total_args = oparg; + if (self_or_null != NULL) { + args--; + total_args++; + } + PyMethodDescrObject *method = (PyMethodDescrObject *)callable; + /* Builtin METH_FASTCALL methods, without keywords */ + DEOPT_IF(!Py_IS_TYPE(method, &PyMethodDescr_Type), CALL); + PyMethodDef *meth = method->d_method; + DEOPT_IF(meth->ml_flags != METH_FASTCALL, CALL); + PyObject *self = args[0]; + DEOPT_IF(!Py_IS_TYPE(self, method->d_common.d_type), CALL); + STAT_INC(CALL, hit); + PyCFunctionFast cfunc = + (PyCFunctionFast)(void(*)(void))meth->ml_meth; + int nargs = total_args - 1; + res = cfunc(self, args + 1, nargs); + assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); + /* Clear the stack of the arguments. */ + for (int i = 0; i < total_args; i++) { + Py_DECREF(args[i]); + } + Py_DECREF(callable); + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } } - PyMethodDescrObject *method = (PyMethodDescrObject *)callable; - /* Builtin METH_FASTCALL methods, without keywords */ - DEOPT_IF(!Py_IS_TYPE(method, &PyMethodDescr_Type), CALL); - PyMethodDef *meth = method->d_method; - DEOPT_IF(meth->ml_flags != METH_FASTCALL, CALL); - PyObject *self = args[0]; - DEOPT_IF(!Py_IS_TYPE(self, method->d_common.d_type), CALL); - STAT_INC(CALL, hit); - PyCFunctionFast cfunc = - (PyCFunctionFast)(void(*)(void))meth->ml_meth; - int nargs = total_args - 1; - res = cfunc(self, args + 1, nargs); - assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - /* Clear the stack of the arguments. */ - for (int i = 0; i < total_args; i++) { - Py_DECREF(args[i]); + // _CHECK_PERIODIC + { } - Py_DECREF(callable); - if (res == NULL) { stack_pointer += -2 - oparg; goto error; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); @@ -1551,33 +1581,39 @@ PyObject *res; /* Skip 1 cache entry */ /* Skip 2 cache entries */ + // _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - int total_args = oparg; - if (self_or_null != NULL) { - args--; - total_args++; + { + int total_args = oparg; + if (self_or_null != NULL) { + args--; + total_args++; + } + PyMethodDescrObject *method = (PyMethodDescrObject *)callable; + DEOPT_IF(!Py_IS_TYPE(method, &PyMethodDescr_Type), CALL); + PyMethodDef *meth = method->d_method; + DEOPT_IF(meth->ml_flags != (METH_FASTCALL|METH_KEYWORDS), CALL); + PyTypeObject *d_type = method->d_common.d_type; + PyObject *self = args[0]; + DEOPT_IF(!Py_IS_TYPE(self, d_type), CALL); + STAT_INC(CALL, hit); + int nargs = total_args - 1; + PyCFunctionFastWithKeywords cfunc = + (PyCFunctionFastWithKeywords)(void(*)(void))meth->ml_meth; + res = cfunc(self, args + 1, nargs, NULL); + assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); + /* Free the arguments. */ + for (int i = 0; i < total_args; i++) { + Py_DECREF(args[i]); + } + Py_DECREF(callable); + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } } - PyMethodDescrObject *method = (PyMethodDescrObject *)callable; - DEOPT_IF(!Py_IS_TYPE(method, &PyMethodDescr_Type), CALL); - PyMethodDef *meth = method->d_method; - DEOPT_IF(meth->ml_flags != (METH_FASTCALL|METH_KEYWORDS), CALL); - PyTypeObject *d_type = method->d_common.d_type; - PyObject *self = args[0]; - DEOPT_IF(!Py_IS_TYPE(self, d_type), CALL); - STAT_INC(CALL, hit); - int nargs = total_args - 1; - PyCFunctionFastWithKeywords cfunc = - (PyCFunctionFastWithKeywords)(void(*)(void))meth->ml_meth; - res = cfunc(self, args + 1, nargs, NULL); - assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - /* Free the arguments. */ - for (int i = 0; i < total_args; i++) { - Py_DECREF(args[i]); + // _CHECK_PERIODIC + { } - Py_DECREF(callable); - if (res == NULL) { stack_pointer += -2 - oparg; goto error; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); @@ -1595,35 +1631,39 @@ PyObject *res; /* Skip 1 cache entry */ /* Skip 2 cache entries */ + // _CALL_METHOD_DESCRIPTOR_NOARGS args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - assert(oparg == 0 || oparg == 1); - int total_args = oparg; - if (self_or_null != NULL) { - args--; - total_args++; + { + assert(oparg == 0 || oparg == 1); + int total_args = oparg; + if (self_or_null != NULL) { + args--; + total_args++; + } + DEOPT_IF(total_args != 1, CALL); + PyMethodDescrObject *method = (PyMethodDescrObject *)callable; + DEOPT_IF(!Py_IS_TYPE(method, &PyMethodDescr_Type), CALL); + PyMethodDef *meth = method->d_method; + PyObject *self = args[0]; + DEOPT_IF(!Py_IS_TYPE(self, method->d_common.d_type), CALL); + DEOPT_IF(meth->ml_flags != METH_NOARGS, CALL); + // CPython promises to check all non-vectorcall function calls. + DEOPT_IF(tstate->c_recursion_remaining <= 0, CALL); + STAT_INC(CALL, hit); + PyCFunction cfunc = meth->ml_meth; + _Py_EnterRecursiveCallTstateUnchecked(tstate); + res = _PyCFunction_TrampolineCall(cfunc, self, NULL); + _Py_LeaveRecursiveCallTstate(tstate); + assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); + Py_DECREF(self); + Py_DECREF(callable); + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } } - DEOPT_IF(total_args != 1, CALL); - PyMethodDescrObject *method = (PyMethodDescrObject *)callable; - DEOPT_IF(!Py_IS_TYPE(method, &PyMethodDescr_Type), CALL); - PyMethodDef *meth = method->d_method; - PyObject *self = args[0]; - DEOPT_IF(!Py_IS_TYPE(self, method->d_common.d_type), CALL); - DEOPT_IF(meth->ml_flags != METH_NOARGS, CALL); - STAT_INC(CALL, hit); - PyCFunction cfunc = meth->ml_meth; - // This is slower but CPython promises to check all non-vectorcall - // function calls. - if (_Py_EnterRecursiveCallTstate(tstate, " while calling a Python object")) { - GOTO_ERROR(error); + // _CHECK_PERIODIC + { } - res = _PyCFunction_TrampolineCall(cfunc, self, NULL); - _Py_LeaveRecursiveCallTstate(tstate); - assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - Py_DECREF(self); - Py_DECREF(callable); - if (res == NULL) { stack_pointer += -2 - oparg; goto error; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); @@ -1641,36 +1681,40 @@ PyObject *res; /* Skip 1 cache entry */ /* Skip 2 cache entries */ + // _CALL_METHOD_DESCRIPTOR_O args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - int total_args = oparg; - if (self_or_null != NULL) { - args--; - total_args++; + { + int total_args = oparg; + if (self_or_null != NULL) { + args--; + total_args++; + } + PyMethodDescrObject *method = (PyMethodDescrObject *)callable; + DEOPT_IF(total_args != 2, CALL); + DEOPT_IF(!Py_IS_TYPE(method, &PyMethodDescr_Type), CALL); + PyMethodDef *meth = method->d_method; + DEOPT_IF(meth->ml_flags != METH_O, CALL); + // CPython promises to check all non-vectorcall function calls. + DEOPT_IF(tstate->c_recursion_remaining <= 0, CALL); + PyObject *arg = args[1]; + PyObject *self = args[0]; + DEOPT_IF(!Py_IS_TYPE(self, method->d_common.d_type), CALL); + STAT_INC(CALL, hit); + PyCFunction cfunc = meth->ml_meth; + _Py_EnterRecursiveCallTstateUnchecked(tstate); + res = _PyCFunction_TrampolineCall(cfunc, self, arg); + _Py_LeaveRecursiveCallTstate(tstate); + assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); + Py_DECREF(self); + Py_DECREF(arg); + Py_DECREF(callable); + if (res == NULL) { stack_pointer += -2 - oparg; goto error; } } - PyMethodDescrObject *method = (PyMethodDescrObject *)callable; - DEOPT_IF(total_args != 2, CALL); - DEOPT_IF(!Py_IS_TYPE(method, &PyMethodDescr_Type), CALL); - PyMethodDef *meth = method->d_method; - DEOPT_IF(meth->ml_flags != METH_O, CALL); - PyObject *arg = args[1]; - PyObject *self = args[0]; - DEOPT_IF(!Py_IS_TYPE(self, method->d_common.d_type), CALL); - STAT_INC(CALL, hit); - PyCFunction cfunc = meth->ml_meth; - // This is slower but CPython promises to check all non-vectorcall - // function calls. - if (_Py_EnterRecursiveCallTstate(tstate, " while calling a Python object")) { - GOTO_ERROR(error); + // _CHECK_PERIODIC + { } - res = _PyCFunction_TrampolineCall(cfunc, self, arg); - _Py_LeaveRecursiveCallTstate(tstate); - assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL)); - Py_DECREF(self); - Py_DECREF(arg); - Py_DECREF(callable); - if (res == NULL) { stack_pointer += -2 - oparg; goto error; } stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; CHECK_EVAL_BREAKER(); @@ -1713,16 +1757,14 @@ args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; { - int argcount = oparg; - if (self_or_null != NULL) { - args--; - argcount++; - } + int has_self = (self_or_null != NULL); STAT_INC(CALL, hit); PyFunctionObject *func = (PyFunctionObject *)callable; - new_frame = _PyFrame_PushUnchecked(tstate, func, argcount); - for (int i = 0; i < argcount; i++) { - new_frame->localsplus[i] = args[i]; + new_frame = _PyFrame_PushUnchecked(tstate, func, oparg + has_self); + PyObject **first_non_self_local = new_frame->localsplus + has_self; + new_frame->localsplus[0] = self_or_null; + for (int i = 0; i < oparg; i++) { + first_non_self_local[i] = args[i]; } } // _SAVE_RETURN_OFFSET @@ -1808,26 +1850,30 @@ next_instr += 4; INSTRUCTION_STATS(CALL_STR_1); static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); - PyObject **args; + PyObject *arg; PyObject *null; PyObject *callable; PyObject *res; /* Skip 1 cache entry */ /* Skip 2 cache entries */ - args = &stack_pointer[-oparg]; - null = stack_pointer[-1 - oparg]; - callable = stack_pointer[-2 - oparg]; - assert(oparg == 1); - DEOPT_IF(null != NULL, CALL); - DEOPT_IF(callable != (PyObject *)&PyUnicode_Type, CALL); - STAT_INC(CALL, hit); - PyObject *arg = args[0]; - res = PyObject_Str(arg); - Py_DECREF(arg); - Py_DECREF(&PyUnicode_Type); // I.e., callable - if (res == NULL) { stack_pointer += -2 - oparg; goto error; } - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; + // _CALL_STR_1 + arg = stack_pointer[-1]; + null = stack_pointer[-2]; + callable = stack_pointer[-3]; + { + assert(oparg == 1); + DEOPT_IF(null != NULL, CALL); + DEOPT_IF(callable != (PyObject *)&PyUnicode_Type, CALL); + STAT_INC(CALL, hit); + res = PyObject_Str(arg); + Py_DECREF(arg); + if (res == NULL) goto pop_3_error; + } + // _CHECK_PERIODIC + { + } + stack_pointer[-3] = res; + stack_pointer += -2; CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1837,26 +1883,30 @@ next_instr += 4; INSTRUCTION_STATS(CALL_TUPLE_1); static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); - PyObject **args; + PyObject *arg; PyObject *null; PyObject *callable; PyObject *res; /* Skip 1 cache entry */ /* Skip 2 cache entries */ - args = &stack_pointer[-oparg]; - null = stack_pointer[-1 - oparg]; - callable = stack_pointer[-2 - oparg]; - assert(oparg == 1); - DEOPT_IF(null != NULL, CALL); - DEOPT_IF(callable != (PyObject *)&PyTuple_Type, CALL); - STAT_INC(CALL, hit); - PyObject *arg = args[0]; - res = PySequence_Tuple(arg); - Py_DECREF(arg); - Py_DECREF(&PyTuple_Type); // I.e., tuple - if (res == NULL) { stack_pointer += -2 - oparg; goto error; } - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; + // _CALL_TUPLE_1 + arg = stack_pointer[-1]; + null = stack_pointer[-2]; + callable = stack_pointer[-3]; + { + assert(oparg == 1); + DEOPT_IF(null != NULL, CALL); + DEOPT_IF(callable != (PyObject *)&PyTuple_Type, CALL); + STAT_INC(CALL, hit); + res = PySequence_Tuple(arg); + Py_DECREF(arg); + if (res == NULL) goto pop_3_error; + } + // _CHECK_PERIODIC + { + } + stack_pointer[-3] = res; + stack_pointer += -2; CHECK_EVAL_BREAKER(); DISPATCH(); } @@ -1866,25 +1916,23 @@ next_instr += 4; INSTRUCTION_STATS(CALL_TYPE_1); static_assert(INLINE_CACHE_ENTRIES_CALL == 3, "incorrect cache size"); - PyObject **args; + PyObject *arg; PyObject *null; PyObject *callable; PyObject *res; /* Skip 1 cache entry */ /* Skip 2 cache entries */ - args = &stack_pointer[-oparg]; - null = stack_pointer[-1 - oparg]; - callable = stack_pointer[-2 - oparg]; + arg = stack_pointer[-1]; + null = stack_pointer[-2]; + callable = stack_pointer[-3]; assert(oparg == 1); DEOPT_IF(null != NULL, CALL); - PyObject *obj = args[0]; DEOPT_IF(callable != (PyObject *)&PyType_Type, CALL); STAT_INC(CALL, hit); - res = Py_NewRef(Py_TYPE(obj)); - Py_DECREF(obj); - Py_DECREF(&PyType_Type); // I.e., callable - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; + res = Py_NewRef(Py_TYPE(arg)); + Py_DECREF(arg); + stack_pointer[-3] = res; + stack_pointer += -2; DISPATCH(); } @@ -1943,6 +1991,7 @@ TARGET(CLEANUP_THROW) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 1; INSTRUCTION_STATS(CLEANUP_THROW); PyObject *exc_value; @@ -1953,7 +2002,6 @@ exc_value = stack_pointer[-1]; last_sent_val = stack_pointer[-2]; sub_iter = stack_pointer[-3]; - TIER_ONE_ONLY assert(throwflag); assert(exc_value && PyExceptionInstance_Check(exc_value)); if (PyErr_GivenExceptionMatches(exc_value, PyExc_StopIteration)) { @@ -1980,6 +2028,7 @@ INSTRUCTION_STATS(COMPARE_OP); PREDICTED(COMPARE_OP); _Py_CODEUNIT *this_instr = next_instr - 2; + (void)this_instr; PyObject *right; PyObject *left; PyObject *res; @@ -1988,7 +2037,7 @@ left = stack_pointer[-2]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY + (void)counter; #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -2124,14 +2173,81 @@ TARGET(CONTAINS_OP) { frame->instr_ptr = next_instr; - next_instr += 1; + next_instr += 2; INSTRUCTION_STATS(CONTAINS_OP); + PREDICTED(CONTAINS_OP); + _Py_CODEUNIT *this_instr = next_instr - 2; + (void)this_instr; + PyObject *right; + PyObject *left; + PyObject *b; + // _SPECIALIZE_CONTAINS_OP + right = stack_pointer[-1]; + left = stack_pointer[-2]; + { + uint16_t counter = read_u16(&this_instr[1].cache); + (void)counter; + #if ENABLE_SPECIALIZATION + if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { + next_instr = this_instr; + _Py_Specialize_ContainsOp(right, next_instr); + DISPATCH_SAME_OPARG(); + } + STAT_INC(CONTAINS_OP, deferred); + DECREMENT_ADAPTIVE_COUNTER(this_instr[1].cache); + #endif /* ENABLE_SPECIALIZATION */ + } + // _CONTAINS_OP + { + int res = PySequence_Contains(right, left); + Py_DECREF(left); + Py_DECREF(right); + if (res < 0) goto pop_2_error; + b = (res ^ oparg) ? Py_True : Py_False; + } + stack_pointer[-2] = b; + stack_pointer += -1; + DISPATCH(); + } + + TARGET(CONTAINS_OP_DICT) { + frame->instr_ptr = next_instr; + next_instr += 2; + INSTRUCTION_STATS(CONTAINS_OP_DICT); + static_assert(INLINE_CACHE_ENTRIES_CONTAINS_OP == 1, "incorrect cache size"); + PyObject *right; + PyObject *left; + PyObject *b; + /* Skip 1 cache entry */ + right = stack_pointer[-1]; + left = stack_pointer[-2]; + DEOPT_IF(!PyDict_CheckExact(right), CONTAINS_OP); + STAT_INC(CONTAINS_OP, hit); + int res = PyDict_Contains(right, left); + Py_DECREF(left); + Py_DECREF(right); + if (res < 0) goto pop_2_error; + b = (res ^ oparg) ? Py_True : Py_False; + stack_pointer[-2] = b; + stack_pointer += -1; + DISPATCH(); + } + + TARGET(CONTAINS_OP_SET) { + frame->instr_ptr = next_instr; + next_instr += 2; + INSTRUCTION_STATS(CONTAINS_OP_SET); + static_assert(INLINE_CACHE_ENTRIES_CONTAINS_OP == 1, "incorrect cache size"); PyObject *right; PyObject *left; PyObject *b; + /* Skip 1 cache entry */ right = stack_pointer[-1]; left = stack_pointer[-2]; - int res = PySequence_Contains(right, left); + DEOPT_IF(!(PySet_CheckExact(right) || PyFrozenSet_CheckExact(right)), CONTAINS_OP); + STAT_INC(CONTAINS_OP, hit); + // Note: both set and frozenset use the same seq_contains method! + int res = _PySet_Contains((PySetObject *)right, left); Py_DECREF(left); Py_DECREF(right); if (res < 0) goto pop_2_error; @@ -2148,9 +2264,9 @@ PyObject *value; PyObject *result; value = stack_pointer[-1]; - convertion_func_ptr conv_fn; + conversion_func conv_fn; assert(oparg >= FVC_STR && oparg <= FVC_ASCII); - conv_fn = CONVERSION_FUNCTIONS[oparg]; + conv_fn = _PyEval_ConversionFuncs[oparg]; result = conv_fn(value); Py_DECREF(value); if (result == NULL) goto pop_1_error; @@ -2235,14 +2351,14 @@ next_instr += 1; INSTRUCTION_STATS(DELETE_GLOBAL); PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); - int err; - err = PyDict_DelItem(GLOBALS(), name); + int err = PyDict_Pop(GLOBALS(), name, NULL); // Can't use ERROR_IF here. - if (err != 0) { - if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) { - _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, - NAME_ERROR_MSG, name); - } + if (err < 0) { + GOTO_ERROR(error); + } + if (err == 0) { + _PyEval_FormatExcCheckArg(tstate, PyExc_NameError, + NAME_ERROR_MSG, name); GOTO_ERROR(error); } DISPATCH(); @@ -2332,13 +2448,13 @@ TARGET(END_ASYNC_FOR) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 1; INSTRUCTION_STATS(END_ASYNC_FOR); PyObject *exc; PyObject *awaitable; exc = stack_pointer[-1]; awaitable = stack_pointer[-2]; - TIER_ONE_ONLY assert(exc && PyExceptionInstance_Check(exc)); if (PyErr_GivenExceptionMatches(exc, PyExc_StopAsyncIteration)) { Py_DECREF(awaitable); @@ -2383,7 +2499,6 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(ENTER_EXECUTOR); - TIER_ONE_ONLY CHECK_EVAL_BREAKER(); PyCodeObject *code = _PyFrame_GetCode(frame); _PyExecutorObject *executor = code->co_executors->executors[oparg & 255]; @@ -2418,7 +2533,6 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(EXTENDED_ARG); - TIER_ONE_ONLY assert(oparg); opcode = next_instr->op.code; oparg = oparg << 8 | next_instr->op.arg; @@ -2471,13 +2585,14 @@ INSTRUCTION_STATS(FOR_ITER); PREDICTED(FOR_ITER); _Py_CODEUNIT *this_instr = next_instr - 2; + (void)this_instr; PyObject *iter; PyObject *next; // _SPECIALIZE_FOR_ITER iter = stack_pointer[-1]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY + (void)counter; #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -2867,7 +2982,6 @@ PyObject *from; PyObject *res; from = stack_pointer[-1]; - TIER_ONE_ONLY PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); res = import_from(tstate, from, name); if (res == NULL) goto error; @@ -2885,7 +2999,6 @@ PyObject *res; fromlist = stack_pointer[-1]; level = stack_pointer[-2]; - TIER_ONE_ONLY PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); res = import_name(tstate, frame, name, fromlist, level); Py_DECREF(level); @@ -2898,6 +3011,7 @@ TARGET(INSTRUMENTED_CALL) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 4; INSTRUCTION_STATS(INSTRUMENTED_CALL); /* Skip 3 cache entries */ @@ -2923,6 +3037,7 @@ TARGET(INSTRUMENTED_CALL_KW) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 1; INSTRUCTION_STATS(INSTRUMENTED_CALL_KW); int is_meth = PEEK(oparg + 2) != NULL; @@ -2939,13 +3054,13 @@ TARGET(INSTRUMENTED_END_FOR) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 1; INSTRUCTION_STATS(INSTRUMENTED_END_FOR); PyObject *value; PyObject *receiver; value = stack_pointer[-1]; receiver = stack_pointer[-2]; - TIER_ONE_ONLY /* Need to create a fake StopIteration error here, * to conform to PEP 380 */ if (PyGen_Check(receiver)) { @@ -2962,13 +3077,13 @@ TARGET(INSTRUMENTED_END_SEND) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 1; INSTRUCTION_STATS(INSTRUMENTED_END_SEND); PyObject *value; PyObject *receiver; value = stack_pointer[-1]; receiver = stack_pointer[-2]; - TIER_ONE_ONLY if (PyGen_Check(receiver) || PyCoro_CheckExact(receiver)) { PyErr_SetObject(PyExc_StopIteration, value); if (monitor_stop_iteration(tstate, frame, this_instr)) { @@ -2984,6 +3099,7 @@ TARGET(INSTRUMENTED_FOR_ITER) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_FOR_ITER); /* Skip 1 cache entry */ @@ -3016,6 +3132,7 @@ TARGET(INSTRUMENTED_INSTRUCTION) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 1; INSTRUCTION_STATS(INSTRUMENTED_INSTRUCTION); int next_opcode = _Py_call_instrumentation_instruction( @@ -3032,6 +3149,7 @@ TARGET(INSTRUMENTED_JUMP_BACKWARD) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_JUMP_BACKWARD); /* Skip 1 cache entry */ @@ -3042,6 +3160,7 @@ TARGET(INSTRUMENTED_JUMP_FORWARD) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 1; INSTRUCTION_STATS(INSTRUMENTED_JUMP_FORWARD); INSTRUMENTED_JUMP(this_instr, next_instr + oparg, PY_MONITORING_EVENT_JUMP); @@ -3050,6 +3169,7 @@ TARGET(INSTRUMENTED_LOAD_SUPER_ATTR) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_LOAD_SUPER_ATTR); /* Skip 1 cache entry */ @@ -3061,6 +3181,7 @@ TARGET(INSTRUMENTED_POP_JUMP_IF_FALSE) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_POP_JUMP_IF_FALSE); /* Skip 1 cache entry */ @@ -3077,6 +3198,7 @@ TARGET(INSTRUMENTED_POP_JUMP_IF_NONE) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_POP_JUMP_IF_NONE); /* Skip 1 cache entry */ @@ -3099,6 +3221,7 @@ TARGET(INSTRUMENTED_POP_JUMP_IF_NOT_NONE) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_POP_JUMP_IF_NOT_NONE); /* Skip 1 cache entry */ @@ -3121,6 +3244,7 @@ TARGET(INSTRUMENTED_POP_JUMP_IF_TRUE) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 2; INSTRUCTION_STATS(INSTRUMENTED_POP_JUMP_IF_TRUE); /* Skip 1 cache entry */ @@ -3137,6 +3261,7 @@ TARGET(INSTRUMENTED_RESUME) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 1; INSTRUCTION_STATS(INSTRUMENTED_RESUME); uintptr_t global_version = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & ~_PY_EVAL_EVENTS_MASK; @@ -3167,6 +3292,7 @@ TARGET(INSTRUMENTED_RETURN_CONST) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 1; INSTRUCTION_STATS(INSTRUMENTED_RETURN_CONST); PyObject *retval = GETITEM(FRAME_CO_CONSTS, oparg); @@ -3190,6 +3316,7 @@ TARGET(INSTRUMENTED_RETURN_VALUE) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 1; INSTRUCTION_STATS(INSTRUMENTED_RETURN_VALUE); PyObject *retval; @@ -3214,6 +3341,7 @@ TARGET(INSTRUMENTED_YIELD_VALUE) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 1; INSTRUCTION_STATS(INSTRUMENTED_YIELD_VALUE); PyObject *retval; @@ -3248,7 +3376,6 @@ INSTRUCTION_STATS(INTERPRETER_EXIT); PyObject *retval; retval = stack_pointer[-1]; - TIER_ONE_ONLY assert(frame == &entry_frame); assert(_PyFrame_IsIncomplete(frame)); /* Restore previous frame and return. */ @@ -3278,10 +3405,10 @@ TARGET(JUMP_BACKWARD) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 2; INSTRUCTION_STATS(JUMP_BACKWARD); /* Skip 1 cache entry */ - TIER_ONE_ONLY CHECK_EVAL_BREAKER(); assert(oparg <= INSTR_OFFSET()); JUMPBY(-oparg); @@ -3296,7 +3423,6 @@ // Use '>=' not '>' so that the optimizer/backoff bits do not effect the result. // Double-check that the opcode isn't instrumented or something: if (offset_counter >= threshold && this_instr->op.code == JUMP_BACKWARD) { - OPT_STAT_INC(attempts); _Py_CODEUNIT *start = this_instr; /* Back up over EXTENDED_ARGs so optimizer sees the whole instruction */ while (oparg > 255) { @@ -3331,7 +3457,6 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(JUMP_BACKWARD_NO_INTERRUPT); - TIER_ONE_ONLY /* This bytecode is used in the `yield from` or `await` loop. * If there is an interrupt, we want it handled in the innermost * generator or coroutine, so we deliberately do not check it here. @@ -3345,7 +3470,6 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(JUMP_FORWARD); - TIER_ONE_ONLY JUMPBY(oparg); DISPATCH(); } @@ -3407,6 +3531,7 @@ INSTRUCTION_STATS(LOAD_ATTR); PREDICTED(LOAD_ATTR); _Py_CODEUNIT *this_instr = next_instr - 10; + (void)this_instr; PyObject *owner; PyObject *attr; PyObject *self_or_null = NULL; @@ -3414,7 +3539,7 @@ owner = stack_pointer[-1]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY + (void)counter; #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg>>1); @@ -4104,12 +4229,13 @@ INSTRUCTION_STATS(LOAD_GLOBAL); PREDICTED(LOAD_GLOBAL); _Py_CODEUNIT *this_instr = next_instr - 5; + (void)this_instr; PyObject *res; PyObject *null = NULL; // _SPECIALIZE_LOAD_GLOBAL { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY + (void)counter; #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg>>1); @@ -4301,6 +4427,7 @@ INSTRUCTION_STATS(LOAD_SUPER_ATTR); PREDICTED(LOAD_SUPER_ATTR); _Py_CODEUNIT *this_instr = next_instr - 2; + (void)this_instr; PyObject *class; PyObject *global_super; PyObject *self; @@ -4311,7 +4438,7 @@ global_super = stack_pointer[-3]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY + (void)counter; #if ENABLE_SPECIALIZATION int load_method = oparg & 1; if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { @@ -4326,7 +4453,6 @@ // _LOAD_SUPER_ATTR self = stack_pointer[-1]; { - TIER_ONE_ONLY if (opcode == INSTRUMENTED_LOAD_SUPER_ATTR) { PyObject *arg = oparg & 2 ? class : &_PyInstrumentation_MISSING; int err = _Py_call_instrumentation_2args( @@ -4589,6 +4715,7 @@ TARGET(POP_JUMP_IF_FALSE) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 2; INSTRUCTION_STATS(POP_JUMP_IF_FALSE); PyObject *cond; @@ -4606,6 +4733,7 @@ TARGET(POP_JUMP_IF_NONE) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 2; INSTRUCTION_STATS(POP_JUMP_IF_NONE); PyObject *value; @@ -4639,6 +4767,7 @@ TARGET(POP_JUMP_IF_NOT_NONE) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 2; INSTRUCTION_STATS(POP_JUMP_IF_NOT_NONE); PyObject *value; @@ -4672,6 +4801,7 @@ TARGET(POP_JUMP_IF_TRUE) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 2; INSTRUCTION_STATS(POP_JUMP_IF_TRUE); PyObject *cond; @@ -4733,11 +4863,11 @@ TARGET(RAISE_VARARGS) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 1; INSTRUCTION_STATS(RAISE_VARARGS); PyObject **args; args = &stack_pointer[-oparg]; - TIER_ONE_ONLY PyObject *cause = NULL, *exc = NULL; switch (oparg) { case 2: @@ -4763,13 +4893,13 @@ TARGET(RERAISE) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + (void)this_instr; next_instr += 1; INSTRUCTION_STATS(RERAISE); PyObject *exc; PyObject **values; exc = stack_pointer[-1]; values = &stack_pointer[-1 - oparg]; - TIER_ONE_ONLY assert(oparg >= 0 && oparg <= 2); if (oparg) { PyObject *lasti = values[0]; @@ -4794,7 +4924,6 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(RESERVED); - TIER_ONE_ONLY assert(0 && "Executing RESERVED instruction."); Py_FatalError("Executing RESERVED instruction."); DISPATCH(); @@ -4806,7 +4935,7 @@ INSTRUCTION_STATS(RESUME); PREDICTED(RESUME); _Py_CODEUNIT *this_instr = next_instr - 1; - TIER_ONE_ONLY + (void)this_instr; assert(frame == tstate->current_frame); uintptr_t global_version = _Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & @@ -4884,7 +5013,6 @@ frame->instr_ptr = next_instr; next_instr += 1; INSTRUCTION_STATS(RETURN_GENERATOR); - TIER_ONE_ONLY assert(PyFunction_Check(frame->f_funcobj)); PyFunctionObject *func = (PyFunctionObject *)frame->f_funcobj; PyGenObject *gen = (PyGenObject *)_Py_MakeCoro(func); @@ -4944,6 +5072,7 @@ INSTRUCTION_STATS(SEND); PREDICTED(SEND); _Py_CODEUNIT *this_instr = next_instr - 2; + (void)this_instr; PyObject *receiver; PyObject *v; PyObject *retval; @@ -4951,7 +5080,7 @@ receiver = stack_pointer[-2]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY + (void)counter; #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -5132,13 +5261,14 @@ INSTRUCTION_STATS(STORE_ATTR); PREDICTED(STORE_ATTR); _Py_CODEUNIT *this_instr = next_instr - 5; + (void)this_instr; PyObject *owner; PyObject *v; // _SPECIALIZE_STORE_ATTR owner = stack_pointer[-1]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY + (void)counter; #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { PyObject *name = GETITEM(FRAME_CO_NAMES, oparg); @@ -5422,6 +5552,7 @@ INSTRUCTION_STATS(STORE_SUBSCR); PREDICTED(STORE_SUBSCR); _Py_CODEUNIT *this_instr = next_instr - 2; + (void)this_instr; PyObject *sub; PyObject *container; PyObject *v; @@ -5430,7 +5561,7 @@ container = stack_pointer[-2]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY + (void)counter; #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -5526,13 +5657,14 @@ INSTRUCTION_STATS(TO_BOOL); PREDICTED(TO_BOOL); _Py_CODEUNIT *this_instr = next_instr - 4; + (void)this_instr; PyObject *value; PyObject *res; // _SPECIALIZE_TO_BOOL value = stack_pointer[-1]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY + (void)counter; #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -5560,17 +5692,24 @@ next_instr += 4; INSTRUCTION_STATS(TO_BOOL_ALWAYS_TRUE); static_assert(INLINE_CACHE_ENTRIES_TO_BOOL == 3, "incorrect cache size"); + PyObject *owner; PyObject *value; PyObject *res; /* Skip 1 cache entry */ - value = stack_pointer[-1]; - uint32_t version = read_u32(&this_instr[2].cache); - // This one is a bit weird, because we expect *some* failures: - assert(version); - DEOPT_IF(Py_TYPE(value)->tp_version_tag != version, TO_BOOL); - STAT_INC(TO_BOOL, hit); - Py_DECREF(value); - res = Py_True; + // _GUARD_TYPE_VERSION + owner = stack_pointer[-1]; + { + uint32_t type_version = read_u32(&this_instr[2].cache); + PyTypeObject *tp = Py_TYPE(owner); + assert(type_version != 0); + DEOPT_IF(tp->tp_version_tag != type_version, TO_BOOL); + } + // _REPLACE_WITH_TRUE + value = owner; + { + Py_DECREF(value); + res = Py_True; + } stack_pointer[-1] = res; DISPATCH(); } @@ -5736,12 +5875,13 @@ INSTRUCTION_STATS(UNPACK_SEQUENCE); PREDICTED(UNPACK_SEQUENCE); _Py_CODEUNIT *this_instr = next_instr - 2; + (void)this_instr; PyObject *seq; // _SPECIALIZE_UNPACK_SEQUENCE seq = stack_pointer[-1]; { uint16_t counter = read_u16(&this_instr[1].cache); - TIER_ONE_ONLY + (void)counter; #if ENABLE_SPECIALIZATION if (ADAPTIVE_COUNTER_IS_ZERO(counter)) { next_instr = this_instr; @@ -5815,18 +5955,20 @@ INSTRUCTION_STATS(UNPACK_SEQUENCE_TWO_TUPLE); static_assert(INLINE_CACHE_ENTRIES_UNPACK_SEQUENCE == 1, "incorrect cache size"); PyObject *seq; - PyObject **values; + PyObject *val1; + PyObject *val0; /* Skip 1 cache entry */ seq = stack_pointer[-1]; - values = &stack_pointer[-1]; + assert(oparg == 2); DEOPT_IF(!PyTuple_CheckExact(seq), UNPACK_SEQUENCE); DEOPT_IF(PyTuple_GET_SIZE(seq) != 2, UNPACK_SEQUENCE); - assert(oparg == 2); STAT_INC(UNPACK_SEQUENCE, hit); - values[0] = Py_NewRef(PyTuple_GET_ITEM(seq, 1)); - values[1] = Py_NewRef(PyTuple_GET_ITEM(seq, 0)); + val0 = Py_NewRef(PyTuple_GET_ITEM(seq, 0)); + val1 = Py_NewRef(PyTuple_GET_ITEM(seq, 1)); Py_DECREF(seq); - stack_pointer += -1 + oparg; + stack_pointer[-1] = val1; + stack_pointer[0] = val0; + stack_pointer += 1; DISPATCH(); } @@ -5876,7 +6018,6 @@ INSTRUCTION_STATS(YIELD_VALUE); PyObject *retval; retval = stack_pointer[-1]; - TIER_ONE_ONLY // NOTE: It's important that YIELD_VALUE never raises an exception! // The compiler treats any exception raised here as a failed close() // or throw() call. diff --git a/Python/getargs.c b/Python/getargs.c index 08e97ee3e627b5a..bec981698767ca0 100644 --- a/Python/getargs.c +++ b/Python/getargs.c @@ -612,7 +612,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, switch (c) { case 'b': { /* unsigned byte -- very short int */ - char *p = va_arg(*p_va, char *); + unsigned char *p = va_arg(*p_va, unsigned char *); long ival = PyLong_AsLong(arg); if (ival == -1 && PyErr_Occurred()) RETURN_ERR_OCCURRED; @@ -633,7 +633,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, case 'B': {/* byte sized bitfield - both signed and unsigned values allowed */ - char *p = va_arg(*p_va, char *); + unsigned char *p = va_arg(*p_va, unsigned char *); unsigned long ival = PyLong_AsUnsignedLongMask(arg); if (ival == (unsigned long)-1 && PyErr_Occurred()) RETURN_ERR_OCCURRED; diff --git a/Python/initconfig.c b/Python/initconfig.c index 74f28f3b39175b1..bbd611f7f7a48c2 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -30,6 +30,7 @@ typedef enum { PyConfig_MEMBER_INT = 0, PyConfig_MEMBER_UINT = 1, PyConfig_MEMBER_ULONG = 2, + PyConfig_MEMBER_BOOL = 3, PyConfig_MEMBER_WSTR = 10, PyConfig_MEMBER_WSTR_OPT = 11, @@ -45,61 +46,65 @@ typedef struct { #define SPEC(MEMBER, TYPE) \ {#MEMBER, offsetof(PyConfig, MEMBER), PyConfig_MEMBER_##TYPE} +// Update _test_embed_set_config when adding new members static const PyConfigSpec PYCONFIG_SPEC[] = { SPEC(_config_init, UINT), - SPEC(isolated, UINT), - SPEC(use_environment, UINT), - SPEC(dev_mode, UINT), - SPEC(install_signal_handlers, UINT), - SPEC(use_hash_seed, UINT), + SPEC(isolated, BOOL), + SPEC(use_environment, BOOL), + SPEC(dev_mode, BOOL), + SPEC(install_signal_handlers, BOOL), + SPEC(use_hash_seed, BOOL), SPEC(hash_seed, ULONG), - SPEC(faulthandler, UINT), + SPEC(faulthandler, BOOL), SPEC(tracemalloc, UINT), - SPEC(perf_profiling, UINT), - SPEC(import_time, UINT), - SPEC(code_debug_ranges, UINT), - SPEC(show_ref_count, UINT), - SPEC(dump_refs, UINT), + SPEC(perf_profiling, BOOL), + SPEC(import_time, BOOL), + SPEC(code_debug_ranges, BOOL), + SPEC(show_ref_count, BOOL), + SPEC(dump_refs, BOOL), SPEC(dump_refs_file, WSTR_OPT), - SPEC(malloc_stats, UINT), + SPEC(malloc_stats, BOOL), SPEC(filesystem_encoding, WSTR), SPEC(filesystem_errors, WSTR), SPEC(pycache_prefix, WSTR_OPT), - SPEC(parse_argv, UINT), + SPEC(parse_argv, BOOL), SPEC(orig_argv, WSTR_LIST), SPEC(argv, WSTR_LIST), SPEC(xoptions, WSTR_LIST), SPEC(warnoptions, WSTR_LIST), - SPEC(site_import, UINT), + SPEC(site_import, BOOL), SPEC(bytes_warning, UINT), - SPEC(warn_default_encoding, UINT), - SPEC(inspect, UINT), - SPEC(interactive, UINT), + SPEC(warn_default_encoding, BOOL), + SPEC(inspect, BOOL), + SPEC(interactive, BOOL), SPEC(optimization_level, UINT), - SPEC(parser_debug, UINT), - SPEC(write_bytecode, UINT), + SPEC(parser_debug, BOOL), + SPEC(write_bytecode, BOOL), SPEC(verbose, UINT), - SPEC(quiet, UINT), - SPEC(user_site_directory, UINT), - SPEC(configure_c_stdio, UINT), - SPEC(buffered_stdio, UINT), + SPEC(quiet, BOOL), + SPEC(user_site_directory, BOOL), + SPEC(configure_c_stdio, BOOL), + SPEC(buffered_stdio, BOOL), SPEC(stdio_encoding, WSTR), SPEC(stdio_errors, WSTR), #ifdef MS_WINDOWS - SPEC(legacy_windows_stdio, UINT), + SPEC(legacy_windows_stdio, BOOL), #endif SPEC(check_hash_pycs_mode, WSTR), - SPEC(use_frozen_modules, UINT), - SPEC(safe_path, UINT), + SPEC(use_frozen_modules, BOOL), + SPEC(safe_path, BOOL), SPEC(int_max_str_digits, INT), SPEC(cpu_count, INT), - SPEC(pathconfig_warnings, UINT), +#ifdef Py_GIL_DISABLED + SPEC(enable_gil, INT), +#endif + SPEC(pathconfig_warnings, BOOL), SPEC(program_name, WSTR), SPEC(pythonpath_env, WSTR_OPT), SPEC(home, WSTR_OPT), SPEC(platlibdir, WSTR), SPEC(sys_path_0, WSTR_OPT), - SPEC(module_search_paths_set, UINT), + SPEC(module_search_paths_set, BOOL), SPEC(module_search_paths, WSTR_LIST), SPEC(stdlib_dir, WSTR_OPT), SPEC(executable, WSTR_OPT), @@ -108,15 +113,15 @@ static const PyConfigSpec PYCONFIG_SPEC[] = { SPEC(base_prefix, WSTR_OPT), SPEC(exec_prefix, WSTR_OPT), SPEC(base_exec_prefix, WSTR_OPT), - SPEC(skip_source_first_line, UINT), + SPEC(skip_source_first_line, BOOL), SPEC(run_command, WSTR_OPT), SPEC(run_module, WSTR_OPT), SPEC(run_filename, WSTR_OPT), - SPEC(_install_importlib, UINT), - SPEC(_init_main, UINT), - SPEC(_is_python_build, UINT), + SPEC(_install_importlib, BOOL), + SPEC(_init_main, BOOL), + SPEC(_is_python_build, BOOL), #ifdef Py_STATS - SPEC(_pystats, UINT), + SPEC(_pystats, BOOL), #endif #ifdef Py_DEBUG SPEC(run_presite, WSTR_OPT), @@ -137,8 +142,8 @@ static const char usage_line[] = /* Lines sorted by option name; keep in sync with usage_envvars* below */ static const char usage_help[] = "\ Options (and corresponding environment variables):\n\ --b : issue warnings about str(bytes_instance), str(bytearray_instance)\n\ - and comparing bytes/bytearray with str. (-bb: issue errors)\n\ +-b : issue warnings about converting bytes/bytearray to str and comparing\n\ + bytes/bytearray with str or bytes with int. (-bb: issue errors)\n\ -B : don't write .pyc files on import; also PYTHONDONTWRITEBYTECODE=x\n\ -c cmd : program passed in as string (terminates option list)\n\ -d : turn on parser debugging output (for experts only, only works on\n\ @@ -182,19 +187,9 @@ arg ...: arguments passed to program in sys.argv[1:]\n\ static const char usage_xoptions[] = "\ The following implementation-specific options are available:\n\ --X faulthandler: enable faulthandler\n\ --X showrefcount: output the total reference count and number of used\n\ - memory blocks when the program finishes or after each statement in\n\ - the interactive interpreter. This only works on debug builds\n\ --X tracemalloc: start tracing Python memory allocations using the\n\ - tracemalloc module. By default, only the most recent frame is stored\n\ - in a traceback of a trace. Use -X tracemalloc=NFRAME to start\n\ - tracing with a traceback limit of NFRAME frames\n\ --X importtime: show how long each import takes. It shows module name,\n\ - cumulative time (including nested imports) and self time (excluding\n\ - nested imports). Note that its output may be broken in\n\ - multi-threaded application.\n\ - Typical usage is python3 -X importtime -c 'import asyncio'\n\ +-X cpu_count=[n|default]: Override the return value of os.cpu_count(),\n\ + os.process_cpu_count(), and multiprocessing.cpu_count(). This can\n\ + help users who need to limit resources in a container.\n\ -X dev : enable CPython's \"development mode\", introducing additional runtime\n\ checks which are too expensive to be enabled by default. Effect of\n\ the developer mode:\n\ @@ -206,12 +201,18 @@ The following implementation-specific options are available:\n\ * Enable asyncio debug mode\n\ * Set the dev_mode attribute of sys.flags to True\n\ * io.IOBase destructor logs close() exceptions\n\ --X utf8: enable UTF-8 mode for operating system interfaces, overriding the\n\ - default locale-aware mode. -X utf8=0 explicitly disables UTF-8 mode\n\ - (even when it would otherwise activate automatically)\n\ --X pycache_prefix=PATH: enable writing .pyc files to a parallel tree rooted\n\ - at the given directory instead of to the code tree\n\ --X warn_default_encoding: enable opt-in EncodingWarning for 'encoding=None'\n\ +-X faulthandler: enable faulthandler\n\ +-X frozen_modules=[on|off]: whether or not frozen modules should be used.\n\ + The default is \"on\" (or \"off\" if you are running a local build).\n\ +-X importtime: show how long each import takes. It shows module name,\n\ + cumulative time (including nested imports) and self time (excluding\n\ + nested imports). Note that its output may be broken in\n\ + multi-threaded application.\n\ + Typical usage is python3 -X importtime -c 'import asyncio'\n\ +-X int_max_str_digits=number: limit the size of int<->str conversions.\n\ + This helps avoid denial of service attacks when parsing untrusted\n\ + data. The default is sys.int_info.default_max_str_digits.\n\ + 0 disables.\n\ -X no_debug_ranges: disable the inclusion of the tables mapping extra location\n\ information (end line, start column offset and end column offset) to\n\ every instruction in code objects. This is useful when smaller code\n\ @@ -222,23 +223,30 @@ The following implementation-specific options are available:\n\ profiler will be able to report Python calls. This option is only\n\ available on some platforms and will do nothing if is not supported\n\ on the current system. The default value is \"off\".\n\ --X frozen_modules=[on|off]: whether or not frozen modules should be used.\n\ - The default is \"on\" (or \"off\" if you are running a local build).\n\ --X int_max_str_digits=number: limit the size of int<->str conversions.\n\ - This helps avoid denial of service attacks when parsing untrusted\n\ - data. The default is sys.int_info.default_max_str_digits.\n\ - 0 disables.\n\ --X cpu_count=[n|default]: Override the return value of os.cpu_count(),\n\ - os.process_cpu_count(), and multiprocessing.cpu_count(). This can\n\ - help users who need to limit resources in a container." -#ifdef Py_STATS -"\n\ --X pystats: Enable pystats collection at startup." -#endif +" #ifdef Py_DEBUG -"\n\ --X presite=package.module: import this module before site.py is run." +"-X presite=package.module: import this module before site.py is run.\n" +#endif +"\ +-X pycache_prefix=PATH: enable writing .pyc files to a parallel tree rooted\n\ + at the given directory instead of to the code tree\n\ +" +#ifdef Py_STATS +"-X pystats: Enable pystats collection at startup.\n" #endif +"\ +-X showrefcount: output the total reference count and number of used\n\ + memory blocks when the program finishes or after each statement in\n\ + the interactive interpreter. This only works on debug builds\n\ +-X tracemalloc: start tracing Python memory allocations using the\n\ + tracemalloc module. By default, only the most recent frame is stored\n\ + in a traceback of a trace. Use -X tracemalloc=NFRAME to start\n\ + tracing with a traceback limit of NFRAME frames\n\ +-X utf8: enable UTF-8 mode for operating system interfaces, overriding the\n\ + default locale-aware mode. -X utf8=0 explicitly disables UTF-8 mode\n\ + (even when it would otherwise activate automatically)\n\ +-X warn_default_encoding: enable opt-in EncodingWarning for 'encoding=None'\ +" ; /* Envvars that don't have equivalent command-line options are listed first */ @@ -251,18 +259,11 @@ static const char usage_envvars[] = " The default module search path uses %s.\n" "PYTHONPLATLIBDIR: override sys.platlibdir.\n" "PYTHONCASEOK : ignore case in 'import' statements (Windows).\n" -"PYTHONUTF8 : if set to 1, enable the UTF-8 mode.\n" "PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr.\n" -"PYTHONFAULTHANDLER: dump the Python traceback on fatal errors.\n" "PYTHONHASHSEED : if this variable is set to 'random', a random value is used\n" " to seed the hashes of str and bytes objects. It can also be\n" " set to an integer in the range [0,4294967295] to get hash\n" " values with a predictable seed.\n" -"PYTHONINTMAXSTRDIGITS: limits the maximum digit characters in an int value\n" -" when converting from a string and when converting an int\n" -" back to a str. A value of 0 disables the limit.\n" -" Conversions to or from bases 2, 4, 8, 16, and 32 are never\n" -" limited.\n" "PYTHONMALLOC : set the Python memory allocators and/or install debug hooks\n" " on Python memory allocators. Use PYTHONMALLOC=debug to\n" " install debug hooks.\n" @@ -273,45 +274,60 @@ static const char usage_envvars[] = "PYTHONBREAKPOINT: if this variable is set to 0, it disables the default\n" " debugger. It can be set to the callable of your debugger of\n" " choice.\n" -"PYTHON_CPU_COUNT: Overrides the return value of os.process_cpu_count(),\n" -" os.cpu_count(), and multiprocessing.cpu_count() if set to\n" -" a positive integer.\n" -"PYTHONDEVMODE : enable the development mode.\n" -"PYTHONPYCACHEPREFIX: root directory for bytecode cache (pyc) files.\n" -"PYTHONWARNDEFAULTENCODING: enable opt-in EncodingWarning for 'encoding=None'.\n" -"PYTHONNODEBUGRANGES: if this variable is set, it disables the inclusion of\n" -" the tables mapping extra location information (end line,\n" -" start column offset and end column offset) to every\n" -" instruction in code objects. This is useful when smaller\n" -" code objects and pyc files are desired as well as\n" -" suppressing the extra visual location indicators when the\n" -" interpreter displays tracebacks.\n" -"PYTHON_FROZEN_MODULES: if this variable is set, it determines whether or not\n" -" frozen modules should be used. The default is \"on\" (or\n" -" \"off\" if you are running a local build).\n" "PYTHON_COLORS : if this variable is set to 1, the interpreter will colorize\n" " various kinds of output. Setting it to 0 deactivates\n" " this behavior.\n" "PYTHON_HISTORY : the location of a .python_history file.\n" "\n" "These variables have equivalent command-line options (see --help for details):\n" +"PYTHON_CPU_COUNT: Overrides the return value of os.process_cpu_count(),\n" +" os.cpu_count(), and multiprocessing.cpu_count() if set to\n" +" a positive integer. (-X cpu_count)\n" "PYTHONDEBUG : enable parser debug mode (-d)\n" +"PYTHONDEVMODE : enable the development mode (-X dev)\n" "PYTHONDONTWRITEBYTECODE: don't write .pyc files (-B)\n" +"PYTHONFAULTHANDLER: dump the Python traceback on fatal errors (-X faulthandler)\n" +"PYTHON_FROZEN_MODULES: if this variable is set, it determines whether or not\n" +" frozen modules should be used. The default is \"on\" (or\n" +" \"off\" if you are running a local build).\n" +" (-X frozen_modules)\n" +#ifdef Py_GIL_DISABLED +"PYTHON_GIL : when set to 0, disables the GIL (-X gil)\n" +#endif "PYTHONINSPECT : inspect interactively after running script (-i)\n" -"PYTHONINTMAXSTRDIGITS: limit max digit characters in an int value\n" +"PYTHONINTMAXSTRDIGITS: limits the maximum digit characters in an int value\n" +" when converting from a string and when converting an int\n" +" back to a str. A value of 0 disables the limit.\n" +" Conversions to or from bases 2, 4, 8, 16, and 32 are never\n" +" limited.\n" " (-X int_max_str_digits=number)\n" +"PYTHONNODEBUGRANGES: if this variable is set, it disables the inclusion of\n" +" the tables mapping extra location information (end line,\n" +" start column offset and end column offset) to every\n" +" instruction in code objects. This is useful when smaller\n" +" code objects and pyc files are desired as well as\n" +" suppressing the extra visual location indicators when the\n" +" interpreter displays tracebacks. (-X no_debug_ranges)\n" "PYTHONNOUSERSITE: disable user site directory (-s)\n" "PYTHONOPTIMIZE : enable level 1 optimizations (-O)\n" +"PYTHONPERFSUPPORT: support the Linux \"perf\" profiler (-X perf)\n" +#ifdef Py_DEBUG +"PYTHON_PRESITE=pkg.mod: import this module before site.py is run (-X presite)\n" +#endif +"PYTHONPROFILEIMPORTTIME: show how long each import takes (-X importtime)\n" +"PYTHONPYCACHEPREFIX: root directory for bytecode cache (pyc) files\n" +" (-X pycache_prefix)\n" "PYTHONSAFEPATH : don't prepend a potentially unsafe path to sys.path.\n" +#ifdef Py_STATS +"PYTHONSTATS : turns on statistics gathering (-X pystats)\n" +#endif +"PYTHONTRACEMALLOC: trace Python memory allocations (-X tracemalloc)\n" "PYTHONUNBUFFERED: disable stdout/stderr buffering (-u)\n" +"PYTHONUTF8 : if set to 1, enable the UTF-8 mode (-X utf8)\n" "PYTHONVERBOSE : trace import statements (-v)\n" +"PYTHONWARNDEFAULTENCODING: enable opt-in EncodingWarning for 'encoding=None'\n" +" (-X warn_default_encoding)\n" "PYTHONWARNINGS=arg: warning control (-W arg)\n" -#ifdef Py_STATS -"PYTHONSTATS : turns on statistics gathering\n" -#endif -#ifdef Py_DEBUG -"PYTHON_PRESITE=pkg.mod: import this module before site.py is run\n" -#endif ; #if defined(MS_WINDOWS) @@ -860,6 +876,9 @@ _PyConfig_InitCompatConfig(PyConfig *config) config->_is_python_build = 0; config->code_debug_ranges = 1; config->cpu_count = -1; +#ifdef Py_GIL_DISABLED + config->enable_gil = _PyConfig_GIL_DEFAULT; +#endif } @@ -1007,6 +1026,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2) switch (spec->type) { case PyConfig_MEMBER_INT: case PyConfig_MEMBER_UINT: + case PyConfig_MEMBER_BOOL: { *(int*)member = *(int*)member2; break; @@ -1062,6 +1082,12 @@ _PyConfig_AsDict(const PyConfig *config) obj = PyLong_FromLong(value); break; } + case PyConfig_MEMBER_BOOL: + { + int value = *(int*)member; + obj = PyBool_FromLong(value); + break; + } case PyConfig_MEMBER_ULONG: { unsigned long value = *(unsigned long*)member; @@ -1285,19 +1311,20 @@ _PyConfig_FromDict(PyConfig *config, PyObject *dict) char *member = (char *)config + spec->offset; switch (spec->type) { case PyConfig_MEMBER_INT: - if (config_dict_get_int(dict, spec->name, (int*)member) < 0) { - return -1; - } - break; case PyConfig_MEMBER_UINT: + case PyConfig_MEMBER_BOOL: { int value; if (config_dict_get_int(dict, spec->name, &value) < 0) { return -1; } - if (value < 0) { - config_dict_invalid_value(spec->name); - return -1; + if (spec->type == PyConfig_MEMBER_BOOL + || spec->type == PyConfig_MEMBER_UINT) + { + if (value < 0) { + config_dict_invalid_value(spec->name); + return -1; + } } *(int*)member = value; break; @@ -1564,6 +1591,24 @@ config_wstr_to_int(const wchar_t *wstr, int *result) return 0; } +static PyStatus +config_read_gil(PyConfig *config, size_t len, wchar_t first_char) +{ +#ifdef Py_GIL_DISABLED + if (len == 1 && first_char == L'0') { + config->enable_gil = _PyConfig_GIL_DISABLE; + } + else if (len == 1 && first_char == L'1') { + config->enable_gil = _PyConfig_GIL_ENABLE; + } + else { + return _PyStatus_ERR("PYTHON_GIL / -X gil must be \"0\" or \"1\""); + } + return _PyStatus_OK(); +#else + return _PyStatus_ERR("PYTHON_GIL / -X gil are not supported by this build"); +#endif +} static PyStatus config_read_env_vars(PyConfig *config) @@ -1642,6 +1687,15 @@ config_read_env_vars(PyConfig *config) config->safe_path = 1; } + const char *gil = config_get_env(config, "PYTHON_GIL"); + if (gil != NULL) { + size_t len = strlen(gil); + status = config_read_gil(config, len, gil[0]); + if (_PyStatus_EXCEPTION(status)) { + return status; + } + } + return _PyStatus_OK(); } @@ -2197,6 +2251,15 @@ config_read(PyConfig *config, int compute_path_config) config->show_ref_count = 1; } + const wchar_t *x_gil = config_get_xoption_value(config, L"gil"); + if (x_gil != NULL) { + size_t len = wcslen(x_gil); + status = config_read_gil(config, len, x_gil[0]); + if (_PyStatus_EXCEPTION(status)) { + return status; + } + } + #ifdef Py_STATS if (config_get_xoption(config, L"pystats")) { config->_pystats = 1; diff --git a/Python/instrumentation.c b/Python/instrumentation.c index 878d19f0552bf59..018cd662b1561af 100644 --- a/Python/instrumentation.c +++ b/Python/instrumentation.c @@ -891,8 +891,16 @@ static inline int most_significant_bit(uint8_t bits) { static uint32_t global_version(PyInterpreterState *interp) { - return (uint32_t)_Py_atomic_load_uintptr_relaxed( + uint32_t version = (uint32_t)_Py_atomic_load_uintptr_relaxed( &interp->ceval.instrumentation_version); +#ifdef Py_DEBUG + PyThreadState *tstate = _PyThreadState_GET(); + uint32_t thread_version = + (uint32_t)(_Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & + ~_PY_EVAL_EVENTS_MASK); + assert(thread_version == version); +#endif + return version; } /* Atomically set the given version in the given location, without touching @@ -1599,7 +1607,7 @@ _Py_Instrument(PyCodeObject *code, PyInterpreterState *interp) if (code->co_executors != NULL) { _PyCode_Clear_Executors(code); } - _Py_Executors_InvalidateDependency(interp, code); + _Py_Executors_InvalidateDependency(interp, code, 1); int code_len = (int)Py_SIZE(code); /* Exit early to avoid creating instrumentation * data for potential statically allocated code @@ -1820,7 +1828,7 @@ _PyMonitoring_SetEvents(int tool_id, _PyMonitoringEventSet events) return -1; } set_global_version(tstate, new_version); - _Py_Executors_InvalidateAll(interp); + _Py_Executors_InvalidateAll(interp, 1); return instrument_all_executing_code_objects(interp); } @@ -1850,7 +1858,7 @@ _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEvent /* Force instrumentation update */ code->_co_instrumentation_version -= MONITORING_VERSION_INCREMENT; } - _Py_Executors_InvalidateDependency(interp, code); + _Py_Executors_InvalidateDependency(interp, code, 1); if (_Py_Instrument(code, interp)) { return -1; } diff --git a/Python/jit.c b/Python/jit.c index 839414bd8106778..dae25166b1f1065 100644 --- a/Python/jit.c +++ b/Python/jit.c @@ -47,18 +47,18 @@ jit_error(const char *message) PyErr_Format(PyExc_RuntimeWarning, "JIT %s (%d)", message, hint); } -static char * +static unsigned char * jit_alloc(size_t size) { assert(size); assert(size % get_page_size() == 0); #ifdef MS_WINDOWS int flags = MEM_COMMIT | MEM_RESERVE; - char *memory = VirtualAlloc(NULL, size, flags, PAGE_READWRITE); + unsigned char *memory = VirtualAlloc(NULL, size, flags, PAGE_READWRITE); int failed = memory == NULL; #else int flags = MAP_ANONYMOUS | MAP_PRIVATE; - char *memory = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, -1, 0); + unsigned char *memory = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, -1, 0); int failed = memory == MAP_FAILED; #endif if (failed) { @@ -69,7 +69,7 @@ jit_alloc(size_t size) } static int -jit_free(char *memory, size_t size) +jit_free(unsigned char *memory, size_t size) { assert(size); assert(size % get_page_size() == 0); @@ -86,7 +86,7 @@ jit_free(char *memory, size_t size) } static int -mark_executable(char *memory, size_t size) +mark_executable(unsigned char *memory, size_t size) { if (size == 0) { return 0; @@ -113,7 +113,7 @@ mark_executable(char *memory, size_t size) } static int -mark_readable(char *memory, size_t size) +mark_readable(unsigned char *memory, size_t size) { if (size == 0) { return 0; @@ -169,20 +169,24 @@ set_bits(uint32_t *loc, uint8_t loc_start, uint64_t value, uint8_t value_start, // Fill all of stencil's holes in the memory pointed to by base, using the // values in patches. static void -patch(char *base, const Stencil *stencil, uint64_t *patches) +patch(unsigned char *base, const Stencil *stencil, uint64_t *patches) { for (uint64_t i = 0; i < stencil->holes_size; i++) { const Hole *hole = &stencil->holes[i]; - void *location = base + hole->offset; + unsigned char *location = base + hole->offset; uint64_t value = patches[hole->value] + (uint64_t)hole->symbol + hole->addend; + uint8_t *loc8 = (uint8_t *)location; uint32_t *loc32 = (uint32_t *)location; uint64_t *loc64 = (uint64_t *)location; // LLD is a great reference for performing relocations... just keep in // mind that Tools/jit/build.py does filtering and preprocessing for us! // Here's a good place to start for each platform: // - aarch64-apple-darwin: + // - https://github.com/llvm/llvm-project/blob/main/lld/MachO/Arch/ARM64.cpp // - https://github.com/llvm/llvm-project/blob/main/lld/MachO/Arch/ARM64Common.cpp // - https://github.com/llvm/llvm-project/blob/main/lld/MachO/Arch/ARM64Common.h + // - aarch64-pc-windows-msvc: + // - https://github.com/llvm/llvm-project/blob/main/lld/COFF/Chunks.cpp // - aarch64-unknown-linux-gnu: // - https://github.com/llvm/llvm-project/blob/main/lld/ELF/Arch/AArch64.cpp // - i686-pc-windows-msvc: @@ -201,13 +205,56 @@ patch(char *base, const Stencil *stencil, uint64_t *patches) *loc32 = (uint32_t)value; continue; case HoleKind_ARM64_RELOC_UNSIGNED: - case HoleKind_IMAGE_REL_AMD64_ADDR64: case HoleKind_R_AARCH64_ABS64: case HoleKind_X86_64_RELOC_UNSIGNED: case HoleKind_R_X86_64_64: // 64-bit absolute address. *loc64 = value; continue; + case HoleKind_IMAGE_REL_AMD64_REL32: + case HoleKind_IMAGE_REL_I386_REL32: + case HoleKind_R_X86_64_GOTPCRELX: + case HoleKind_R_X86_64_REX_GOTPCRELX: + case HoleKind_X86_64_RELOC_GOT: + case HoleKind_X86_64_RELOC_GOT_LOAD: { + // 32-bit relative address. + // Try to relax the GOT load into an immediate value: + uint64_t relaxed = *(uint64_t *)(value + 4) - 4; + if ((int64_t)relaxed - (int64_t)location >= -(1LL << 31) && + (int64_t)relaxed - (int64_t)location + 1 < (1LL << 31)) + { + if (loc8[-2] == 0x8B) { + // mov reg, dword ptr [rip + AAA] -> lea reg, [rip + XXX] + loc8[-2] = 0x8D; + value = relaxed; + } + else if (loc8[-2] == 0xFF && loc8[-1] == 0x15) { + // call qword ptr [rip + AAA] -> nop; call XXX + loc8[-2] = 0x90; + loc8[-1] = 0xE8; + value = relaxed; + } + else if (loc8[-2] == 0xFF && loc8[-1] == 0x25) { + // jmp qword ptr [rip + AAA] -> nop; jmp XXX + loc8[-2] = 0x90; + loc8[-1] = 0xE9; + value = relaxed; + } + } + } + // Fall through... + case HoleKind_R_X86_64_GOTPCREL: + case HoleKind_R_X86_64_PC32: + case HoleKind_X86_64_RELOC_SIGNED: + case HoleKind_X86_64_RELOC_BRANCH: + // 32-bit relative address. + value -= (uint64_t)location; + // Check that we're not out of range of 32 signed bits: + assert((int64_t)value >= -(1LL << 31)); + assert((int64_t)value < (1LL << 31)); + *loc32 = (uint32_t)value; + continue; + case HoleKind_IMAGE_REL_ARM64_BRANCH26: case HoleKind_R_AARCH64_CALL26: case HoleKind_R_AARCH64_JUMP26: // 28-bit relative branch. @@ -249,10 +296,57 @@ patch(char *base, const Stencil *stencil, uint64_t *patches) set_bits(loc32, 5, value, 48, 16); continue; case HoleKind_ARM64_RELOC_GOT_LOAD_PAGE21: + case HoleKind_IMAGE_REL_ARM64_PAGEBASE_REL21: + case HoleKind_R_AARCH64_ADR_GOT_PAGE: // 21-bit count of pages between this page and an absolute address's // page... I know, I know, it's weird. Pairs nicely with // ARM64_RELOC_GOT_LOAD_PAGEOFF12 (below). assert(IS_AARCH64_ADRP(*loc32)); + // Try to relax the pair of GOT loads into an immediate value: + const Hole *next_hole = &stencil->holes[i + 1]; + if (i + 1 < stencil->holes_size && + (next_hole->kind == HoleKind_ARM64_RELOC_GOT_LOAD_PAGEOFF12 || + next_hole->kind == HoleKind_IMAGE_REL_ARM64_PAGEOFFSET_12L || + next_hole->kind == HoleKind_R_AARCH64_LD64_GOT_LO12_NC) && + next_hole->offset == hole->offset + 4 && + next_hole->symbol == hole->symbol && + next_hole->addend == hole->addend && + next_hole->value == hole->value) + { + unsigned char reg = get_bits(loc32[0], 0, 5); + assert(IS_AARCH64_LDR_OR_STR(loc32[1])); + // There should be only one register involved: + assert(reg == get_bits(loc32[1], 0, 5)); // ldr's output register. + assert(reg == get_bits(loc32[1], 5, 5)); // ldr's input register. + uint64_t relaxed = *(uint64_t *)value; + if (relaxed < (1UL << 16)) { + // adrp reg, AAA; ldr reg, [reg + BBB] -> movz reg, XXX; nop + loc32[0] = 0xD2800000 | (get_bits(relaxed, 0, 16) << 5) | reg; + loc32[1] = 0xD503201F; + i++; + continue; + } + if (relaxed < (1ULL << 32)) { + // adrp reg, AAA; ldr reg, [reg + BBB] -> movz reg, XXX; movk reg, YYY + loc32[0] = 0xD2800000 | (get_bits(relaxed, 0, 16) << 5) | reg; + loc32[1] = 0xF2A00000 | (get_bits(relaxed, 16, 16) << 5) | reg; + i++; + continue; + } + relaxed = (uint64_t)value - (uint64_t)location; + if ((relaxed & 0x3) == 0 && + (int64_t)relaxed >= -(1L << 19) && + (int64_t)relaxed < (1L << 19)) + { + // adrp reg, AAA; ldr reg, [reg + BBB] -> ldr reg, XXX; nop + loc32[0] = 0x58000000 | (get_bits(relaxed, 2, 19) << 5) | reg; + loc32[1] = 0xD503201F; + i++; + continue; + } + } + // Fall through... + case HoleKind_ARM64_RELOC_PAGE21: // Number of pages between this page and the value's page: value = (value >> 12) - ((uint64_t)location >> 12); // Check that we're not out of range of 21 signed bits: @@ -264,6 +358,10 @@ patch(char *base, const Stencil *stencil, uint64_t *patches) set_bits(loc32, 5, value, 2, 19); continue; case HoleKind_ARM64_RELOC_GOT_LOAD_PAGEOFF12: + case HoleKind_ARM64_RELOC_PAGEOFF12: + case HoleKind_IMAGE_REL_ARM64_PAGEOFFSET_12A: + case HoleKind_IMAGE_REL_ARM64_PAGEOFFSET_12L: + case HoleKind_R_AARCH64_LD64_GOT_LO12_NC: // 12-bit low part of an absolute address. Pairs nicely with // ARM64_RELOC_GOT_LOAD_PAGE21 (above). assert(IS_AARCH64_LDR_OR_STR(*loc32) || IS_AARCH64_ADD_OR_SUB(*loc32)); @@ -285,7 +383,7 @@ patch(char *base, const Stencil *stencil, uint64_t *patches) } static void -copy_and_patch(char *base, const Stencil *stencil, uint64_t *patches) +copy_and_patch(unsigned char *base, const Stencil *stencil, uint64_t *patches) { memcpy(base, stencil->body, stencil->body_size); patch(base, stencil, patches); @@ -294,8 +392,8 @@ copy_and_patch(char *base, const Stencil *stencil, uint64_t *patches) static void emit(const StencilGroup *group, uint64_t patches[]) { - copy_and_patch((char *)patches[HoleValue_CODE], &group->code, patches); - copy_and_patch((char *)patches[HoleValue_DATA], &group->data, patches); + copy_and_patch((unsigned char *)patches[HoleValue_DATA], &group->data, patches); + copy_and_patch((unsigned char *)patches[HoleValue_CODE], &group->code, patches); } // Compiles executor in-place. Don't forget to call _PyJIT_Free later! @@ -316,14 +414,14 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction *trace, size assert((page_size & (page_size - 1)) == 0); code_size += page_size - (code_size & (page_size - 1)); data_size += page_size - (data_size & (page_size - 1)); - char *memory = jit_alloc(code_size + data_size); + unsigned char *memory = jit_alloc(code_size + data_size); if (memory == NULL) { return -1; } // Loop again to emit the code: - char *code = memory; - char *data = memory + code_size; - char *top = code; + unsigned char *code = memory; + unsigned char *data = memory + code_size; + unsigned char *top = code; if (trace[0].opcode == _START_EXECUTOR) { // Don't want to execute this more than once: top += stencil_groups[_START_EXECUTOR].code.body_size; @@ -360,7 +458,7 @@ _PyJIT_Compile(_PyExecutorObject *executor, const _PyUOpInstruction *trace, size void _PyJIT_Free(_PyExecutorObject *executor) { - char *memory = (char *)executor->jit_code; + unsigned char *memory = (unsigned char *)executor->jit_code; size_t size = executor->jit_size; if (memory) { executor->jit_code = NULL; diff --git a/Python/lock.c b/Python/lock.c index 5fa8bf78da23808..7d1ead585dee6ce 100644 --- a/Python/lock.c +++ b/Python/lock.c @@ -249,6 +249,13 @@ _PyRawMutex_UnlockSlow(_PyRawMutex *m) } } +int +_PyEvent_IsSet(PyEvent *evt) +{ + uint8_t v = _Py_atomic_load_uint8(&evt->v); + return v == _Py_LOCKED; +} + void _PyEvent_Notify(PyEvent *evt) { diff --git a/Python/opcode_targets.h b/Python/opcode_targets.h index e664e638bdb7490..4061ba33cea53ea 100644 --- a/Python/opcode_targets.h +++ b/Python/opcode_targets.h @@ -182,6 +182,8 @@ static void *opcode_targets[256] = { &&TARGET_COMPARE_OP_FLOAT, &&TARGET_COMPARE_OP_INT, &&TARGET_COMPARE_OP_STR, + &&TARGET_CONTAINS_OP_DICT, + &&TARGET_CONTAINS_OP_SET, &&TARGET_FOR_ITER_GEN, &&TARGET_FOR_ITER_LIST, &&TARGET_FOR_ITER_RANGE, @@ -233,8 +235,6 @@ static void *opcode_targets[256] = { &&_unknown_opcode, &&_unknown_opcode, &&_unknown_opcode, - &&_unknown_opcode, - &&_unknown_opcode, &&TARGET_INSTRUMENTED_RESUME, &&TARGET_INSTRUMENTED_END_FOR, &&TARGET_INSTRUMENTED_END_SEND, diff --git a/Python/optimizer.c b/Python/optimizer.c index 6b2ba3addefc95d..88c45f2e73c6821 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -899,7 +899,8 @@ make_executor_from_uops(_PyUOpInstruction *buffer, const _PyBloomFilter *depende uint32_t used[(UOP_MAX_TRACE_LENGTH + 31)/32] = { 0 }; int exit_count; int length = compute_used(buffer, used, &exit_count); - _PyExecutorObject *executor = allocate_executor(exit_count, length+1); + length += 1; // For _START_EXECUTOR + _PyExecutorObject *executor = allocate_executor(exit_count, length); if (executor == NULL) { return NULL; } @@ -909,7 +910,7 @@ make_executor_from_uops(_PyUOpInstruction *buffer, const _PyBloomFilter *depende executor->exits[i].temperature = 0; } int next_exit = exit_count-1; - _PyUOpInstruction *dest = (_PyUOpInstruction *)&executor->trace[length]; + _PyUOpInstruction *dest = (_PyUOpInstruction *)&executor->trace[length-1]; /* Scan backwards, so that we see the destinations of jumps before the jumps themselves. */ for (int i = UOP_MAX_TRACE_LENGTH-1; i >= 0; i--) { if (!BIT_IS_SET(used, i)) { @@ -957,7 +958,7 @@ make_executor_from_uops(_PyUOpInstruction *buffer, const _PyBloomFilter *depende #ifdef _Py_JIT executor->jit_code = NULL; executor->jit_size = 0; - if (_PyJIT_Compile(executor, executor->trace, length+1)) { + if (_PyJIT_Compile(executor, executor->trace, length)) { Py_DECREF(executor); return NULL; } @@ -1002,14 +1003,15 @@ uop_optimize( _PyBloomFilter dependencies; _Py_BloomFilter_Init(&dependencies); _PyUOpInstruction buffer[UOP_MAX_TRACE_LENGTH]; + OPT_STAT_INC(attempts); int err = translate_bytecode_to_trace(frame, instr, buffer, UOP_MAX_TRACE_LENGTH, &dependencies); if (err <= 0) { // Error or nothing translated return err; } OPT_STAT_INC(traces_created); - char *uop_optimize = Py_GETENV("PYTHONUOPSOPTIMIZE"); - if (uop_optimize == NULL || *uop_optimize > '0') { + char *env_var = Py_GETENV("PYTHON_UOPS_OPTIMIZE"); + if (env_var == NULL || *env_var == '\0' || *env_var > '0') { err = _Py_uop_analyze_and_optimize(frame, buffer, UOP_MAX_TRACE_LENGTH, curr_stackentries, &dependencies); @@ -1032,6 +1034,7 @@ uop_optimize( break; } assert(_PyOpcode_uop_name[buffer[pc].opcode]); + assert(strncmp(_PyOpcode_uop_name[buffer[pc].opcode], _PyOpcode_uop_name[opcode], strlen(_PyOpcode_uop_name[opcode])) == 0); } _PyExecutorObject *executor = make_executor_from_uops(buffer, &dependencies); if (executor == NULL) { @@ -1348,7 +1351,7 @@ _Py_Executor_DependsOn(_PyExecutorObject *executor, void *obj) * May cause other executors to be invalidated as well */ void -_Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj) +_Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is_invalidation) { _PyBloomFilter obj_filter; _Py_BloomFilter_Init(&obj_filter); @@ -1360,6 +1363,9 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj) _PyExecutorObject *next = exec->vm_data.links.next; if (bloom_filter_may_contain(&exec->vm_data.bloom, &obj_filter)) { _Py_ExecutorClear(exec); + if (is_invalidation) { + OPT_STAT_INC(executors_invalidated); + } } exec = next; } @@ -1367,7 +1373,7 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj) /* Invalidate all executors */ void -_Py_Executors_InvalidateAll(PyInterpreterState *interp) +_Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation) { while (interp->executor_list_head) { _PyExecutorObject *executor = interp->executor_list_head; @@ -1378,5 +1384,8 @@ _Py_Executors_InvalidateAll(PyInterpreterState *interp) else { _Py_ExecutorClear(executor); } + if (is_invalidation) { + OPT_STAT_INC(executors_invalidated); + } } } diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 9503dcc74656cd8..9fd4b1967ecc3bd 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -1,5 +1,5 @@ /* - * This file contains the support code for CPython's uops redundancy eliminator. + * This file contains the support code for CPython's uops optimizer. * It also performs some simple optimizations. * It performs a traditional data-flow analysis[1] over the trace of uops. * Using the information gained, it chooses to emit, or skip certain instructions @@ -33,16 +33,6 @@ #include #include -// Holds locals, stack, locals, stack ... co_consts (in that order) -#define MAX_ABSTRACT_INTERP_SIZE 4096 - -#define OVERALLOCATE_FACTOR 5 - -#define TY_ARENA_SIZE (UOP_MAX_TRACE_LENGTH * OVERALLOCATE_FACTOR) - -// Need extras for root frame and for overflow frame (see TRACE_STACK_PUSH()) -#define MAX_ABSTRACT_FRAME_DEPTH (TRACE_STACK_SIZE + 2) - #ifdef Py_DEBUG extern const char *_PyUOpName(int index); static const char *const DEBUG_ENV = "PYTHON_OPT_DEBUG"; @@ -61,318 +51,6 @@ #endif -// Flags for below. -#define KNOWN 1 << 0 -#define TRUE_CONST 1 << 1 -#define IS_NULL 1 << 2 -#define NOT_NULL 1 << 3 - -typedef struct { - int flags; - PyTypeObject *typ; - // constant propagated value (might be NULL) - PyObject *const_val; -} _Py_UOpsSymType; - - -typedef struct _Py_UOpsAbstractFrame { - // Max stacklen - int stack_len; - int locals_len; - - _Py_UOpsSymType **stack_pointer; - _Py_UOpsSymType **stack; - _Py_UOpsSymType **locals; -} _Py_UOpsAbstractFrame; - - -typedef struct ty_arena { - int ty_curr_number; - int ty_max_number; - _Py_UOpsSymType arena[TY_ARENA_SIZE]; -} ty_arena; - -// Tier 2 types meta interpreter -typedef struct _Py_UOpsAbstractInterpContext { - PyObject_HEAD - // The current "executing" frame. - _Py_UOpsAbstractFrame *frame; - _Py_UOpsAbstractFrame frames[MAX_ABSTRACT_FRAME_DEPTH]; - int curr_frame_depth; - - // Arena for the symbolic types. - ty_arena t_arena; - - _Py_UOpsSymType **n_consumed; - _Py_UOpsSymType **limit; - _Py_UOpsSymType *locals_and_stack[MAX_ABSTRACT_INTERP_SIZE]; -} _Py_UOpsAbstractInterpContext; - -static inline _Py_UOpsSymType* sym_new_unknown(_Py_UOpsAbstractInterpContext *ctx); - -// 0 on success, -1 on error. -static _Py_UOpsAbstractFrame * -ctx_frame_new( - _Py_UOpsAbstractInterpContext *ctx, - PyCodeObject *co, - _Py_UOpsSymType **localsplus_start, - int n_locals_already_filled, - int curr_stackentries -) -{ - assert(ctx->curr_frame_depth < MAX_ABSTRACT_FRAME_DEPTH); - _Py_UOpsAbstractFrame *frame = &ctx->frames[ctx->curr_frame_depth]; - - frame->stack_len = co->co_stacksize; - frame->locals_len = co->co_nlocalsplus; - - frame->locals = localsplus_start; - frame->stack = frame->locals + co->co_nlocalsplus; - frame->stack_pointer = frame->stack + curr_stackentries; - ctx->n_consumed = localsplus_start + (co->co_nlocalsplus + co->co_stacksize); - if (ctx->n_consumed >= ctx->limit) { - return NULL; - } - - - // Initialize with the initial state of all local variables - for (int i = n_locals_already_filled; i < co->co_nlocalsplus; i++) { - _Py_UOpsSymType *local = sym_new_unknown(ctx); - if (local == NULL) { - return NULL; - } - frame->locals[i] = local; - } - - - // Initialize the stack as well - for (int i = 0; i < curr_stackentries; i++) { - _Py_UOpsSymType *stackvar = sym_new_unknown(ctx); - if (stackvar == NULL) { - return NULL; - } - frame->stack[i] = stackvar; - } - - return frame; -} - -static void -abstractcontext_fini(_Py_UOpsAbstractInterpContext *ctx) -{ - if (ctx == NULL) { - return; - } - ctx->curr_frame_depth = 0; - int tys = ctx->t_arena.ty_curr_number; - for (int i = 0; i < tys; i++) { - Py_CLEAR(ctx->t_arena.arena[i].const_val); - } -} - -static int -abstractcontext_init( - _Py_UOpsAbstractInterpContext *ctx, - PyCodeObject *co, - int curr_stacklen, - int ir_entries -) -{ - ctx->limit = ctx->locals_and_stack + MAX_ABSTRACT_INTERP_SIZE; - ctx->n_consumed = ctx->locals_and_stack; -#ifdef Py_DEBUG // Aids debugging a little. There should never be NULL in the abstract interpreter. - for (int i = 0 ; i < MAX_ABSTRACT_INTERP_SIZE; i++) { - ctx->locals_and_stack[i] = NULL; - } -#endif - - // Setup the arena for sym expressions. - ctx->t_arena.ty_curr_number = 0; - ctx->t_arena.ty_max_number = TY_ARENA_SIZE; - - // Frame setup - ctx->curr_frame_depth = 0; - _Py_UOpsAbstractFrame *frame = ctx_frame_new(ctx, co, ctx->n_consumed, 0, curr_stacklen); - if (frame == NULL) { - return -1; - } - ctx->curr_frame_depth++; - ctx->frame = frame; - return 0; -} - - -static int -ctx_frame_pop( - _Py_UOpsAbstractInterpContext *ctx -) -{ - _Py_UOpsAbstractFrame *frame = ctx->frame; - - ctx->n_consumed = frame->locals; - ctx->curr_frame_depth--; - assert(ctx->curr_frame_depth >= 1); - ctx->frame = &ctx->frames[ctx->curr_frame_depth - 1]; - - return 0; -} - - -// Takes a borrowed reference to const_val, turns that into a strong reference. -static _Py_UOpsSymType* -sym_new(_Py_UOpsAbstractInterpContext *ctx, - PyObject *const_val) -{ - _Py_UOpsSymType *self = &ctx->t_arena.arena[ctx->t_arena.ty_curr_number]; - if (ctx->t_arena.ty_curr_number >= ctx->t_arena.ty_max_number) { - OPT_STAT_INC(optimizer_failure_reason_no_memory); - DPRINTF(1, "out of space for symbolic expression type\n"); - return NULL; - } - ctx->t_arena.ty_curr_number++; - self->const_val = NULL; - self->typ = NULL; - self->flags = 0; - - if (const_val != NULL) { - self->const_val = Py_NewRef(const_val); - } - - return self; -} - -static inline void -sym_set_flag(_Py_UOpsSymType *sym, int flag) -{ - sym->flags |= flag; -} - -static inline bool -sym_has_flag(_Py_UOpsSymType *sym, int flag) -{ - return (sym->flags & flag) != 0; -} - -static inline bool -sym_is_known(_Py_UOpsSymType *sym) -{ - return sym_has_flag(sym, KNOWN); -} - -static inline bool -sym_is_not_null(_Py_UOpsSymType *sym) -{ - return (sym->flags & (IS_NULL | NOT_NULL)) == NOT_NULL; -} - -static inline bool -sym_is_null(_Py_UOpsSymType *sym) -{ - return (sym->flags & (IS_NULL | NOT_NULL)) == IS_NULL; -} - -static inline bool -sym_is_const(_Py_UOpsSymType *sym) -{ - return (sym->flags & TRUE_CONST) != 0; -} - -static inline PyObject * -sym_get_const(_Py_UOpsSymType *sym) -{ - assert(sym_is_const(sym)); - assert(sym->const_val); - return sym->const_val; -} - -static inline void -sym_set_type(_Py_UOpsSymType *sym, PyTypeObject *tp) -{ - assert(PyType_Check(tp)); - sym->typ = tp; - sym_set_flag(sym, KNOWN); - sym_set_flag(sym, NOT_NULL); -} - -static inline void -sym_set_null(_Py_UOpsSymType *sym) -{ - sym_set_flag(sym, IS_NULL); - sym_set_flag(sym, KNOWN); -} - - -static inline _Py_UOpsSymType* -sym_new_unknown(_Py_UOpsAbstractInterpContext *ctx) -{ - return sym_new(ctx,NULL); -} - -static inline _Py_UOpsSymType* -sym_new_known_notnull(_Py_UOpsAbstractInterpContext *ctx) -{ - _Py_UOpsSymType *res = sym_new_unknown(ctx); - if (res == NULL) { - return NULL; - } - sym_set_flag(res, KNOWN); - sym_set_flag(res, NOT_NULL); - return res; -} - -static inline _Py_UOpsSymType* -sym_new_known_type(_Py_UOpsAbstractInterpContext *ctx, - PyTypeObject *typ) -{ - _Py_UOpsSymType *res = sym_new(ctx,NULL); - if (res == NULL) { - return NULL; - } - sym_set_type(res, typ); - return res; -} - -// Takes a borrowed reference to const_val. -static inline _Py_UOpsSymType* -sym_new_const(_Py_UOpsAbstractInterpContext *ctx, PyObject *const_val) -{ - assert(const_val != NULL); - _Py_UOpsSymType *temp = sym_new( - ctx, - const_val - ); - if (temp == NULL) { - return NULL; - } - sym_set_type(temp, Py_TYPE(const_val)); - sym_set_flag(temp, TRUE_CONST); - sym_set_flag(temp, KNOWN); - sym_set_flag(temp, NOT_NULL); - return temp; -} - -static _Py_UOpsSymType* -sym_new_null(_Py_UOpsAbstractInterpContext *ctx) -{ - _Py_UOpsSymType *null_sym = sym_new_unknown(ctx); - if (null_sym == NULL) { - return NULL; - } - sym_set_null(null_sym); - return null_sym; -} - - -static inline bool -sym_matches_type(_Py_UOpsSymType *sym, PyTypeObject *typ) -{ - assert(typ == NULL || PyType_Check(typ)); - if (!sym_has_flag(sym, KNOWN)) { - return false; - } - return sym->typ == typ; -} - static inline bool op_is_end(uint32_t opcode) @@ -405,7 +83,7 @@ globals_watcher_callback(PyDict_WatchEvent event, PyObject* dict, { RARE_EVENT_STAT_INC(watched_globals_modification); assert(get_mutations(dict) < _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS); - _Py_Executors_InvalidateDependency(_PyInterpreterState_GET(), dict); + _Py_Executors_InvalidateDependency(_PyInterpreterState_GET(), dict, 1); increment_mutations(dict); PyDict_Unwatch(GLOBALS_WATCHER_ID, dict); return 0; @@ -463,9 +141,11 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, return 1; } PyObject *globals = frame->f_globals; - assert(PyFunction_Check(((PyFunctionObject *)frame->f_funcobj))); - assert(((PyFunctionObject *)frame->f_funcobj)->func_builtins == builtins); - assert(((PyFunctionObject *)frame->f_funcobj)->func_globals == globals); + PyFunctionObject *function = (PyFunctionObject *)frame->f_funcobj; + assert(PyFunction_Check(function)); + assert(function->func_builtins == builtins); + assert(function->func_globals == globals); + uint32_t function_version = _PyFunction_GetVersionForCurrentState(function); /* In order to treat globals as constants, we need to * know that the globals dict is the one we expected, and * that it hasn't changed @@ -476,10 +156,10 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, /* These values represent stacks of booleans (one bool per bit). * Pushing a frame shifts left, popping a frame shifts right. */ - uint32_t builtins_checked = 0; + uint32_t function_checked = 0; uint32_t builtins_watched = 0; - uint32_t globals_checked = 0; uint32_t globals_watched = 0; + uint32_t prechecked_function_version = 0; if (interp->dict_state.watchers[GLOBALS_WATCHER_ID] == NULL) { interp->dict_state.watchers[GLOBALS_WATCHER_ID] = globals_watcher_callback; } @@ -498,13 +178,13 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, PyDict_Watch(BUILTINS_WATCHER_ID, builtins); builtins_watched |= 1; } - if (builtins_checked & 1) { + if (function_checked & 1) { buffer[pc].opcode = NOP; } else { - buffer[pc].opcode = _CHECK_BUILTINS; - buffer[pc].operand = (uintptr_t)builtins; - builtins_checked |= 1; + buffer[pc].opcode = _CHECK_FUNCTION; + buffer[pc].operand = function_version; + function_checked |= 1; } break; case _GUARD_GLOBALS_VERSION: @@ -520,36 +200,40 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, _Py_BloomFilter_Add(dependencies, globals); globals_watched |= 1; } - if (globals_checked & 1) { + if (function_checked & 1) { buffer[pc].opcode = NOP; } else { - buffer[pc].opcode = _CHECK_GLOBALS; - buffer[pc].operand = (uintptr_t)globals; - globals_checked |= 1; + buffer[pc].opcode = _CHECK_FUNCTION; + buffer[pc].operand = function_version; + function_checked |= 1; } break; case _LOAD_GLOBAL_BUILTINS: - if (globals_checked & builtins_checked & globals_watched & builtins_watched & 1) { + if (function_checked & globals_watched & builtins_watched & 1) { convert_global_to_const(inst, builtins); } break; case _LOAD_GLOBAL_MODULE: - if (globals_checked & globals_watched & 1) { + if (function_checked & globals_watched & 1) { convert_global_to_const(inst, globals); } break; case _PUSH_FRAME: { - globals_checked <<= 1; - globals_watched <<= 1; - builtins_checked <<= 1; builtins_watched <<= 1; + globals_watched <<= 1; + function_checked <<= 1; PyFunctionObject *func = (PyFunctionObject *)buffer[pc].operand; if (func == NULL) { return 1; } assert(PyFunction_Check(func)); + function_version = func->func_version; + if (prechecked_function_version == function_version) { + function_checked |= 1; + } + prechecked_function_version = 0; globals = func->func_globals; builtins = func->func_builtins; if (builtins != interp->builtins) { @@ -559,16 +243,19 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, } case _POP_FRAME: { - globals_checked >>= 1; - globals_watched >>= 1; - builtins_checked >>= 1; builtins_watched >>= 1; + globals_watched >>= 1; + function_checked >>= 1; PyFunctionObject *func = (PyFunctionObject *)buffer[pc].operand; assert(PyFunction_Check(func)); + function_version = func->func_version; globals = func->func_globals; builtins = func->func_builtins; break; } + case _CHECK_FUNCTION_EXACT_ARGS: + prechecked_function_version = (uint32_t)buffer[pc].operand; + break; default: if (op_is_end(opcode)) { return 1; @@ -599,14 +286,67 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, #define _LOAD_ATTR_NOT_NULL \ do { \ - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); \ - OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); \ + OUT_OF_SPACE_IF_NULL(attr = _Py_uop_sym_new_not_null(ctx)); \ + OUT_OF_SPACE_IF_NULL(null = _Py_uop_sym_new_null(ctx)); \ } while (0); +/* Shortened forms for convenience, used in optimizer_bytecodes.c */ +#define sym_is_not_null _Py_uop_sym_is_not_null +#define sym_is_const _Py_uop_sym_is_const +#define sym_get_const _Py_uop_sym_get_const +#define sym_new_unknown _Py_uop_sym_new_unknown +#define sym_new_not_null _Py_uop_sym_new_not_null +#define sym_new_type _Py_uop_sym_new_type +#define sym_is_null _Py_uop_sym_is_null +#define sym_new_const _Py_uop_sym_new_const +#define sym_new_null _Py_uop_sym_new_null +#define sym_has_type _Py_uop_sym_has_type +#define sym_matches_type _Py_uop_sym_matches_type +#define sym_set_null _Py_uop_sym_set_null +#define sym_set_non_null _Py_uop_sym_set_non_null +#define sym_set_type _Py_uop_sym_set_type +#define sym_set_const _Py_uop_sym_set_const +#define sym_is_bottom _Py_uop_sym_is_bottom +#define sym_truthiness _Py_uop_sym_truthiness +#define frame_new _Py_uop_frame_new +#define frame_pop _Py_uop_frame_pop + +static int +optimize_to_bool( + _PyUOpInstruction *this_instr, + _Py_UOpsContext *ctx, + _Py_UopsSymbol *value, + _Py_UopsSymbol **result_ptr) +{ + if (sym_matches_type(value, &PyBool_Type)) { + REPLACE_OP(this_instr, _NOP, 0, 0); + *result_ptr = value; + return 1; + } + int truthiness = sym_truthiness(value); + if (truthiness >= 0) { + PyObject *load = truthiness ? Py_True : Py_False; + REPLACE_OP(this_instr, _POP_TOP_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)load); + *result_ptr = sym_new_const(ctx, load); + return 1; + } + return 0; +} + +static void +eliminate_pop_guard(_PyUOpInstruction *this_instr, bool exit) +{ + REPLACE_OP(this_instr, _POP_TOP, 0, 0); + if (exit) { + REPLACE_OP((this_instr+1), _EXIT_TRACE, 0, 0); + this_instr[1].target = this_instr->target; + } +} + /* 1 for success, 0 for not ready, cannot error at the moment. */ static int -uop_redundancy_eliminator( +optimize_uops( PyCodeObject *co, _PyUOpInstruction *trace, int trace_len, @@ -615,15 +355,18 @@ uop_redundancy_eliminator( ) { - _Py_UOpsAbstractInterpContext context; - _Py_UOpsAbstractInterpContext *ctx = &context; + _Py_UOpsContext context; + _Py_UOpsContext *ctx = &context; - if (abstractcontext_init( - ctx, - co, curr_stacklen, - trace_len) < 0) { + if (_Py_uop_abstractcontext_init(ctx) < 0) { goto out_of_space; } + _Py_UOpsAbstractFrame *frame = _Py_uop_frame_new(ctx, co, ctx->n_consumed, 0, curr_stacklen); + if (frame == NULL) { + return -1; + } + ctx->curr_frame_depth++; + ctx->frame = frame; for (_PyUOpInstruction *this_instr = trace; this_instr < trace + trace_len && !op_is_end(this_instr->opcode); @@ -632,13 +375,13 @@ uop_redundancy_eliminator( int oparg = this_instr->oparg; uint32_t opcode = this_instr->opcode; - _Py_UOpsSymType **stack_pointer = ctx->frame->stack_pointer; + _Py_UopsSymbol **stack_pointer = ctx->frame->stack_pointer; DPRINTF(3, "Abstract interpreting %s:%d ", _PyUOpName(opcode), oparg); switch (opcode) { -#include "tier2_redundancy_eliminator_cases.c.h" +#include "optimizer_cases.c.h" default: DPRINTF(1, "Unknown opcode in abstract interpreter\n"); @@ -650,17 +393,26 @@ uop_redundancy_eliminator( assert(STACK_LEVEL() >= 0); } - abstractcontext_fini(ctx); + _Py_uop_abstractcontext_fini(ctx); return 1; out_of_space: DPRINTF(1, "Out of space in abstract interpreter\n"); - abstractcontext_fini(ctx); + _Py_uop_abstractcontext_fini(ctx); return 0; error: DPRINTF(1, "Encountered error in abstract interpreter\n"); - abstractcontext_fini(ctx); + _Py_uop_abstractcontext_fini(ctx); + return 0; + +hit_bottom: + // Attempted to push a "bottom" (contradition) symbol onto the stack. + // This means that the abstract interpreter has hit unreachable code. + // We *could* generate an _EXIT_TRACE or _FATAL_ERROR here, but it's + // simpler to just admit failure and not create the executor. + DPRINTF(1, "Hit bottom in abstract interpreter\n"); + _Py_uop_abstractcontext_fini(ctx); return 0; } @@ -678,7 +430,7 @@ remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size) int opcode = buffer[pc].opcode; switch (opcode) { case _SET_IP: - buffer[pc].opcode = NOP; + buffer[pc].opcode = _NOP; last_set_ip = pc; break; case _CHECK_VALIDITY: @@ -686,7 +438,7 @@ remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size) may_have_escaped = false; } else { - buffer[pc].opcode = NOP; + buffer[pc].opcode = _NOP; } break; case _CHECK_VALIDITY_AND_SET_IP: @@ -695,7 +447,7 @@ remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size) buffer[pc].opcode = _CHECK_VALIDITY; } else { - buffer[pc].opcode = NOP; + buffer[pc].opcode = _NOP; } last_set_ip = pc; break; @@ -711,7 +463,10 @@ remove_unneeded_uops(_PyUOpInstruction *buffer, int buffer_size) last->opcode == _COPY ) { last->opcode = _NOP; - buffer[pc].opcode = NOP; + buffer[pc].opcode = _NOP; + } + if (last->opcode == _REPLACE_WITH_TRUE) { + last->opcode = _NOP; } break; } @@ -810,7 +565,7 @@ _Py_uop_analyze_and_optimize( peephole_opt(frame, buffer, buffer_size); - err = uop_redundancy_eliminator( + err = optimize_uops( (PyCodeObject *)frame->f_executable, buffer, buffer_size, curr_stacklen, dependencies); diff --git a/Python/tier2_redundancy_eliminator_bytecodes.c b/Python/optimizer_bytecodes.c similarity index 52% rename from Python/tier2_redundancy_eliminator_bytecodes.c rename to Python/optimizer_bytecodes.c index ef7b43d53539ce6..54abbcd74d79340 100644 --- a/Python/tier2_redundancy_eliminator_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -1,29 +1,60 @@ #include "Python.h" +#include "pycore_optimizer.h" #include "pycore_uops.h" #include "pycore_uop_ids.h" #include "internal/pycore_moduleobject.h" #define op(name, ...) /* NAME is ignored */ -typedef struct _Py_UOpsSymType _Py_UOpsSymType; -typedef struct _Py_UOpsAbstractInterpContext _Py_UOpsAbstractInterpContext; +typedef struct _Py_UopsSymbol _Py_UopsSymbol; +typedef struct _Py_UOpsContext _Py_UOpsContext; typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame; +/* Shortened forms for convenience */ +#define sym_is_not_null _Py_uop_sym_is_not_null +#define sym_is_const _Py_uop_sym_is_const +#define sym_get_const _Py_uop_sym_get_const +#define sym_new_unknown _Py_uop_sym_new_unknown +#define sym_new_not_null _Py_uop_sym_new_not_null +#define sym_new_type _Py_uop_sym_new_type +#define sym_is_null _Py_uop_sym_is_null +#define sym_new_const _Py_uop_sym_new_const +#define sym_new_null _Py_uop_sym_new_null +#define sym_matches_type _Py_uop_sym_matches_type +#define sym_has_type _Py_uop_sym_has_type +#define sym_set_null _Py_uop_sym_set_null +#define sym_set_non_null _Py_uop_sym_set_non_null +#define sym_set_type _Py_uop_sym_set_type +#define sym_set_const _Py_uop_sym_set_const +#define sym_is_bottom _Py_uop_sym_is_bottom +#define frame_new _Py_uop_frame_new +#define frame_pop _Py_uop_frame_pop + +extern int +optimize_to_bool( + _PyUOpInstruction *this_instr, + _Py_UOpsContext *ctx, + _Py_UopsSymbol *value, + _Py_UopsSymbol **result_ptr); + +extern void +eliminate_pop_guard(_PyUOpInstruction *this_instr, bool exit) + static int dummy_func(void) { PyCodeObject *code; int oparg; - _Py_UOpsSymType *flag; - _Py_UOpsSymType *left; - _Py_UOpsSymType *right; - _Py_UOpsSymType *value; - _Py_UOpsSymType *res; - _Py_UOpsSymType *iter; - _Py_UOpsSymType *top; - _Py_UOpsSymType *bottom; + _Py_UopsSymbol *flag; + _Py_UopsSymbol *left; + _Py_UopsSymbol *right; + _Py_UopsSymbol *value; + _Py_UopsSymbol *res; + _Py_UopsSymbol *iter; + _Py_UopsSymbol *top; + _Py_UopsSymbol *bottom; _Py_UOpsAbstractFrame *frame; - _Py_UOpsAbstractInterpContext *ctx; + _Py_UOpsContext *ctx; _PyUOpInstruction *this_instr; _PyBloomFilter *dependencies; int modified; @@ -44,7 +75,7 @@ dummy_func(void) { op(_LOAD_FAST_AND_CLEAR, (-- value)) { value = GETLOCAL(oparg); - _Py_UOpsSymType *temp; + _Py_UopsSymbol *temp; OUT_OF_SPACE_IF_NULL(temp = sym_new_null(ctx)); GETLOCAL(oparg) = temp; } @@ -65,8 +96,12 @@ dummy_func(void) { sym_matches_type(right, &PyLong_Type)) { REPLACE_OP(this_instr, _NOP, 0, 0); } - sym_set_type(left, &PyLong_Type); - sym_set_type(right, &PyLong_Type); + if (!sym_set_type(left, &PyLong_Type)) { + goto hit_bottom; + } + if (!sym_set_type(right, &PyLong_Type)) { + goto hit_bottom; + } } op(_GUARD_BOTH_FLOAT, (left, right -- left, right)) { @@ -74,8 +109,12 @@ dummy_func(void) { sym_matches_type(right, &PyFloat_Type)) { REPLACE_OP(this_instr, _NOP, 0 ,0); } - sym_set_type(left, &PyFloat_Type); - sym_set_type(right, &PyFloat_Type); + if (!sym_set_type(left, &PyFloat_Type)) { + goto hit_bottom; + } + if (!sym_set_type(right, &PyFloat_Type)) { + goto hit_bottom; + } } op(_GUARD_BOTH_UNICODE, (left, right -- left, right)) { @@ -83,12 +122,18 @@ dummy_func(void) { sym_matches_type(right, &PyUnicode_Type)) { REPLACE_OP(this_instr, _NOP, 0 ,0); } - sym_set_type(left, &PyUnicode_Type); - sym_set_type(right, &PyUnicode_Type); + if (!sym_set_type(left, &PyUnicode_Type)) { + goto hit_bottom; + } + if (!sym_set_type(right, &PyUnicode_Type)) { + goto hit_bottom; + } } op(_BINARY_OP_ADD_INT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) + { assert(PyLong_CheckExact(sym_get_const(left))); assert(PyLong_CheckExact(sym_get_const(right))); PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(left), @@ -96,17 +141,21 @@ dummy_func(void) { if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyLong_Type)); } } op(_BINARY_OP_SUBTRACT_INT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) + { assert(PyLong_CheckExact(sym_get_const(left))); assert(PyLong_CheckExact(sym_get_const(right))); PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(left), @@ -114,17 +163,21 @@ dummy_func(void) { if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyLong_Type)); } } op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) + { assert(PyLong_CheckExact(sym_get_const(left))); assert(PyLong_CheckExact(sym_get_const(right))); PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(left), @@ -132,17 +185,21 @@ dummy_func(void) { if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyLong_Type)); } } op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) + { assert(PyFloat_CheckExact(sym_get_const(left))); assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( @@ -152,16 +209,20 @@ dummy_func(void) { goto error; } res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyFloat_Type)); } } op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) + { assert(PyFloat_CheckExact(sym_get_const(left))); assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( @@ -171,16 +232,20 @@ dummy_func(void) { goto error; } res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyFloat_Type)); } } op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) + { assert(PyFloat_CheckExact(sym_get_const(left))); assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( @@ -190,14 +255,143 @@ dummy_func(void) { goto error; } res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyFloat_Type)); + } + } + + op(_BINARY_OP_ADD_UNICODE, (left, right -- res)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyUnicode_Type) && sym_matches_type(right, &PyUnicode_Type)) { + PyObject *temp = PyUnicode_Concat(sym_get_const(left), sym_get_const(right)); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); + } + else { + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyUnicode_Type)); + } + } + + op(_TO_BOOL, (value -- res)) { + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + res = sym_new_type(ctx, &PyBool_Type); + OUT_OF_SPACE_IF_NULL(res); } } + op(_TO_BOOL_BOOL, (value -- res)) { + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + if(!sym_set_type(value, &PyBool_Type)) { + goto hit_bottom; + } + res = value; + } + } + + op(_TO_BOOL_INT, (value -- res)) { + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + if(!sym_set_type(value, &PyLong_Type)) { + goto hit_bottom; + } + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); + } + } + + op(_TO_BOOL_LIST, (value -- res)) { + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + if(!sym_set_type(value, &PyList_Type)) { + goto hit_bottom; + } + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); + } + } + + op(_TO_BOOL_NONE, (value -- res)) { + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + if (!sym_set_const(value, Py_None)) { + goto hit_bottom; + } + OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, Py_False)); + } + } + + op(_TO_BOOL_STR, (value -- res)) { + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); + if(!sym_set_type(value, &PyUnicode_Type)) { + goto hit_bottom; + } + } + } + + op(_COMPARE_OP, (left, right -- res)) { + (void)left; + (void)right; + if (oparg & 16) { + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); + } + else { + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_not_null(ctx)); + } + } + + op(_COMPARE_OP_INT, (left, right -- res)) { + (void)left; + (void)right; + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); + } + + op(_COMPARE_OP_FLOAT, (left, right -- res)) { + (void)left; + (void)right; + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); + } + + op(_COMPARE_OP_STR, (left, right -- res)) { + (void)left; + (void)right; + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); + } + + op(_IS_OP, (left, right -- res)) { + (void)left; + (void)right; + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); + } + + op(_CONTAINS_OP, (left, right -- res)) { + (void)left; + (void)right; + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); + } + op(_LOAD_CONST, (-- value)) { // There should be no LOAD_CONST. It should be all // replaced by peephole_opt. @@ -255,6 +449,14 @@ dummy_func(void) { } } + op(_LOAD_ATTR, (owner -- attr, self_or_null if (oparg & 1))) { + (void)owner; + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); + if (oparg & 1) { + OUT_OF_SPACE_IF_NULL(self_or_null = sym_new_unknown(ctx)); + } + } + op(_LOAD_ATTR_MODULE, (index/1, owner -- attr, null if (oparg & 1))) { (void)index; OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); @@ -273,7 +475,7 @@ dummy_func(void) { } if (attr == NULL) { /* No conversion made. We don't know what `attr` is. */ - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); } } @@ -296,35 +498,44 @@ dummy_func(void) { } op(_LOAD_ATTR_METHOD_WITH_VALUES, (descr/4, owner -- attr, self if (1))) { - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); - OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + (void)descr; + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); + self = owner; } op(_LOAD_ATTR_METHOD_NO_DICT, (descr/4, owner -- attr, self if (1))) { - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); - OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + (void)descr; + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); + self = owner; } op(_LOAD_ATTR_METHOD_LAZY_DICT, (descr/4, owner -- attr, self if (1))) { - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); - OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + (void)descr; + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); + self = owner; } op(_INIT_CALL_BOUND_METHOD_EXACT_ARGS, (callable, unused, unused[oparg] -- func, self, unused[oparg])) { - OUT_OF_SPACE_IF_NULL(func = sym_new_known_notnull(ctx)); - OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + (void)callable; + OUT_OF_SPACE_IF_NULL(func = sym_new_not_null(ctx)); + OUT_OF_SPACE_IF_NULL(self = sym_new_not_null(ctx)); } - op(_CHECK_FUNCTION_EXACT_ARGS, (func_version/2, callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) { - sym_set_type(callable, &PyFunction_Type); + if (!sym_set_type(callable, &PyFunction_Type)) { + goto hit_bottom; + } (void)self_or_null; (void)func_version; } op(_CHECK_CALL_BOUND_METHOD_EXACT_ARGS, (callable, null, unused[oparg] -- callable, null, unused[oparg])) { - sym_set_null(null); - sym_set_type(callable, &PyMethod_Type); + if (!sym_set_null(null)) { + goto hit_bottom; + } + if (!sym_set_type(callable, &PyMethod_Type)) { + goto hit_bottom; + } } op(_INIT_CALL_PY_EXACT_ARGS, (callable, self_or_null, args[oparg] -- new_frame: _Py_UOpsAbstractFrame *)) { @@ -346,23 +557,23 @@ dummy_func(void) { argcount++; } - _Py_UOpsSymType **localsplus_start = ctx->n_consumed; + _Py_UopsSymbol **localsplus_start = ctx->n_consumed; int n_locals_already_filled = 0; // Can determine statically, so we interleave the new locals // and make the current stack the new locals. // This also sets up for true call inlining. - if (sym_is_known(self_or_null)) { + if (sym_is_null(self_or_null) || sym_is_not_null(self_or_null)) { localsplus_start = args; n_locals_already_filled = argcount; } OUT_OF_SPACE_IF_NULL(new_frame = - ctx_frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0)); + frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0)); } op(_POP_FRAME, (retval -- res)) { SYNC_SP(); ctx->frame->stack_pointer = stack_pointer; - ctx_frame_pop(ctx); + frame_pop(ctx); stack_pointer = ctx->frame->stack_pointer; res = retval; } @@ -393,11 +604,49 @@ dummy_func(void) { } op(_ITER_NEXT_RANGE, (iter -- iter, next)) { - OUT_OF_SPACE_IF_NULL(next = sym_new_known_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(next = sym_new_type(ctx, &PyLong_Type)); (void)iter; } + op(_GUARD_IS_TRUE_POP, (flag -- )) { + if (sym_is_const(flag)) { + PyObject *value = sym_get_const(flag); + assert(value != NULL); + eliminate_pop_guard(this_instr, value != Py_True); + } + } + + op(_GUARD_IS_FALSE_POP, (flag -- )) { + if (sym_is_const(flag)) { + PyObject *value = sym_get_const(flag); + assert(value != NULL); + eliminate_pop_guard(this_instr, value != Py_False); + } + } + op(_GUARD_IS_NONE_POP, (flag -- )) { + if (sym_is_const(flag)) { + PyObject *value = sym_get_const(flag); + assert(value != NULL); + eliminate_pop_guard(this_instr, !Py_IsNone(value)); + } + else if (sym_has_type(flag)) { + assert(!sym_matches_type(flag, &_PyNone_Type)); + eliminate_pop_guard(this_instr, true); + } + } + + op(_GUARD_IS_NOT_NONE_POP, (flag -- )) { + if (sym_is_const(flag)) { + PyObject *value = sym_get_const(flag); + assert(value != NULL); + eliminate_pop_guard(this_instr, Py_IsNone(value)); + } + else if (sym_has_type(flag)) { + assert(!sym_matches_type(flag, &_PyNone_Type)); + eliminate_pop_guard(this_instr, false); + } + } // END BYTECODES // diff --git a/Python/tier2_redundancy_eliminator_cases.c.h b/Python/optimizer_cases.c.h similarity index 64% rename from Python/tier2_redundancy_eliminator_cases.c.h rename to Python/optimizer_cases.c.h index ca9b5953d21012f..cf36f1ba792775e 100644 --- a/Python/tier2_redundancy_eliminator_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -1,6 +1,6 @@ -// This file is generated by Tools/cases_generator/tier2_abstract_generator.py +// This file is generated by Tools/cases_generator/optimizer_generator.py // from: -// Python/tier2_redundancy_eliminator_bytecodes.c +// Python/optimizer_bytecodes.c // Do not edit! case _NOP: { @@ -14,7 +14,7 @@ /* _INSTRUMENTED_RESUME is not a viable micro-op for tier 2 */ case _LOAD_FAST_CHECK: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; value = GETLOCAL(oparg); // We guarantee this will error - just bail and don't optimize it. if (sym_is_null(value)) { @@ -26,7 +26,7 @@ } case _LOAD_FAST: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; value = GETLOCAL(oparg); stack_pointer[0] = value; stack_pointer += 1; @@ -34,9 +34,9 @@ } case _LOAD_FAST_AND_CLEAR: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; value = GETLOCAL(oparg); - _Py_UOpsSymType *temp; + _Py_UopsSymbol *temp; OUT_OF_SPACE_IF_NULL(temp = sym_new_null(ctx)); GETLOCAL(oparg) = temp; stack_pointer[0] = value; @@ -45,7 +45,7 @@ } case _LOAD_CONST: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; // There should be no LOAD_CONST. It should be all // replaced by peephole_opt. Py_UNREACHABLE(); @@ -55,7 +55,7 @@ } case _STORE_FAST: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; value = stack_pointer[-1]; GETLOCAL(oparg) = value; stack_pointer += -1; @@ -68,7 +68,7 @@ } case _PUSH_NULL: { - _Py_UOpsSymType *res; + _Py_UopsSymbol *res; res = sym_new_null(ctx); if (res == NULL) { goto out_of_space; @@ -79,8 +79,8 @@ } case _END_SEND: { - _Py_UOpsSymType *value; - value = sym_new_unknown(ctx); + _Py_UopsSymbol *value; + value = sym_new_not_null(ctx); if (value == NULL) goto out_of_space; stack_pointer[-2] = value; stack_pointer += -1; @@ -88,102 +88,164 @@ } case _UNARY_NEGATIVE: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; } case _UNARY_NOT: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; } case _TO_BOOL: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + _Py_UopsSymbol *value; + _Py_UopsSymbol *res; + value = stack_pointer[-1]; + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + res = sym_new_type(ctx, &PyBool_Type); + OUT_OF_SPACE_IF_NULL(res); + } stack_pointer[-1] = res; break; } case _TO_BOOL_BOOL: { + _Py_UopsSymbol *value; + _Py_UopsSymbol *res; + value = stack_pointer[-1]; + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + if(!sym_set_type(value, &PyBool_Type)) { + goto hit_bottom; + } + res = value; + } + stack_pointer[-1] = res; break; } case _TO_BOOL_INT: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + _Py_UopsSymbol *value; + _Py_UopsSymbol *res; + value = stack_pointer[-1]; + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + if(!sym_set_type(value, &PyLong_Type)) { + goto hit_bottom; + } + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); + } stack_pointer[-1] = res; break; } case _TO_BOOL_LIST: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + _Py_UopsSymbol *value; + _Py_UopsSymbol *res; + value = stack_pointer[-1]; + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + if(!sym_set_type(value, &PyList_Type)) { + goto hit_bottom; + } + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); + } stack_pointer[-1] = res; break; } case _TO_BOOL_NONE: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + _Py_UopsSymbol *value; + _Py_UopsSymbol *res; + value = stack_pointer[-1]; + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + if (!sym_set_const(value, Py_None)) { + goto hit_bottom; + } + OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, Py_False)); + } stack_pointer[-1] = res; break; } case _TO_BOOL_STR: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + _Py_UopsSymbol *value; + _Py_UopsSymbol *res; + value = stack_pointer[-1]; + if (optimize_to_bool(this_instr, ctx, value, &res)) { + OUT_OF_SPACE_IF_NULL(res); + } + else { + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); + if(!sym_set_type(value, &PyUnicode_Type)) { + goto hit_bottom; + } + } stack_pointer[-1] = res; break; } - case _TO_BOOL_ALWAYS_TRUE: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + case _REPLACE_WITH_TRUE: { + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; } case _UNARY_INVERT: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; } case _GUARD_BOTH_INT: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; right = stack_pointer[-1]; left = stack_pointer[-2]; if (sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) { REPLACE_OP(this_instr, _NOP, 0, 0); } - sym_set_type(left, &PyLong_Type); - sym_set_type(right, &PyLong_Type); + if (!sym_set_type(left, &PyLong_Type)) { + goto hit_bottom; + } + if (!sym_set_type(right, &PyLong_Type)) { + goto hit_bottom; + } break; } case _BINARY_OP_MULTIPLY_INT: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; - _Py_UOpsSymType *res; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) + { assert(PyLong_CheckExact(sym_get_const(left))); assert(PyLong_CheckExact(sym_get_const(right))); PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(left), @@ -191,12 +253,14 @@ if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyLong_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -204,12 +268,14 @@ } case _BINARY_OP_ADD_INT: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; - _Py_UOpsSymType *res; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) + { assert(PyLong_CheckExact(sym_get_const(left))); assert(PyLong_CheckExact(sym_get_const(right))); PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(left), @@ -217,12 +283,14 @@ if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyLong_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -230,12 +298,14 @@ } case _BINARY_OP_SUBTRACT_INT: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; - _Py_UOpsSymType *res; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyLong_Type) && sym_matches_type(right, &PyLong_Type)) + { assert(PyLong_CheckExact(sym_get_const(left))); assert(PyLong_CheckExact(sym_get_const(right))); PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(left), @@ -243,12 +313,14 @@ if (temp == NULL) { goto error; } - OUT_OF_SPACE_IF_NULL(res = sym_new_const(ctx, temp)); + res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and add tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyLong_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -256,26 +328,32 @@ } case _GUARD_BOTH_FLOAT: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; right = stack_pointer[-1]; left = stack_pointer[-2]; if (sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) { REPLACE_OP(this_instr, _NOP, 0 ,0); } - sym_set_type(left, &PyFloat_Type); - sym_set_type(right, &PyFloat_Type); + if (!sym_set_type(left, &PyFloat_Type)) { + goto hit_bottom; + } + if (!sym_set_type(right, &PyFloat_Type)) { + goto hit_bottom; + } break; } case _BINARY_OP_MULTIPLY_FLOAT: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; - _Py_UOpsSymType *res; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) + { assert(PyFloat_CheckExact(sym_get_const(left))); assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( @@ -285,11 +363,13 @@ goto error; } res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyFloat_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -297,12 +377,14 @@ } case _BINARY_OP_ADD_FLOAT: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; - _Py_UOpsSymType *res; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) + { assert(PyFloat_CheckExact(sym_get_const(left))); assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( @@ -312,11 +394,13 @@ goto error; } res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyFloat_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -324,12 +408,14 @@ } case _BINARY_OP_SUBTRACT_FLOAT: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; - _Py_UOpsSymType *res; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(left) && sym_is_const(right)) { + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyFloat_Type) && sym_matches_type(right, &PyFloat_Type)) + { assert(PyFloat_CheckExact(sym_get_const(left))); assert(PyFloat_CheckExact(sym_get_const(right))); PyObject *temp = PyFloat_FromDouble( @@ -339,11 +425,13 @@ goto error; } res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); // TODO gh-115506: // replace opcode with constant propagated one and update tests! } else { - OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyFloat_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -351,31 +439,50 @@ } case _GUARD_BOTH_UNICODE: { - _Py_UOpsSymType *right; - _Py_UOpsSymType *left; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; right = stack_pointer[-1]; left = stack_pointer[-2]; if (sym_matches_type(left, &PyUnicode_Type) && sym_matches_type(right, &PyUnicode_Type)) { REPLACE_OP(this_instr, _NOP, 0 ,0); } - sym_set_type(left, &PyUnicode_Type); - sym_set_type(right, &PyUnicode_Type); + if (!sym_set_type(left, &PyUnicode_Type)) { + goto hit_bottom; + } + if (!sym_set_type(right, &PyUnicode_Type)) { + goto hit_bottom; + } break; } case _BINARY_OP_ADD_UNICODE: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + if (sym_is_const(left) && sym_is_const(right) && + sym_matches_type(left, &PyUnicode_Type) && sym_matches_type(right, &PyUnicode_Type)) { + PyObject *temp = PyUnicode_Concat(sym_get_const(left), sym_get_const(right)); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + Py_DECREF(temp); + OUT_OF_SPACE_IF_NULL(res); + } + else { + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyUnicode_Type)); + } stack_pointer[-2] = res; stack_pointer += -1; break; } case _BINARY_SUBSCR: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -383,8 +490,8 @@ } case _BINARY_SLICE: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-3] = res; stack_pointer += -2; @@ -397,8 +504,8 @@ } case _BINARY_SUBSCR_LIST_INT: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -406,8 +513,8 @@ } case _BINARY_SUBSCR_STR_INT: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -415,8 +522,8 @@ } case _BINARY_SUBSCR_TUPLE_INT: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -424,8 +531,8 @@ } case _BINARY_SUBSCR_DICT: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -465,16 +572,16 @@ } case _CALL_INTRINSIC_1: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; } case _CALL_INTRINSIC_2: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -482,12 +589,12 @@ } case _POP_FRAME: { - _Py_UOpsSymType *retval; - _Py_UOpsSymType *res; + _Py_UopsSymbol *retval; + _Py_UopsSymbol *res; retval = stack_pointer[-1]; stack_pointer += -1; ctx->frame->stack_pointer = stack_pointer; - ctx_frame_pop(ctx); + frame_pop(ctx); stack_pointer = ctx->frame->stack_pointer; res = retval; stack_pointer[0] = res; @@ -500,16 +607,16 @@ /* _INSTRUMENTED_RETURN_CONST is not a viable micro-op for tier 2 */ case _GET_AITER: { - _Py_UOpsSymType *iter; - iter = sym_new_unknown(ctx); + _Py_UopsSymbol *iter; + iter = sym_new_not_null(ctx); if (iter == NULL) goto out_of_space; stack_pointer[-1] = iter; break; } case _GET_ANEXT: { - _Py_UOpsSymType *awaitable; - awaitable = sym_new_unknown(ctx); + _Py_UopsSymbol *awaitable; + awaitable = sym_new_not_null(ctx); if (awaitable == NULL) goto out_of_space; stack_pointer[0] = awaitable; stack_pointer += 1; @@ -517,8 +624,8 @@ } case _GET_AWAITABLE: { - _Py_UOpsSymType *iter; - iter = sym_new_unknown(ctx); + _Py_UopsSymbol *iter; + iter = sym_new_not_null(ctx); if (iter == NULL) goto out_of_space; stack_pointer[-1] = iter; break; @@ -536,8 +643,8 @@ } case _LOAD_ASSERTION_ERROR: { - _Py_UOpsSymType *value; - value = sym_new_unknown(ctx); + _Py_UopsSymbol *value; + value = sym_new_not_null(ctx); if (value == NULL) goto out_of_space; stack_pointer[0] = value; stack_pointer += 1; @@ -545,8 +652,8 @@ } case _LOAD_BUILD_CLASS: { - _Py_UOpsSymType *bc; - bc = sym_new_unknown(ctx); + _Py_UopsSymbol *bc; + bc = sym_new_not_null(ctx); if (bc == NULL) goto out_of_space; stack_pointer[0] = bc; stack_pointer += 1; @@ -563,8 +670,8 @@ } case _UNPACK_SEQUENCE: { - _Py_UOpsSymType *seq; - _Py_UOpsSymType **values; + _Py_UopsSymbol *seq; + _Py_UopsSymbol **values; seq = stack_pointer[-1]; values = &stack_pointer[-1]; /* This has to be done manually */ @@ -577,21 +684,23 @@ } case _UNPACK_SEQUENCE_TWO_TUPLE: { - _Py_UOpsSymType **values; - values = &stack_pointer[-1]; - for (int _i = oparg; --_i >= 0;) { - values[_i] = sym_new_unknown(ctx); - if (values[_i] == NULL) goto out_of_space; - } - stack_pointer += -1 + oparg; + _Py_UopsSymbol *val1; + _Py_UopsSymbol *val0; + val1 = sym_new_not_null(ctx); + if (val1 == NULL) goto out_of_space; + val0 = sym_new_not_null(ctx); + if (val0 == NULL) goto out_of_space; + stack_pointer[-1] = val1; + stack_pointer[0] = val0; + stack_pointer += 1; break; } case _UNPACK_SEQUENCE_TUPLE: { - _Py_UOpsSymType **values; + _Py_UopsSymbol **values; values = &stack_pointer[-1]; for (int _i = oparg; --_i >= 0;) { - values[_i] = sym_new_unknown(ctx); + values[_i] = sym_new_not_null(ctx); if (values[_i] == NULL) goto out_of_space; } stack_pointer += -1 + oparg; @@ -599,10 +708,10 @@ } case _UNPACK_SEQUENCE_LIST: { - _Py_UOpsSymType **values; + _Py_UopsSymbol **values; values = &stack_pointer[-1]; for (int _i = oparg; --_i >= 0;) { - values[_i] = sym_new_unknown(ctx); + values[_i] = sym_new_not_null(ctx); if (values[_i] == NULL) goto out_of_space; } stack_pointer += -1 + oparg; @@ -610,8 +719,8 @@ } case _UNPACK_EX: { - _Py_UOpsSymType *seq; - _Py_UOpsSymType **values; + _Py_UopsSymbol *seq; + _Py_UopsSymbol **values; seq = stack_pointer[-1]; values = &stack_pointer[-1]; /* This has to be done manually */ @@ -644,8 +753,8 @@ } case _LOAD_LOCALS: { - _Py_UOpsSymType *locals; - locals = sym_new_unknown(ctx); + _Py_UopsSymbol *locals; + locals = sym_new_not_null(ctx); if (locals == NULL) goto out_of_space; stack_pointer[0] = locals; stack_pointer += 1; @@ -653,16 +762,16 @@ } case _LOAD_FROM_DICT_OR_GLOBALS: { - _Py_UOpsSymType *v; - v = sym_new_unknown(ctx); + _Py_UopsSymbol *v; + v = sym_new_not_null(ctx); if (v == NULL) goto out_of_space; stack_pointer[-1] = v; break; } case _LOAD_NAME: { - _Py_UOpsSymType *v; - v = sym_new_unknown(ctx); + _Py_UopsSymbol *v; + v = sym_new_not_null(ctx); if (v == NULL) goto out_of_space; stack_pointer[0] = v; stack_pointer += 1; @@ -670,9 +779,9 @@ } case _LOAD_GLOBAL: { - _Py_UOpsSymType *res; - _Py_UOpsSymType *null = NULL; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + _Py_UopsSymbol *null = NULL; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; null = sym_new_null(ctx); if (null == NULL) goto out_of_space; @@ -691,9 +800,9 @@ } case _LOAD_GLOBAL_MODULE: { - _Py_UOpsSymType *res; - _Py_UOpsSymType *null = NULL; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + _Py_UopsSymbol *null = NULL; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; null = sym_new_null(ctx); if (null == NULL) goto out_of_space; @@ -704,9 +813,9 @@ } case _LOAD_GLOBAL_BUILTINS: { - _Py_UOpsSymType *res; - _Py_UOpsSymType *null = NULL; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + _Py_UopsSymbol *null = NULL; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; null = sym_new_null(ctx); if (null == NULL) goto out_of_space; @@ -729,16 +838,16 @@ } case _LOAD_FROM_DICT_OR_DEREF: { - _Py_UOpsSymType *value; - value = sym_new_unknown(ctx); + _Py_UopsSymbol *value; + value = sym_new_not_null(ctx); if (value == NULL) goto out_of_space; stack_pointer[-1] = value; break; } case _LOAD_DEREF: { - _Py_UOpsSymType *value; - value = sym_new_unknown(ctx); + _Py_UopsSymbol *value; + value = sym_new_not_null(ctx); if (value == NULL) goto out_of_space; stack_pointer[0] = value; stack_pointer += 1; @@ -755,8 +864,8 @@ } case _BUILD_STRING: { - _Py_UOpsSymType *str; - str = sym_new_unknown(ctx); + _Py_UopsSymbol *str; + str = sym_new_not_null(ctx); if (str == NULL) goto out_of_space; stack_pointer[-oparg] = str; stack_pointer += 1 - oparg; @@ -764,8 +873,8 @@ } case _BUILD_TUPLE: { - _Py_UOpsSymType *tup; - tup = sym_new_unknown(ctx); + _Py_UopsSymbol *tup; + tup = sym_new_not_null(ctx); if (tup == NULL) goto out_of_space; stack_pointer[-oparg] = tup; stack_pointer += 1 - oparg; @@ -773,8 +882,8 @@ } case _BUILD_LIST: { - _Py_UOpsSymType *list; - list = sym_new_unknown(ctx); + _Py_UopsSymbol *list; + list = sym_new_not_null(ctx); if (list == NULL) goto out_of_space; stack_pointer[-oparg] = list; stack_pointer += 1 - oparg; @@ -792,8 +901,8 @@ } case _BUILD_SET: { - _Py_UOpsSymType *set; - set = sym_new_unknown(ctx); + _Py_UopsSymbol *set; + set = sym_new_not_null(ctx); if (set == NULL) goto out_of_space; stack_pointer[-oparg] = set; stack_pointer += 1 - oparg; @@ -801,8 +910,8 @@ } case _BUILD_MAP: { - _Py_UOpsSymType *map; - map = sym_new_unknown(ctx); + _Py_UopsSymbol *map; + map = sym_new_not_null(ctx); if (map == NULL) goto out_of_space; stack_pointer[-oparg*2] = map; stack_pointer += 1 - oparg*2; @@ -814,8 +923,8 @@ } case _BUILD_CONST_KEY_MAP: { - _Py_UOpsSymType *map; - map = sym_new_unknown(ctx); + _Py_UopsSymbol *map; + map = sym_new_not_null(ctx); if (map == NULL) goto out_of_space; stack_pointer[-1 - oparg] = map; stack_pointer += -oparg; @@ -840,8 +949,8 @@ /* _INSTRUMENTED_LOAD_SUPER_ATTR is not a viable micro-op for tier 2 */ case _LOAD_SUPER_ATTR_ATTR: { - _Py_UOpsSymType *attr; - attr = sym_new_unknown(ctx); + _Py_UopsSymbol *attr; + attr = sym_new_not_null(ctx); if (attr == NULL) goto out_of_space; stack_pointer[-3] = attr; stack_pointer += -2; @@ -849,11 +958,11 @@ } case _LOAD_SUPER_ATTR_METHOD: { - _Py_UOpsSymType *attr; - _Py_UOpsSymType *self_or_null; - attr = sym_new_unknown(ctx); + _Py_UopsSymbol *attr; + _Py_UopsSymbol *self_or_null; + attr = sym_new_not_null(ctx); if (attr == NULL) goto out_of_space; - self_or_null = sym_new_unknown(ctx); + self_or_null = sym_new_not_null(ctx); if (self_or_null == NULL) goto out_of_space; stack_pointer[-3] = attr; stack_pointer[-2] = self_or_null; @@ -862,12 +971,15 @@ } case _LOAD_ATTR: { - _Py_UOpsSymType *attr; - _Py_UOpsSymType *self_or_null = NULL; - attr = sym_new_unknown(ctx); - if (attr == NULL) goto out_of_space; - self_or_null = sym_new_unknown(ctx); - if (self_or_null == NULL) goto out_of_space; + _Py_UopsSymbol *owner; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *self_or_null = NULL; + owner = stack_pointer[-1]; + (void)owner; + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); + if (oparg & 1) { + OUT_OF_SPACE_IF_NULL(self_or_null = sym_new_unknown(ctx)); + } stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = self_or_null; stack_pointer += (oparg & 1); @@ -883,9 +995,9 @@ } case _LOAD_ATTR_INSTANCE_VALUE: { - _Py_UOpsSymType *owner; - _Py_UOpsSymType *attr; - _Py_UOpsSymType *null = NULL; + _Py_UopsSymbol *owner; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *null = NULL; owner = stack_pointer[-1]; uint16_t index = (uint16_t)this_instr->operand; _LOAD_ATTR_NOT_NULL @@ -898,7 +1010,7 @@ } case _CHECK_ATTR_MODULE: { - _Py_UOpsSymType *owner; + _Py_UopsSymbol *owner; owner = stack_pointer[-1]; uint32_t dict_version = (uint32_t)this_instr->operand; (void)dict_version; @@ -919,9 +1031,9 @@ } case _LOAD_ATTR_MODULE: { - _Py_UOpsSymType *owner; - _Py_UOpsSymType *attr; - _Py_UOpsSymType *null = NULL; + _Py_UopsSymbol *owner; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *null = NULL; owner = stack_pointer[-1]; uint16_t index = (uint16_t)this_instr->operand; (void)index; @@ -941,7 +1053,7 @@ } if (attr == NULL) { /* No conversion made. We don't know what `attr` is. */ - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); } stack_pointer[-1] = attr; if (oparg & 1) stack_pointer[0] = null; @@ -954,9 +1066,9 @@ } case _LOAD_ATTR_WITH_HINT: { - _Py_UOpsSymType *owner; - _Py_UOpsSymType *attr; - _Py_UOpsSymType *null = NULL; + _Py_UopsSymbol *owner; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *null = NULL; owner = stack_pointer[-1]; uint16_t hint = (uint16_t)this_instr->operand; _LOAD_ATTR_NOT_NULL @@ -969,9 +1081,9 @@ } case _LOAD_ATTR_SLOT: { - _Py_UOpsSymType *owner; - _Py_UOpsSymType *attr; - _Py_UOpsSymType *null = NULL; + _Py_UopsSymbol *owner; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *null = NULL; owner = stack_pointer[-1]; uint16_t index = (uint16_t)this_instr->operand; _LOAD_ATTR_NOT_NULL @@ -988,9 +1100,9 @@ } case _LOAD_ATTR_CLASS: { - _Py_UOpsSymType *owner; - _Py_UOpsSymType *attr; - _Py_UOpsSymType *null = NULL; + _Py_UopsSymbol *owner; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *null = NULL; owner = stack_pointer[-1]; PyObject *descr = (PyObject *)this_instr->operand; _LOAD_ATTR_NOT_NULL @@ -1023,53 +1135,106 @@ } case _COMPARE_OP: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + (void)left; + (void)right; + if (oparg & 16) { + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); + } + else { + OUT_OF_SPACE_IF_NULL(res = _Py_uop_sym_new_not_null(ctx)); + } stack_pointer[-2] = res; stack_pointer += -1; break; } case _COMPARE_OP_FLOAT: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + (void)left; + (void)right; + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); stack_pointer[-2] = res; stack_pointer += -1; break; } case _COMPARE_OP_INT: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + (void)left; + (void)right; + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); stack_pointer[-2] = res; stack_pointer += -1; break; } case _COMPARE_OP_STR: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + (void)left; + (void)right; + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); stack_pointer[-2] = res; stack_pointer += -1; break; } case _IS_OP: { - _Py_UOpsSymType *b; - b = sym_new_unknown(ctx); + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + (void)left; + (void)right; + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _CONTAINS_OP: { + _Py_UopsSymbol *right; + _Py_UopsSymbol *left; + _Py_UopsSymbol *res; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + (void)left; + (void)right; + OUT_OF_SPACE_IF_NULL(res = sym_new_type(ctx, &PyBool_Type)); + stack_pointer[-2] = res; + stack_pointer += -1; + break; + } + + case _CONTAINS_OP_SET: { + _Py_UopsSymbol *b; + b = sym_new_not_null(ctx); if (b == NULL) goto out_of_space; stack_pointer[-2] = b; stack_pointer += -1; break; } - case _CONTAINS_OP: { - _Py_UOpsSymType *b; - b = sym_new_unknown(ctx); + case _CONTAINS_OP_DICT: { + _Py_UopsSymbol *b; + b = sym_new_not_null(ctx); if (b == NULL) goto out_of_space; stack_pointer[-2] = b; stack_pointer += -1; @@ -1077,11 +1242,11 @@ } case _CHECK_EG_MATCH: { - _Py_UOpsSymType *rest; - _Py_UOpsSymType *match; - rest = sym_new_unknown(ctx); + _Py_UopsSymbol *rest; + _Py_UopsSymbol *match; + rest = sym_new_not_null(ctx); if (rest == NULL) goto out_of_space; - match = sym_new_unknown(ctx); + match = sym_new_not_null(ctx); if (match == NULL) goto out_of_space; stack_pointer[-2] = rest; stack_pointer[-1] = match; @@ -1089,8 +1254,8 @@ } case _CHECK_EXC_MATCH: { - _Py_UOpsSymType *b; - b = sym_new_unknown(ctx); + _Py_UopsSymbol *b; + b = sym_new_not_null(ctx); if (b == NULL) goto out_of_space; stack_pointer[-1] = b; break; @@ -1101,16 +1266,16 @@ /* _POP_JUMP_IF_TRUE is not a viable micro-op for tier 2 */ case _IS_NONE: { - _Py_UOpsSymType *b; - b = sym_new_unknown(ctx); + _Py_UopsSymbol *b; + b = sym_new_not_null(ctx); if (b == NULL) goto out_of_space; stack_pointer[-1] = b; break; } case _GET_LEN: { - _Py_UOpsSymType *len_o; - len_o = sym_new_unknown(ctx); + _Py_UopsSymbol *len_o; + len_o = sym_new_not_null(ctx); if (len_o == NULL) goto out_of_space; stack_pointer[0] = len_o; stack_pointer += 1; @@ -1118,8 +1283,8 @@ } case _MATCH_CLASS: { - _Py_UOpsSymType *attrs; - attrs = sym_new_unknown(ctx); + _Py_UopsSymbol *attrs; + attrs = sym_new_not_null(ctx); if (attrs == NULL) goto out_of_space; stack_pointer[-3] = attrs; stack_pointer += -2; @@ -1127,8 +1292,8 @@ } case _MATCH_MAPPING: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[0] = res; stack_pointer += 1; @@ -1136,8 +1301,8 @@ } case _MATCH_SEQUENCE: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[0] = res; stack_pointer += 1; @@ -1145,8 +1310,8 @@ } case _MATCH_KEYS: { - _Py_UOpsSymType *values_or_none; - values_or_none = sym_new_unknown(ctx); + _Py_UopsSymbol *values_or_none; + values_or_none = sym_new_not_null(ctx); if (values_or_none == NULL) goto out_of_space; stack_pointer[0] = values_or_none; stack_pointer += 1; @@ -1154,16 +1319,16 @@ } case _GET_ITER: { - _Py_UOpsSymType *iter; - iter = sym_new_unknown(ctx); + _Py_UopsSymbol *iter; + iter = sym_new_not_null(ctx); if (iter == NULL) goto out_of_space; stack_pointer[-1] = iter; break; } case _GET_YIELD_FROM_ITER: { - _Py_UOpsSymType *iter; - iter = sym_new_unknown(ctx); + _Py_UopsSymbol *iter; + iter = sym_new_not_null(ctx); if (iter == NULL) goto out_of_space; stack_pointer[-1] = iter; break; @@ -1172,8 +1337,8 @@ /* _FOR_ITER is not a viable micro-op for tier 2 */ case _FOR_ITER_TIER_TWO: { - _Py_UOpsSymType *next; - next = sym_new_unknown(ctx); + _Py_UopsSymbol *next; + next = sym_new_not_null(ctx); if (next == NULL) goto out_of_space; stack_pointer[0] = next; stack_pointer += 1; @@ -1193,8 +1358,8 @@ } case _ITER_NEXT_LIST: { - _Py_UOpsSymType *next; - next = sym_new_unknown(ctx); + _Py_UopsSymbol *next; + next = sym_new_not_null(ctx); if (next == NULL) goto out_of_space; stack_pointer[0] = next; stack_pointer += 1; @@ -1212,8 +1377,8 @@ } case _ITER_NEXT_TUPLE: { - _Py_UOpsSymType *next; - next = sym_new_unknown(ctx); + _Py_UopsSymbol *next; + next = sym_new_not_null(ctx); if (next == NULL) goto out_of_space; stack_pointer[0] = next; stack_pointer += 1; @@ -1231,10 +1396,10 @@ } case _ITER_NEXT_RANGE: { - _Py_UOpsSymType *iter; - _Py_UOpsSymType *next; + _Py_UopsSymbol *iter; + _Py_UopsSymbol *next; iter = stack_pointer[-1]; - OUT_OF_SPACE_IF_NULL(next = sym_new_known_type(ctx, &PyLong_Type)); + OUT_OF_SPACE_IF_NULL(next = sym_new_type(ctx, &PyLong_Type)); (void)iter; stack_pointer[0] = next; stack_pointer += 1; @@ -1244,11 +1409,11 @@ /* _FOR_ITER_GEN is not a viable micro-op for tier 2 */ case _BEFORE_ASYNC_WITH: { - _Py_UOpsSymType *exit; - _Py_UOpsSymType *res; - exit = sym_new_unknown(ctx); + _Py_UopsSymbol *exit; + _Py_UopsSymbol *res; + exit = sym_new_not_null(ctx); if (exit == NULL) goto out_of_space; - res = sym_new_unknown(ctx); + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = exit; stack_pointer[0] = res; @@ -1257,11 +1422,11 @@ } case _BEFORE_WITH: { - _Py_UOpsSymType *exit; - _Py_UOpsSymType *res; - exit = sym_new_unknown(ctx); + _Py_UopsSymbol *exit; + _Py_UopsSymbol *res; + exit = sym_new_not_null(ctx); if (exit == NULL) goto out_of_space; - res = sym_new_unknown(ctx); + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = exit; stack_pointer[0] = res; @@ -1270,8 +1435,8 @@ } case _WITH_EXCEPT_START: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[0] = res; stack_pointer += 1; @@ -1279,11 +1444,11 @@ } case _PUSH_EXC_INFO: { - _Py_UOpsSymType *prev_exc; - _Py_UOpsSymType *new_exc; - prev_exc = sym_new_unknown(ctx); + _Py_UopsSymbol *prev_exc; + _Py_UopsSymbol *new_exc; + prev_exc = sym_new_not_null(ctx); if (prev_exc == NULL) goto out_of_space; - new_exc = sym_new_unknown(ctx); + new_exc = sym_new_not_null(ctx); if (new_exc == NULL) goto out_of_space; stack_pointer[-1] = prev_exc; stack_pointer[0] = new_exc; @@ -1300,13 +1465,14 @@ } case _LOAD_ATTR_METHOD_WITH_VALUES: { - _Py_UOpsSymType *owner; - _Py_UOpsSymType *attr; - _Py_UOpsSymType *self = NULL; + _Py_UopsSymbol *owner; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *self = NULL; owner = stack_pointer[-1]; PyObject *descr = (PyObject *)this_instr->operand; - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); - OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + (void)descr; + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); + self = owner; stack_pointer[-1] = attr; stack_pointer[0] = self; stack_pointer += 1; @@ -1314,13 +1480,14 @@ } case _LOAD_ATTR_METHOD_NO_DICT: { - _Py_UOpsSymType *owner; - _Py_UOpsSymType *attr; - _Py_UOpsSymType *self = NULL; + _Py_UopsSymbol *owner; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *self = NULL; owner = stack_pointer[-1]; PyObject *descr = (PyObject *)this_instr->operand; - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); - OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + (void)descr; + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); + self = owner; stack_pointer[-1] = attr; stack_pointer[0] = self; stack_pointer += 1; @@ -1328,16 +1495,16 @@ } case _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES: { - _Py_UOpsSymType *attr; - attr = sym_new_unknown(ctx); + _Py_UopsSymbol *attr; + attr = sym_new_not_null(ctx); if (attr == NULL) goto out_of_space; stack_pointer[-1] = attr; break; } case _LOAD_ATTR_NONDESCRIPTOR_NO_DICT: { - _Py_UOpsSymType *attr; - attr = sym_new_unknown(ctx); + _Py_UopsSymbol *attr; + attr = sym_new_not_null(ctx); if (attr == NULL) goto out_of_space; stack_pointer[-1] = attr; break; @@ -1348,13 +1515,14 @@ } case _LOAD_ATTR_METHOD_LAZY_DICT: { - _Py_UOpsSymType *owner; - _Py_UOpsSymType *attr; - _Py_UOpsSymType *self = NULL; + _Py_UopsSymbol *owner; + _Py_UopsSymbol *attr; + _Py_UopsSymbol *self = NULL; owner = stack_pointer[-1]; PyObject *descr = (PyObject *)this_instr->operand; - OUT_OF_SPACE_IF_NULL(attr = sym_new_known_notnull(ctx)); - OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + (void)descr; + OUT_OF_SPACE_IF_NULL(attr = sym_new_not_null(ctx)); + self = owner; stack_pointer[-1] = attr; stack_pointer[0] = self; stack_pointer += 1; @@ -1365,23 +1533,32 @@ /* _CALL is not a viable micro-op for tier 2 */ + case _CHECK_PERIODIC: { + break; + } + case _CHECK_CALL_BOUND_METHOD_EXACT_ARGS: { - _Py_UOpsSymType *null; - _Py_UOpsSymType *callable; + _Py_UopsSymbol *null; + _Py_UopsSymbol *callable; null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; - sym_set_null(null); - sym_set_type(callable, &PyMethod_Type); + if (!sym_set_null(null)) { + goto hit_bottom; + } + if (!sym_set_type(callable, &PyMethod_Type)) { + goto hit_bottom; + } break; } case _INIT_CALL_BOUND_METHOD_EXACT_ARGS: { - _Py_UOpsSymType *callable; - _Py_UOpsSymType *func; - _Py_UOpsSymType *self; + _Py_UopsSymbol *callable; + _Py_UopsSymbol *func; + _Py_UopsSymbol *self; callable = stack_pointer[-2 - oparg]; - OUT_OF_SPACE_IF_NULL(func = sym_new_known_notnull(ctx)); - OUT_OF_SPACE_IF_NULL(self = sym_new_known_notnull(ctx)); + (void)callable; + OUT_OF_SPACE_IF_NULL(func = sym_new_not_null(ctx)); + OUT_OF_SPACE_IF_NULL(self = sym_new_not_null(ctx)); stack_pointer[-2 - oparg] = func; stack_pointer[-1 - oparg] = self; break; @@ -1392,12 +1569,14 @@ } case _CHECK_FUNCTION_EXACT_ARGS: { - _Py_UOpsSymType *self_or_null; - _Py_UOpsSymType *callable; + _Py_UopsSymbol *self_or_null; + _Py_UopsSymbol *callable; self_or_null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; uint32_t func_version = (uint32_t)this_instr->operand; - sym_set_type(callable, &PyFunction_Type); + if (!sym_set_type(callable, &PyFunction_Type)) { + goto hit_bottom; + } (void)self_or_null; (void)func_version; break; @@ -1408,9 +1587,9 @@ } case _INIT_CALL_PY_EXACT_ARGS: { - _Py_UOpsSymType **args; - _Py_UOpsSymType *self_or_null; - _Py_UOpsSymType *callable; + _Py_UopsSymbol **args; + _Py_UopsSymbol *self_or_null; + _Py_UopsSymbol *callable; _Py_UOpsAbstractFrame *new_frame; args = &stack_pointer[-oparg]; self_or_null = stack_pointer[-1 - oparg]; @@ -1429,18 +1608,18 @@ args--; argcount++; } - _Py_UOpsSymType **localsplus_start = ctx->n_consumed; + _Py_UopsSymbol **localsplus_start = ctx->n_consumed; int n_locals_already_filled = 0; // Can determine statically, so we interleave the new locals // and make the current stack the new locals. // This also sets up for true call inlining. - if (sym_is_known(self_or_null)) { + if (sym_is_null(self_or_null) || sym_is_not_null(self_or_null)) { localsplus_start = args; n_locals_already_filled = argcount; } OUT_OF_SPACE_IF_NULL(new_frame = - ctx_frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0)); - stack_pointer[-2 - oparg] = (_Py_UOpsSymType *)new_frame; + frame_new(ctx, co, localsplus_start, n_locals_already_filled, 0)); + stack_pointer[-2 - oparg] = (_Py_UopsSymbol *)new_frame; stack_pointer += -1 - oparg; break; } @@ -1459,29 +1638,29 @@ /* _CALL_PY_WITH_DEFAULTS is not a viable micro-op for tier 2 */ case _CALL_TYPE_1: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; + stack_pointer[-3] = res; + stack_pointer += -2; break; } case _CALL_STR_1: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; + stack_pointer[-3] = res; + stack_pointer += -2; break; } case _CALL_TUPLE_1: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; - stack_pointer[-2 - oparg] = res; - stack_pointer += -1 - oparg; + stack_pointer[-3] = res; + stack_pointer += -2; break; } @@ -1493,8 +1672,8 @@ } case _CALL_BUILTIN_CLASS: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1502,8 +1681,8 @@ } case _CALL_BUILTIN_O: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1511,8 +1690,8 @@ } case _CALL_BUILTIN_FAST: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1520,8 +1699,8 @@ } case _CALL_BUILTIN_FAST_WITH_KEYWORDS: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1529,8 +1708,8 @@ } case _CALL_LEN: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1538,8 +1717,8 @@ } case _CALL_ISINSTANCE: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1547,8 +1726,8 @@ } case _CALL_METHOD_DESCRIPTOR_O: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1556,8 +1735,8 @@ } case _CALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1565,8 +1744,8 @@ } case _CALL_METHOD_DESCRIPTOR_NOARGS: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1574,8 +1753,8 @@ } case _CALL_METHOD_DESCRIPTOR_FAST: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2 - oparg] = res; stack_pointer += -1 - oparg; @@ -1591,16 +1770,16 @@ /* _CALL_FUNCTION_EX is not a viable micro-op for tier 2 */ case _MAKE_FUNCTION: { - _Py_UOpsSymType *func; - func = sym_new_unknown(ctx); + _Py_UopsSymbol *func; + func = sym_new_not_null(ctx); if (func == NULL) goto out_of_space; stack_pointer[-1] = func; break; } case _SET_FUNCTION_ATTRIBUTE: { - _Py_UOpsSymType *func; - func = sym_new_unknown(ctx); + _Py_UopsSymbol *func; + func = sym_new_not_null(ctx); if (func == NULL) goto out_of_space; stack_pointer[-2] = func; stack_pointer += -1; @@ -1608,8 +1787,8 @@ } case _BUILD_SLICE: { - _Py_UOpsSymType *slice; - slice = sym_new_unknown(ctx); + _Py_UopsSymbol *slice; + slice = sym_new_not_null(ctx); if (slice == NULL) goto out_of_space; stack_pointer[-2 - ((oparg == 3) ? 1 : 0)] = slice; stack_pointer += -1 - ((oparg == 3) ? 1 : 0); @@ -1617,24 +1796,24 @@ } case _CONVERT_VALUE: { - _Py_UOpsSymType *result; - result = sym_new_unknown(ctx); + _Py_UopsSymbol *result; + result = sym_new_not_null(ctx); if (result == NULL) goto out_of_space; stack_pointer[-1] = result; break; } case _FORMAT_SIMPLE: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-1] = res; break; } case _FORMAT_WITH_SPEC: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -1642,8 +1821,8 @@ } case _COPY: { - _Py_UOpsSymType *bottom; - _Py_UOpsSymType *top; + _Py_UopsSymbol *bottom; + _Py_UopsSymbol *top; bottom = stack_pointer[-1 - (oparg-1)]; assert(oparg > 0); top = bottom; @@ -1653,8 +1832,8 @@ } case _BINARY_OP: { - _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); + _Py_UopsSymbol *res; + res = sym_new_not_null(ctx); if (res == NULL) goto out_of_space; stack_pointer[-2] = res; stack_pointer += -1; @@ -1662,8 +1841,8 @@ } case _SWAP: { - _Py_UOpsSymType *top; - _Py_UOpsSymType *bottom; + _Py_UopsSymbol *top; + _Py_UopsSymbol *bottom; top = stack_pointer[-1]; bottom = stack_pointer[-2 - (oparg-2)]; stack_pointer[-2 - (oparg-2)] = top; @@ -1686,21 +1865,57 @@ /* _INSTRUMENTED_POP_JUMP_IF_NOT_NONE is not a viable micro-op for tier 2 */ case _GUARD_IS_TRUE_POP: { + _Py_UopsSymbol *flag; + flag = stack_pointer[-1]; + if (sym_is_const(flag)) { + PyObject *value = sym_get_const(flag); + assert(value != NULL); + eliminate_pop_guard(this_instr, value != Py_True); + } stack_pointer += -1; break; } case _GUARD_IS_FALSE_POP: { + _Py_UopsSymbol *flag; + flag = stack_pointer[-1]; + if (sym_is_const(flag)) { + PyObject *value = sym_get_const(flag); + assert(value != NULL); + eliminate_pop_guard(this_instr, value != Py_False); + } stack_pointer += -1; break; } case _GUARD_IS_NONE_POP: { + _Py_UopsSymbol *flag; + flag = stack_pointer[-1]; + if (sym_is_const(flag)) { + PyObject *value = sym_get_const(flag); + assert(value != NULL); + eliminate_pop_guard(this_instr, !Py_IsNone(value)); + } + else if (sym_has_type(flag)) { + assert(!sym_matches_type(flag, &_PyNone_Type)); + eliminate_pop_guard(this_instr, true); + } stack_pointer += -1; break; } case _GUARD_IS_NOT_NONE_POP: { + _Py_UopsSymbol *flag; + flag = stack_pointer[-1]; + if (sym_is_const(flag)) { + PyObject *value = sym_get_const(flag); + assert(value != NULL); + eliminate_pop_guard(this_instr, Py_IsNone(value)); + } + else if (sym_has_type(flag)) { + assert(!sym_matches_type(flag, &_PyNone_Type)); + eliminate_pop_guard(this_instr, false); + } stack_pointer += -1; break; } @@ -1726,7 +1941,7 @@ } case _LOAD_CONST_INLINE: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; PyObject *ptr = (PyObject *)this_instr->operand; OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); stack_pointer[0] = value; @@ -1735,7 +1950,7 @@ } case _LOAD_CONST_INLINE_BORROW: { - _Py_UOpsSymType *value; + _Py_UopsSymbol *value; PyObject *ptr = (PyObject *)this_instr->operand; OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); stack_pointer[0] = value; @@ -1743,9 +1958,17 @@ break; } + case _POP_TOP_LOAD_CONST_INLINE_BORROW: { + _Py_UopsSymbol *value; + value = sym_new_not_null(ctx); + if (value == NULL) goto out_of_space; + stack_pointer[-1] = value; + break; + } + case _LOAD_CONST_INLINE_WITH_NULL: { - _Py_UOpsSymType *value; - _Py_UOpsSymType *null; + _Py_UopsSymbol *value; + _Py_UopsSymbol *null; PyObject *ptr = (PyObject *)this_instr->operand; OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); @@ -1756,8 +1979,8 @@ } case _LOAD_CONST_INLINE_BORROW_WITH_NULL: { - _Py_UOpsSymType *value; - _Py_UOpsSymType *null; + _Py_UopsSymbol *value; + _Py_UopsSymbol *null; PyObject *ptr = (PyObject *)this_instr->operand; OUT_OF_SPACE_IF_NULL(value = sym_new_const(ctx, ptr)); OUT_OF_SPACE_IF_NULL(null = sym_new_null(ctx)); @@ -1767,11 +1990,7 @@ break; } - case _CHECK_GLOBALS: { - break; - } - - case _CHECK_BUILTINS: { + case _CHECK_FUNCTION: { break; } diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c new file mode 100644 index 000000000000000..86b0d4d395afa2c --- /dev/null +++ b/Python/optimizer_symbols.c @@ -0,0 +1,502 @@ + +#include "Python.h" + +#include "cpython/optimizer.h" +#include "pycore_code.h" +#include "pycore_frame.h" +#include "pycore_long.h" +#include "pycore_optimizer.h" + +#include +#include +#include + +/* Symbols + ======= + + See the diagram at + https://github.com/faster-cpython/ideas/blob/main/3.13/redundancy_eliminator.md + + We represent the nodes in the diagram as follows + (the flag bits are only defined in optimizer_symbols.c): + - Top: no flag bits, typ and const_val are NULL. + - NULL: IS_NULL flag set, type and const_val NULL. + - Not NULL: NOT_NULL flag set, type and const_val NULL. + - None/not None: not used. (None could be represented as any other constant.) + - Known type: NOT_NULL flag set and typ set; const_val is NULL. + - Known constant: NOT_NULL flag set, type set, const_val set. + - Bottom: IS_NULL and NOT_NULL flags set, type and const_val NULL. + */ + +// Flags for below. +#define IS_NULL 1 << 0 +#define NOT_NULL 1 << 1 + +#ifdef Py_DEBUG +static inline int get_lltrace(void) { + char *uop_debug = Py_GETENV("PYTHON_OPT_DEBUG"); + int lltrace = 0; + if (uop_debug != NULL && *uop_debug >= '0') { + lltrace = *uop_debug - '0'; // TODO: Parse an int and all that + } + return lltrace; +} +#define DPRINTF(level, ...) \ + if (get_lltrace() >= (level)) { printf(__VA_ARGS__); } +#else +#define DPRINTF(level, ...) +#endif + +static _Py_UopsSymbol * +sym_new(_Py_UOpsContext *ctx) +{ + _Py_UopsSymbol *self = &ctx->t_arena.arena[ctx->t_arena.ty_curr_number]; + if (ctx->t_arena.ty_curr_number >= ctx->t_arena.ty_max_number) { + OPT_STAT_INC(optimizer_failure_reason_no_memory); + DPRINTF(1, "out of space for symbolic expression type\n"); + return NULL; + } + ctx->t_arena.ty_curr_number++; + self->flags = 0; + self->typ = NULL; + self->const_val = NULL; + + return self; +} + +static inline void +sym_set_flag(_Py_UopsSymbol *sym, int flag) +{ + sym->flags |= flag; +} + +static inline void +sym_set_bottom(_Py_UopsSymbol *sym) +{ + sym_set_flag(sym, IS_NULL | NOT_NULL); + sym->typ = NULL; + Py_CLEAR(sym->const_val); +} + +bool +_Py_uop_sym_is_bottom(_Py_UopsSymbol *sym) +{ + if ((sym->flags & IS_NULL) && (sym->flags & NOT_NULL)) { + assert(sym->flags == (IS_NULL | NOT_NULL)); + assert(sym->typ == NULL); + assert(sym->const_val == NULL); + return true; + } + return false; +} + +bool +_Py_uop_sym_is_not_null(_Py_UopsSymbol *sym) +{ + return sym->flags == NOT_NULL; +} + +bool +_Py_uop_sym_is_null(_Py_UopsSymbol *sym) +{ + return sym->flags == IS_NULL; +} + +bool +_Py_uop_sym_is_const(_Py_UopsSymbol *sym) +{ + return sym->const_val != NULL; +} + +PyObject * +_Py_uop_sym_get_const(_Py_UopsSymbol *sym) +{ + return sym->const_val; +} + +bool +_Py_uop_sym_set_type(_Py_UopsSymbol *sym, PyTypeObject *typ) +{ + assert(typ != NULL && PyType_Check(typ)); + if (sym->flags & IS_NULL) { + sym_set_bottom(sym); + return false; + } + if (sym->typ != NULL) { + if (sym->typ != typ) { + sym_set_bottom(sym); + return false; + } + } + else { + sym_set_flag(sym, NOT_NULL); + sym->typ = typ; + } + return true; +} + +bool +_Py_uop_sym_set_const(_Py_UopsSymbol *sym, PyObject *const_val) +{ + assert(const_val != NULL); + if (sym->flags & IS_NULL) { + sym_set_bottom(sym); + return false; + } + PyTypeObject *typ = Py_TYPE(const_val); + if (sym->typ != NULL && sym->typ != typ) { + sym_set_bottom(sym); + return false; + } + if (sym->const_val != NULL) { + if (sym->const_val != const_val) { + // TODO: What if they're equal? + sym_set_bottom(sym); + return false; + } + } + else { + sym_set_flag(sym, NOT_NULL); + sym->typ = typ; + sym->const_val = Py_NewRef(const_val); + } + return true; +} + + +bool +_Py_uop_sym_set_null(_Py_UopsSymbol *sym) +{ + sym_set_flag(sym, IS_NULL); + return !_Py_uop_sym_is_bottom(sym); +} + +bool +_Py_uop_sym_set_non_null(_Py_UopsSymbol *sym) +{ + sym_set_flag(sym, NOT_NULL); + return !_Py_uop_sym_is_bottom(sym); +} + + +_Py_UopsSymbol * +_Py_uop_sym_new_unknown(_Py_UOpsContext *ctx) +{ + return sym_new(ctx); +} + +_Py_UopsSymbol * +_Py_uop_sym_new_not_null(_Py_UOpsContext *ctx) +{ + _Py_UopsSymbol *res = _Py_uop_sym_new_unknown(ctx); + if (res == NULL) { + return NULL; + } + sym_set_flag(res, NOT_NULL); + return res; +} + +_Py_UopsSymbol * +_Py_uop_sym_new_type(_Py_UOpsContext *ctx, PyTypeObject *typ) +{ + _Py_UopsSymbol *res = sym_new(ctx); + if (res == NULL) { + return NULL; + } + _Py_uop_sym_set_type(res, typ); + return res; +} + +// Adds a new reference to const_val, owned by the symbol. +_Py_UopsSymbol * +_Py_uop_sym_new_const(_Py_UOpsContext *ctx, PyObject *const_val) +{ + assert(const_val != NULL); + _Py_UopsSymbol *res = sym_new(ctx); + if (res == NULL) { + return NULL; + } + _Py_uop_sym_set_const(res, const_val); + return res; +} + +_Py_UopsSymbol * +_Py_uop_sym_new_null(_Py_UOpsContext *ctx) +{ + _Py_UopsSymbol *null_sym = _Py_uop_sym_new_unknown(ctx); + if (null_sym == NULL) { + return NULL; + } + _Py_uop_sym_set_null(null_sym); + return null_sym; +} + +bool +_Py_uop_sym_has_type(_Py_UopsSymbol *sym) +{ + if (_Py_uop_sym_is_bottom(sym)) { + return false; + } + return sym->typ != NULL; +} + +bool +_Py_uop_sym_matches_type(_Py_UopsSymbol *sym, PyTypeObject *typ) +{ + assert(typ != NULL && PyType_Check(typ)); + if (_Py_uop_sym_is_bottom(sym)) { + return false; + } + return sym->typ == typ; +} + +int +_Py_uop_sym_truthiness(_Py_UopsSymbol *sym) +{ + /* There are some non-constant values for + * which `bool(val)` always evaluates to + * True or False, such as tuples with known + * length, but unknown contents, or bound-methods. + * This function will need updating + * should we support those values. + */ + if (_Py_uop_sym_is_bottom(sym)) { + return -1; + } + if (!_Py_uop_sym_is_const(sym)) { + return -1; + } + PyObject *value = _Py_uop_sym_get_const(sym); + if (value == Py_None) { + return 0; + } + /* Only handle a few known safe types */ + PyTypeObject *tp = Py_TYPE(value); + if (tp == &PyLong_Type) { + return !_PyLong_IsZero((PyLongObject *)value); + } + if (tp == &PyUnicode_Type) { + return value != &_Py_STR(empty); + } + if (tp == &PyBool_Type) { + return value == Py_True; + } + return -1; +} + +// 0 on success, -1 on error. +_Py_UOpsAbstractFrame * +_Py_uop_frame_new( + _Py_UOpsContext *ctx, + PyCodeObject *co, + _Py_UopsSymbol **localsplus_start, + int n_locals_already_filled, + int curr_stackentries) +{ + assert(ctx->curr_frame_depth < MAX_ABSTRACT_FRAME_DEPTH); + _Py_UOpsAbstractFrame *frame = &ctx->frames[ctx->curr_frame_depth]; + + frame->stack_len = co->co_stacksize; + frame->locals_len = co->co_nlocalsplus; + + frame->locals = localsplus_start; + frame->stack = frame->locals + co->co_nlocalsplus; + frame->stack_pointer = frame->stack + curr_stackentries; + ctx->n_consumed = localsplus_start + (co->co_nlocalsplus + co->co_stacksize); + if (ctx->n_consumed >= ctx->limit) { + return NULL; + } + + + // Initialize with the initial state of all local variables + for (int i = n_locals_already_filled; i < co->co_nlocalsplus; i++) { + _Py_UopsSymbol *local = _Py_uop_sym_new_unknown(ctx); + if (local == NULL) { + return NULL; + } + frame->locals[i] = local; + } + + + // Initialize the stack as well + for (int i = 0; i < curr_stackentries; i++) { + _Py_UopsSymbol *stackvar = _Py_uop_sym_new_unknown(ctx); + if (stackvar == NULL) { + return NULL; + } + frame->stack[i] = stackvar; + } + + return frame; +} + +void +_Py_uop_abstractcontext_fini(_Py_UOpsContext *ctx) +{ + if (ctx == NULL) { + return; + } + ctx->curr_frame_depth = 0; + int tys = ctx->t_arena.ty_curr_number; + for (int i = 0; i < tys; i++) { + Py_CLEAR(ctx->t_arena.arena[i].const_val); + } +} + +int +_Py_uop_abstractcontext_init(_Py_UOpsContext *ctx) +{ + ctx->limit = ctx->locals_and_stack + MAX_ABSTRACT_INTERP_SIZE; + ctx->n_consumed = ctx->locals_and_stack; +#ifdef Py_DEBUG // Aids debugging a little. There should never be NULL in the abstract interpreter. + for (int i = 0 ; i < MAX_ABSTRACT_INTERP_SIZE; i++) { + ctx->locals_and_stack[i] = NULL; + } +#endif + + // Setup the arena for sym expressions. + ctx->t_arena.ty_curr_number = 0; + ctx->t_arena.ty_max_number = TY_ARENA_SIZE; + + // Frame setup + ctx->curr_frame_depth = 0; + + return 0; +} + +int +_Py_uop_frame_pop(_Py_UOpsContext *ctx) +{ + _Py_UOpsAbstractFrame *frame = ctx->frame; + ctx->n_consumed = frame->locals; + ctx->curr_frame_depth--; + assert(ctx->curr_frame_depth >= 1); + ctx->frame = &ctx->frames[ctx->curr_frame_depth - 1]; + + return 0; +} + +#define TEST_PREDICATE(PRED, MSG) \ +do { \ + if (!(PRED)) { \ + PyErr_SetString( \ + PyExc_AssertionError, \ + (MSG)); \ + goto fail; \ + } \ +} while (0) + +static _Py_UopsSymbol * +make_bottom(_Py_UOpsContext *ctx) +{ + _Py_UopsSymbol *sym = _Py_uop_sym_new_unknown(ctx); + _Py_uop_sym_set_null(sym); + _Py_uop_sym_set_non_null(sym); + return sym; +} + +PyObject * +_Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) +{ + _Py_UOpsContext context; + _Py_UOpsContext *ctx = &context; + _Py_uop_abstractcontext_init(ctx); + PyObject *val_42 = NULL; + PyObject *val_43 = NULL; + + // Use a single 'sym' variable so copy-pasting tests is easier. + _Py_UopsSymbol *sym = _Py_uop_sym_new_unknown(ctx); + if (sym == NULL) { + goto fail; + } + TEST_PREDICATE(!_Py_uop_sym_is_null(sym), "top is NULL"); + TEST_PREDICATE(!_Py_uop_sym_is_not_null(sym), "top is not NULL"); + TEST_PREDICATE(!_Py_uop_sym_matches_type(sym, &PyLong_Type), "top matches a type"); + TEST_PREDICATE(!_Py_uop_sym_is_const(sym), "top is a constant"); + TEST_PREDICATE(_Py_uop_sym_get_const(sym) == NULL, "top as constant is not NULL"); + TEST_PREDICATE(!_Py_uop_sym_is_bottom(sym), "top is bottom"); + + sym = make_bottom(ctx); + if (sym == NULL) { + goto fail; + } + TEST_PREDICATE(!_Py_uop_sym_is_null(sym), "bottom is NULL is not false"); + TEST_PREDICATE(!_Py_uop_sym_is_not_null(sym), "bottom is not NULL is not false"); + TEST_PREDICATE(!_Py_uop_sym_matches_type(sym, &PyLong_Type), "bottom matches a type"); + TEST_PREDICATE(!_Py_uop_sym_is_const(sym), "bottom is a constant is not false"); + TEST_PREDICATE(_Py_uop_sym_get_const(sym) == NULL, "bottom as constant is not NULL"); + TEST_PREDICATE(_Py_uop_sym_is_bottom(sym), "bottom isn't bottom"); + + sym = _Py_uop_sym_new_type(ctx, &PyLong_Type); + if (sym == NULL) { + goto fail; + } + TEST_PREDICATE(!_Py_uop_sym_is_null(sym), "int is NULL"); + TEST_PREDICATE(_Py_uop_sym_is_not_null(sym), "int isn't not NULL"); + TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyLong_Type), "int isn't int"); + TEST_PREDICATE(!_Py_uop_sym_matches_type(sym, &PyFloat_Type), "int matches float"); + TEST_PREDICATE(!_Py_uop_sym_is_const(sym), "int is a constant"); + TEST_PREDICATE(_Py_uop_sym_get_const(sym) == NULL, "int as constant is not NULL"); + + _Py_uop_sym_set_type(sym, &PyLong_Type); // Should be a no-op + TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyLong_Type), "(int and int) isn't int"); + + _Py_uop_sym_set_type(sym, &PyFloat_Type); // Should make it bottom + TEST_PREDICATE(_Py_uop_sym_is_bottom(sym), "(int and float) isn't bottom"); + + val_42 = PyLong_FromLong(42); + assert(val_42 != NULL); + assert(_Py_IsImmortal(val_42)); + + val_43 = PyLong_FromLong(43); + assert(val_43 != NULL); + assert(_Py_IsImmortal(val_43)); + + sym = _Py_uop_sym_new_type(ctx, &PyLong_Type); + if (sym == NULL) { + goto fail; + } + _Py_uop_sym_set_const(sym, val_42); + TEST_PREDICATE(_Py_uop_sym_truthiness(sym) == 1, "bool(42) is not True"); + TEST_PREDICATE(!_Py_uop_sym_is_null(sym), "42 is NULL"); + TEST_PREDICATE(_Py_uop_sym_is_not_null(sym), "42 isn't not NULL"); + TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyLong_Type), "42 isn't an int"); + TEST_PREDICATE(!_Py_uop_sym_matches_type(sym, &PyFloat_Type), "42 matches float"); + TEST_PREDICATE(_Py_uop_sym_is_const(sym), "42 is not a constant"); + TEST_PREDICATE(_Py_uop_sym_get_const(sym) != NULL, "42 as constant is NULL"); + TEST_PREDICATE(_Py_uop_sym_get_const(sym) == val_42, "42 as constant isn't 42"); + + _Py_uop_sym_set_type(sym, &PyLong_Type); // Should be a no-op + TEST_PREDICATE(_Py_uop_sym_matches_type(sym, &PyLong_Type), "(42 and 42) isn't an int"); + TEST_PREDICATE(_Py_uop_sym_get_const(sym) == val_42, "(42 and 42) as constant isn't 42"); + + _Py_uop_sym_set_type(sym, &PyFloat_Type); // Should make it bottom + TEST_PREDICATE(_Py_uop_sym_is_bottom(sym), "(42 and float) isn't bottom"); + + sym = _Py_uop_sym_new_type(ctx, &PyLong_Type); + if (sym == NULL) { + goto fail; + } + _Py_uop_sym_set_const(sym, val_42); + _Py_uop_sym_set_const(sym, val_43); // Should make it bottom + TEST_PREDICATE(_Py_uop_sym_is_bottom(sym), "(42 and 43) isn't bottom"); + + + sym = _Py_uop_sym_new_const(ctx, Py_None); + TEST_PREDICATE(_Py_uop_sym_truthiness(sym) == 0, "bool(None) is not False"); + sym = _Py_uop_sym_new_const(ctx, Py_False); + TEST_PREDICATE(_Py_uop_sym_truthiness(sym) == 0, "bool(False) is not False"); + sym = _Py_uop_sym_new_const(ctx, PyLong_FromLong(0)); + TEST_PREDICATE(_Py_uop_sym_truthiness(sym) == 0, "bool(0) is not False"); + + _Py_uop_abstractcontext_fini(ctx); + Py_DECREF(val_42); + Py_DECREF(val_43); + Py_RETURN_NONE; + +fail: + _Py_uop_abstractcontext_fini(ctx); + Py_XDECREF(val_42); + Py_XDECREF(val_43); + return NULL; +} diff --git a/Python/parking_lot.c b/Python/parking_lot.c index 0a897f9952f648e..d5877fef56e4d02 100644 --- a/Python/parking_lot.c +++ b/Python/parking_lot.c @@ -194,14 +194,16 @@ _PySemaphore_Wait(_PySemaphore *sema, PyTime_t timeout, int detach) PyThreadState *tstate = NULL; if (detach) { tstate = _PyThreadState_GET(); - if (tstate) { + if (tstate && tstate->state == _Py_THREAD_ATTACHED) { + // Only detach if we are attached PyEval_ReleaseThread(tstate); } + else { + tstate = NULL; + } } - int res = _PySemaphore_PlatformWait(sema, timeout); - - if (detach && tstate) { + if (tstate) { PyEval_AcquireThread(tstate); } return res; diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 04487345f7ec053..3a2c0a450ac9d97 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -612,7 +612,7 @@ builtins_dict_watcher(PyDict_WatchEvent event, PyObject *dict, PyObject *key, Py { PyInterpreterState *interp = _PyInterpreterState_GET(); if (interp->rare_events.builtin_dict < _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS) { - _Py_Executors_InvalidateAll(interp); + _Py_Executors_InvalidateAll(interp, 1); } RARE_EVENT_INTERP_INC(interp, builtin_dict); return 0; @@ -1628,7 +1628,7 @@ finalize_modules(PyThreadState *tstate) PyInterpreterState *interp = tstate->interp; // Invalidate all executors and turn off tier 2 optimizer - _Py_Executors_InvalidateAll(interp); + _Py_Executors_InvalidateAll(interp, 0); _PyOptimizerObject *old = _Py_SetOptimizer(interp, NULL); Py_XDECREF(old); diff --git a/Python/pystate.c b/Python/pystate.c index bb8e24c1dbe12fe..9f142223aff340f 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -795,7 +795,10 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate) Py_CLEAR(interp->audit_hooks); + // At this time, all the threads should be cleared so we don't need atomic + // operations for instrumentation_version or eval_breaker. interp->ceval.instrumentation_version = 0; + tstate->eval_breaker = 0; for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) { interp->monitors.tools[i] = 0; @@ -900,7 +903,7 @@ _PyInterpreterState_Clear(PyThreadState *tstate) static inline void tstate_deactivate(PyThreadState *tstate); -static void tstate_set_detached(PyThreadState *tstate); +static void tstate_set_detached(PyThreadState *tstate, int detached_state); static void zapthreads(PyInterpreterState *interp); void @@ -1029,20 +1032,7 @@ _PyInterpreterState_SetRunningMain(PyInterpreterState *interp) void _PyInterpreterState_SetNotRunningMain(PyInterpreterState *interp) { - PyThreadState *tstate = interp->threads.main; - assert(tstate == current_fast_get()); - - if (tstate->on_delete != NULL) { - // The threading module was imported for the first time in this - // thread, so it was set as threading._main_thread. (See gh-75698.) - // The thread has finished running the Python program so we mark - // the thread object as finished. - assert(tstate->_whence != _PyThreadState_WHENCE_THREADING); - tstate->on_delete(tstate->on_delete_data); - tstate->on_delete = NULL; - tstate->on_delete_data = NULL; - } - + assert(interp->threads.main == current_fast_get()); interp->threads.main = NULL; } @@ -1567,16 +1557,6 @@ PyThreadState_Clear(PyThreadState *tstate) Py_CLEAR(tstate->context); - if (tstate->on_delete != NULL) { - // For the "main" thread of each interpreter, this is meant - // to be done in _PyInterpreterState_SetNotRunningMain(). - // That leaves threads created by the threading module, - // and any threads killed by forking. - // However, we also accommodate "main" threads that still - // don't call _PyInterpreterState_SetNotRunningMain() yet. - tstate->on_delete(tstate->on_delete_data); - } - #ifdef Py_GIL_DISABLED // Each thread should clear own freelists in free-threading builds. struct _Py_object_freelists *freelists = _Py_object_freelists_GET(); @@ -1606,6 +1586,7 @@ tstate_delete_common(PyThreadState *tstate) { assert(tstate->_status.cleared && !tstate->_status.finalized); assert(tstate->state != _Py_THREAD_ATTACHED); + tstate_verify_not_active(tstate); PyInterpreterState *interp = tstate->interp; if (interp == NULL) { @@ -1683,9 +1664,9 @@ _PyThreadState_DeleteCurrent(PyThreadState *tstate) #ifdef Py_GIL_DISABLED _Py_qsbr_detach(((_PyThreadStateImpl *)tstate)->qsbr); #endif - tstate_set_detached(tstate); - tstate_delete_common(tstate); + tstate_set_detached(tstate, _Py_THREAD_DETACHED); current_fast_clear(tstate->interp->runtime); + tstate_delete_common(tstate); _PyEval_ReleaseLock(tstate->interp, NULL); free_threadstate((_PyThreadStateImpl *)tstate); } @@ -1856,13 +1837,13 @@ tstate_try_attach(PyThreadState *tstate) } static void -tstate_set_detached(PyThreadState *tstate) +tstate_set_detached(PyThreadState *tstate, int detached_state) { assert(tstate->state == _Py_THREAD_ATTACHED); #ifdef Py_GIL_DISABLED - _Py_atomic_store_int(&tstate->state, _Py_THREAD_DETACHED); + _Py_atomic_store_int(&tstate->state, detached_state); #else - tstate->state = _Py_THREAD_DETACHED; + tstate->state = detached_state; #endif } @@ -1932,7 +1913,7 @@ detach_thread(PyThreadState *tstate, int detached_state) _Py_qsbr_detach(((_PyThreadStateImpl *)tstate)->qsbr); #endif tstate_deactivate(tstate); - tstate_set_detached(tstate); + tstate_set_detached(tstate, detached_state); current_fast_clear(&_PyRuntime); _PyEval_ReleaseLock(tstate->interp, tstate); } @@ -2528,16 +2509,7 @@ PyGILState_Check(void) return 0; } -#ifdef MS_WINDOWS - int err = GetLastError(); -#endif - PyThreadState *tcur = gilstate_tss_get(runtime); - -#ifdef MS_WINDOWS - SetLastError(err); -#endif - return (tstate == tcur); } @@ -2666,7 +2638,7 @@ _PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, return; } if (eval_frame != NULL) { - _Py_Executors_InvalidateAll(interp); + _Py_Executors_InvalidateAll(interp, 1); } RARE_EVENT_INC(set_eval_frame_func); interp->eval_frame = eval_frame; @@ -2845,6 +2817,7 @@ tstate_mimalloc_bind(PyThreadState *tstate) // the "backing" heap. mi_tld_t *tld = &mts->tld; _mi_tld_init(tld, &mts->heaps[_Py_MIMALLOC_HEAP_MEM]); + llist_init(&mts->page_list); // Exiting threads push any remaining in-use segments to the abandoned // pool to be re-claimed later by other threads. We use per-interpreter @@ -2871,6 +2844,12 @@ tstate_mimalloc_bind(PyThreadState *tstate) mts->heaps[i].debug_offset = (uint8_t)debug_offsets[i]; } + // Heaps that store Python objects should use QSBR to delay freeing + // mimalloc pages while there may be concurrent lock-free readers. + mts->heaps[_Py_MIMALLOC_HEAP_OBJECT].page_use_qsbr = true; + mts->heaps[_Py_MIMALLOC_HEAP_GC].page_use_qsbr = true; + mts->heaps[_Py_MIMALLOC_HEAP_GC_PRE].page_use_qsbr = true; + // By default, object allocations use _Py_MIMALLOC_HEAP_OBJECT. // _PyObject_GC_New() and similar functions temporarily override this to // use one of the GC heaps. diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 5f305aa00e08b9c..2970248da137055 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -89,7 +89,7 @@ int PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit, PyCompilerFlags *flags) { - PyObject *filename_obj; + PyObject *filename_obj = NULL; if (filename != NULL) { filename_obj = PyUnicode_DecodeFSDefault(filename); if (filename_obj == NULL) { @@ -97,9 +97,6 @@ PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit, return -1; } } - else { - filename_obj = NULL; - } int res = _PyRun_AnyFileObject(fp, filename_obj, closeit, flags); Py_XDECREF(filename_obj); return res; @@ -455,7 +452,7 @@ _PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit, v = run_pyc_file(pyc_fp, dict, dict, flags); } else { /* When running from stdin, leave __main__.__loader__ alone */ - if (PyUnicode_CompareWithASCIIString(filename, "") != 0 && + if ((!PyUnicode_Check(filename) || !PyUnicode_EqualToUTF8(filename, "")) && set_main_loader(dict, filename, "SourceFileLoader") < 0) { fprintf(stderr, "python: failed to set __main__.__loader__\n"); ret = -1; @@ -475,11 +472,11 @@ _PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit, done: if (set_file_name) { - if (PyDict_DelItemString(dict, "__file__")) { - PyErr_Clear(); + if (PyDict_PopString(dict, "__file__", NULL) < 0) { + PyErr_Print(); } - if (PyDict_DelItemString(dict, "__cached__")) { - PyErr_Clear(); + if (PyDict_PopString(dict, "__cached__", NULL) < 0) { + PyErr_Print(); } } Py_XDECREF(main_module); diff --git a/Python/pytime.c b/Python/pytime.c index 90ef2eeb546f7fd..45be6a3dbd33413 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -565,6 +565,7 @@ pytime_from_double(PyTime_t *tp, double value, _PyTime_round_t round, /* See comments in pytime_double_to_denominator */ if (!((double)PyTime_MIN <= d && d < -(double)PyTime_MIN)) { pytime_time_t_overflow(); + *tp = 0; return -1; } PyTime_t ns = (PyTime_t)d; @@ -652,14 +653,10 @@ _PyTime_AsLong(PyTime_t ns) return PyLong_FromLongLong((long long)ns); } -PyTime_t -_PyTime_FromSecondsDouble(double seconds, _PyTime_round_t round) +int +_PyTime_FromSecondsDouble(double seconds, _PyTime_round_t round, PyTime_t *result) { - PyTime_t tp; - if(pytime_from_double(&tp, seconds, round, SEC_TO_NS) < 0) { - return -1; - } - return tp; + return pytime_from_double(result, seconds, round, SEC_TO_NS); } @@ -1030,9 +1027,76 @@ _PyTime_TimeWithInfo(PyTime_t *t, _Py_clock_info_t *info) } +#ifdef MS_WINDOWS +static int +py_win_perf_counter_frequency(_PyTimeFraction *base, int raise_exc) +{ + LARGE_INTEGER freq; + // Since Windows XP, the function cannot fail. + (void)QueryPerformanceFrequency(&freq); + LONGLONG frequency = freq.QuadPart; + + // Since Windows XP, frequency cannot be zero. + assert(frequency >= 1); + + Py_BUILD_ASSERT(sizeof(PyTime_t) == sizeof(frequency)); + PyTime_t denom = (PyTime_t)frequency; + + // Known QueryPerformanceFrequency() values: + // + // * 10,000,000 (10 MHz): 100 ns resolution + // * 3,579,545 Hz (3.6 MHz): 279 ns resolution + if (_PyTimeFraction_Set(base, SEC_TO_NS, denom) < 0) { + if (raise_exc) { + PyErr_SetString(PyExc_RuntimeError, + "invalid QueryPerformanceFrequency"); + } + return -1; + } + return 0; +} + + +// N.B. If raise_exc=0, this may be called without the GIL. +static int +py_get_win_perf_counter(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) +{ + assert(info == NULL || raise_exc); + + static _PyTimeFraction base = {0, 0}; + if (base.denom == 0) { + if (py_win_perf_counter_frequency(&base, raise_exc) < 0) { + return -1; + } + } + + if (info) { + info->implementation = "QueryPerformanceCounter()"; + info->resolution = _PyTimeFraction_Resolution(&base); + info->monotonic = 1; + info->adjustable = 0; + } + + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + LONGLONG ticksll = now.QuadPart; + + /* Make sure that casting LONGLONG to PyTime_t cannot overflow, + both types are signed */ + PyTime_t ticks; + static_assert(sizeof(ticksll) <= sizeof(ticks), + "LONGLONG is larger than PyTime_t"); + ticks = (PyTime_t)ticksll; + + *tp = _PyTimeFraction_Mul(ticks, &base); + return 0; +} +#endif // MS_WINDOWS + + #ifdef __APPLE__ static int -py_mach_timebase_info(_PyTimeFraction *base, int raise) +py_mach_timebase_info(_PyTimeFraction *base, int raise_exc) { mach_timebase_info_data_t timebase; // According to the Technical Q&A QA1398, mach_timebase_info() cannot @@ -1054,7 +1118,7 @@ py_mach_timebase_info(_PyTimeFraction *base, int raise) // * (1000000000, 33333335) on PowerPC: ~30 ns // * (1000000000, 25000000) on PowerPC: 40 ns if (_PyTimeFraction_Set(base, numer, denom) < 0) { - if (raise) { + if (raise_exc) { PyErr_SetString(PyExc_RuntimeError, "invalid mach_timebase_info"); } @@ -1072,42 +1136,9 @@ py_get_monotonic_clock(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) assert(info == NULL || raise_exc); #if defined(MS_WINDOWS) - ULONGLONG ticks = GetTickCount64(); - static_assert(sizeof(ticks) <= sizeof(PyTime_t), - "ULONGLONG is larger than PyTime_t"); - PyTime_t t; - if (ticks <= (ULONGLONG)PyTime_MAX) { - t = (PyTime_t)ticks; - } - else { - // GetTickCount64() maximum is larger than PyTime_t maximum: - // ULONGLONG is unsigned, whereas PyTime_t is signed. - t = PyTime_MAX; - } - - int res = pytime_mul(&t, MS_TO_NS); - *tp = t; - - if (raise_exc && res < 0) { - pytime_overflow(); + if (py_get_win_perf_counter(tp, info, raise_exc) < 0) { return -1; } - - if (info) { - DWORD timeAdjustment, timeIncrement; - BOOL isTimeAdjustmentDisabled, ok; - info->implementation = "GetTickCount64()"; - info->monotonic = 1; - ok = GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement, - &isTimeAdjustmentDisabled); - if (!ok) { - PyErr_SetFromWindowsErr(0); - return -1; - } - info->resolution = timeIncrement * 1e-7; - info->adjustable = 0; - } - #elif defined(__APPLE__) static _PyTimeFraction base = {0, 0}; if (base.denom == 0) { @@ -1193,8 +1224,7 @@ _PyTime_MonotonicUnchecked(void) { PyTime_t t; if (py_get_monotonic_clock(&t, NULL, 0) < 0) { - // If mach_timebase_info(), clock_gettime() or gethrtime() fails: - // silently ignore the failure and return 0. + // Ignore silently the error and return 0. t = 0; } return t; @@ -1219,122 +1249,24 @@ _PyTime_MonotonicWithInfo(PyTime_t *tp, _Py_clock_info_t *info) } -#ifdef MS_WINDOWS -static int -py_win_perf_counter_frequency(_PyTimeFraction *base, int raise) -{ - LONGLONG frequency; - - LARGE_INTEGER freq; - // Since Windows XP, the function cannot fail. - (void)QueryPerformanceFrequency(&freq); - frequency = freq.QuadPart; - - // Since Windows XP, frequency cannot be zero. - assert(frequency >= 1); - - Py_BUILD_ASSERT(sizeof(PyTime_t) == sizeof(frequency)); - PyTime_t denom = (PyTime_t)frequency; - - // Known QueryPerformanceFrequency() values: - // - // * 10,000,000 (10 MHz): 100 ns resolution - // * 3,579,545 Hz (3.6 MHz): 279 ns resolution - if (_PyTimeFraction_Set(base, SEC_TO_NS, denom) < 0) { - if (raise) { - PyErr_SetString(PyExc_RuntimeError, - "invalid QueryPerformanceFrequency"); - } - return -1; - } - return 0; -} - - -// N.B. If raise_exc=0, this may be called without the GIL. -static int -py_get_win_perf_counter(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) -{ - assert(info == NULL || raise_exc); - - static _PyTimeFraction base = {0, 0}; - if (base.denom == 0) { - if (py_win_perf_counter_frequency(&base, raise_exc) < 0) { - return -1; - } - } - - if (info) { - info->implementation = "QueryPerformanceCounter()"; - info->resolution = _PyTimeFraction_Resolution(&base); - info->monotonic = 1; - info->adjustable = 0; - } - - LARGE_INTEGER now; - QueryPerformanceCounter(&now); - LONGLONG ticksll = now.QuadPart; - - /* Make sure that casting LONGLONG to PyTime_t cannot overflow, - both types are signed */ - PyTime_t ticks; - static_assert(sizeof(ticksll) <= sizeof(ticks), - "LONGLONG is larger than PyTime_t"); - ticks = (PyTime_t)ticksll; - - PyTime_t ns = _PyTimeFraction_Mul(ticks, &base); - *tp = ns; - return 0; -} -#endif // MS_WINDOWS - - int _PyTime_PerfCounterWithInfo(PyTime_t *t, _Py_clock_info_t *info) { -#ifdef MS_WINDOWS - return py_get_win_perf_counter(t, info, 1); -#else return _PyTime_MonotonicWithInfo(t, info); -#endif } PyTime_t _PyTime_PerfCounterUnchecked(void) { - PyTime_t t; - int res; -#ifdef MS_WINDOWS - res = py_get_win_perf_counter(&t, NULL, 0); -#else - res = py_get_monotonic_clock(&t, NULL, 0); -#endif - if (res < 0) { - // If py_win_perf_counter_frequency() or py_get_monotonic_clock() - // fails: silently ignore the failure and return 0. - t = 0; - } - return t; + return _PyTime_MonotonicUnchecked(); } int PyTime_PerfCounter(PyTime_t *result) { - int res; -#ifdef MS_WINDOWS - res = py_get_win_perf_counter(result, NULL, 1); -#else - res = py_get_monotonic_clock(result, NULL, 1); -#endif - if (res < 0) { - // If py_win_perf_counter_frequency() or py_get_monotonic_clock() - // fails: silently ignore the failure and return 0. - *result = 0; - return -1; - } - return 0; + return PyTime_Monotonic(result); } diff --git a/Python/qsbr.c b/Python/qsbr.c index 7f7ae03cf60d22f..d7ac8f479cda1ba 100644 --- a/Python/qsbr.c +++ b/Python/qsbr.c @@ -38,12 +38,6 @@ #include "pycore_pystate.h" // _PyThreadState_GET() -// Wrap-around safe comparison. This is a holdover from the FreeBSD -// implementation, which uses 32-bit sequence numbers. We currently use 64-bit -// sequence numbers, so wrap-around is unlikely. -#define QSBR_LT(a, b) ((int64_t)((a)-(b)) < 0) -#define QSBR_LEQ(a, b) ((int64_t)((a)-(b)) <= 0) - // Starting size of the array of qsbr thread states #define MIN_ARRAY_SIZE 8 @@ -167,13 +161,11 @@ bool _Py_qsbr_poll(struct _qsbr_thread_state *qsbr, uint64_t goal) { assert(_PyThreadState_GET()->state == _Py_THREAD_ATTACHED); - - uint64_t rd_seq = _Py_atomic_load_uint64(&qsbr->shared->rd_seq); - if (QSBR_LEQ(goal, rd_seq)) { + if (_Py_qbsr_goal_reached(qsbr, goal)) { return true; } - rd_seq = qsbr_poll_scan(qsbr->shared); + uint64_t rd_seq = qsbr_poll_scan(qsbr->shared); return QSBR_LEQ(goal, rd_seq); } @@ -241,13 +233,17 @@ _Py_qsbr_register(_PyThreadStateImpl *tstate, PyInterpreterState *interp, void _Py_qsbr_unregister(_PyThreadStateImpl *tstate) { + struct _qsbr_shared *shared = tstate->qsbr->shared; + + PyMutex_Lock(&shared->mutex); + // NOTE: we must load (or reload) the thread state's qbsr inside the mutex + // because the array may have been resized (changing tstate->qsbr) while + // we waited to acquire the mutex. struct _qsbr_thread_state *qsbr = tstate->qsbr; - struct _qsbr_shared *shared = qsbr->shared; assert(qsbr->seq == 0 && "thread state must be detached"); - - PyMutex_Lock(&shared->mutex); assert(qsbr->allocated && qsbr->tstate == (PyThreadState *)tstate); + tstate->qsbr = NULL; qsbr->tstate = NULL; qsbr->allocated = false; diff --git a/Python/specialize.c b/Python/specialize.c index 871979d92298b6e..b1f9eb756c36653 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -103,6 +103,7 @@ _Py_GetSpecializationStats(void) { return NULL; } int err = 0; + err += add_stat_dict(stats, CONTAINS_OP, "contains_op"); err += add_stat_dict(stats, LOAD_SUPER_ATTR, "load_super_attr"); err += add_stat_dict(stats, LOAD_ATTR, "load_attr"); err += add_stat_dict(stats, LOAD_GLOBAL, "load_global"); @@ -236,6 +237,7 @@ print_optimization_stats(FILE *out, OptimizationStats *stats) fprintf(out, "Optimization inner loop: %" PRIu64 "\n", stats->inner_loop); fprintf(out, "Optimization recursive call: %" PRIu64 "\n", stats->recursive_call); fprintf(out, "Optimization low confidence: %" PRIu64 "\n", stats->low_confidence); + fprintf(out, "Executors invalidated: %" PRIu64 "\n", stats->executors_invalidated); print_histogram(out, "Trace length", stats->trace_length_hist); print_histogram(out, "Trace run length", stats->trace_run_length_hist); @@ -598,6 +600,12 @@ _PyCode_Quicken(PyCodeObject *code) #define SPEC_FAIL_TO_BOOL_SET 17 #define SPEC_FAIL_TO_BOOL_TUPLE 18 +// CONTAINS_OP +#define SPEC_FAIL_CONTAINS_OP_STR 9 +#define SPEC_FAIL_CONTAINS_OP_TUPLE 10 +#define SPEC_FAIL_CONTAINS_OP_LIST 11 +#define SPEC_FAIL_CONTAINS_OP_USER_CLASS 12 + static int function_kind(PyCodeObject *code); static bool function_check_args(PyObject *o, int expected_argcount, int opcode); static uint32_t function_get_version(PyObject *o, int opcode); @@ -2560,6 +2568,49 @@ _Py_Specialize_ToBool(PyObject *value, _Py_CODEUNIT *instr) cache->counter = adaptive_counter_cooldown(); } +#ifdef Py_STATS +static int containsop_fail_kind(PyObject *value) { + if (PyUnicode_CheckExact(value)) { + return SPEC_FAIL_CONTAINS_OP_STR; + } + if (PyList_CheckExact(value)) { + return SPEC_FAIL_CONTAINS_OP_LIST; + } + if (PyTuple_CheckExact(value)) { + return SPEC_FAIL_CONTAINS_OP_TUPLE; + } + if (PyType_Check(value)) { + return SPEC_FAIL_CONTAINS_OP_USER_CLASS; + } + return SPEC_FAIL_OTHER; +} +#endif // Py_STATS + +void +_Py_Specialize_ContainsOp(PyObject *value, _Py_CODEUNIT *instr) +{ + assert(ENABLE_SPECIALIZATION); + assert(_PyOpcode_Caches[CONTAINS_OP] == INLINE_CACHE_ENTRIES_COMPARE_OP); + _PyContainsOpCache *cache = (_PyContainsOpCache *)(instr + 1); + if (PyDict_CheckExact(value)) { + instr->op.code = CONTAINS_OP_DICT; + goto success; + } + if (PySet_CheckExact(value) || PyFrozenSet_CheckExact(value)) { + instr->op.code = CONTAINS_OP_SET; + goto success; + } + + SPECIALIZATION_FAIL(CONTAINS_OP, containsop_fail_kind(value)); + STAT_INC(CONTAINS_OP, failure); + instr->op.code = CONTAINS_OP; + cache->counter = adaptive_counter_backoff(cache->counter); + return; +success: + STAT_INC(CONTAINS_OP, success); + cache->counter = adaptive_counter_cooldown(); +} + /* Code init cleanup. * CALL_ALLOC_AND_ENTER_INIT will set up * the frame to execute the EXIT_INIT_CHECK diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 69b6d886ccc3e90..cd193c1581c6794 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1498,31 +1498,33 @@ get_hash_info(PyThreadState *tstate) int field = 0; PyHash_FuncDef *hashfunc; hash_info = PyStructSequence_New(&Hash_InfoType); - if (hash_info == NULL) - return NULL; - hashfunc = PyHash_GetFuncDef(); - PyStructSequence_SET_ITEM(hash_info, field++, - PyLong_FromLong(8*sizeof(Py_hash_t))); - PyStructSequence_SET_ITEM(hash_info, field++, - PyLong_FromSsize_t(_PyHASH_MODULUS)); - PyStructSequence_SET_ITEM(hash_info, field++, - PyLong_FromLong(_PyHASH_INF)); - PyStructSequence_SET_ITEM(hash_info, field++, - PyLong_FromLong(0)); // This is no longer used - PyStructSequence_SET_ITEM(hash_info, field++, - PyLong_FromLong(_PyHASH_IMAG)); - PyStructSequence_SET_ITEM(hash_info, field++, - PyUnicode_FromString(hashfunc->name)); - PyStructSequence_SET_ITEM(hash_info, field++, - PyLong_FromLong(hashfunc->hash_bits)); - PyStructSequence_SET_ITEM(hash_info, field++, - PyLong_FromLong(hashfunc->seed_bits)); - PyStructSequence_SET_ITEM(hash_info, field++, - PyLong_FromLong(Py_HASH_CUTOFF)); - if (_PyErr_Occurred(tstate)) { - Py_CLEAR(hash_info); + if (hash_info == NULL) { return NULL; } + hashfunc = PyHash_GetFuncDef(); + +#define SET_HASH_INFO_ITEM(CALL) \ + do { \ + PyObject *item = (CALL); \ + if (item == NULL) { \ + Py_CLEAR(hash_info); \ + return NULL; \ + } \ + PyStructSequence_SET_ITEM(hash_info, field++, item); \ + } while(0) + + SET_HASH_INFO_ITEM(PyLong_FromLong(8 * sizeof(Py_hash_t))); + SET_HASH_INFO_ITEM(PyLong_FromSsize_t(_PyHASH_MODULUS)); + SET_HASH_INFO_ITEM(PyLong_FromLong(_PyHASH_INF)); + SET_HASH_INFO_ITEM(PyLong_FromLong(0)); // This is no longer used + SET_HASH_INFO_ITEM(PyLong_FromLong(_PyHASH_IMAG)); + SET_HASH_INFO_ITEM(PyUnicode_FromString(hashfunc->name)); + SET_HASH_INFO_ITEM(PyLong_FromLong(hashfunc->hash_bits)); + SET_HASH_INFO_ITEM(PyLong_FromLong(hashfunc->seed_bits)); + SET_HASH_INFO_ITEM(PyLong_FromLong(Py_HASH_CUTOFF)); + +#undef SET_HASH_INFO_ITEM + return hash_info; } /*[clinic input] @@ -1641,12 +1643,13 @@ sys_getwindowsversion_impl(PyObject *module) int pos = 0; OSVERSIONINFOEXW ver; - version = PyObject_GetAttrString(module, "_cached_windows_version"); + if (PyObject_GetOptionalAttrString(module, "_cached_windows_version", &version) < 0) { + return NULL; + }; if (version && PyObject_TypeCheck(version, &WindowsVersionType)) { return version; } Py_XDECREF(version); - PyErr_Clear(); ver.dwOSVersionInfoSize = sizeof(ver); if (!GetVersionExW((OSVERSIONINFOW*) &ver)) @@ -1656,15 +1659,24 @@ sys_getwindowsversion_impl(PyObject *module) if (version == NULL) return NULL; - PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.dwMajorVersion)); - PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.dwMinorVersion)); - PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.dwBuildNumber)); - PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.dwPlatformId)); - PyStructSequence_SET_ITEM(version, pos++, PyUnicode_FromWideChar(ver.szCSDVersion, -1)); - PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.wServicePackMajor)); - PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.wServicePackMinor)); - PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.wSuiteMask)); - PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.wProductType)); +#define SET_VERSION_INFO(CALL) \ + do { \ + PyObject *item = (CALL); \ + if (item == NULL) { \ + goto error; \ + } \ + PyStructSequence_SET_ITEM(version, pos++, item); \ + } while(0) + + SET_VERSION_INFO(PyLong_FromLong(ver.dwMajorVersion)); + SET_VERSION_INFO(PyLong_FromLong(ver.dwMinorVersion)); + SET_VERSION_INFO(PyLong_FromLong(ver.dwBuildNumber)); + SET_VERSION_INFO(PyLong_FromLong(ver.dwPlatformId)); + SET_VERSION_INFO(PyUnicode_FromWideChar(ver.szCSDVersion, -1)); + SET_VERSION_INFO(PyLong_FromLong(ver.wServicePackMajor)); + SET_VERSION_INFO(PyLong_FromLong(ver.wServicePackMinor)); + SET_VERSION_INFO(PyLong_FromLong(ver.wSuiteMask)); + SET_VERSION_INFO(PyLong_FromLong(ver.wProductType)); // GetVersion will lie if we are running in a compatibility mode. // We need to read the version info from a system file resource @@ -1672,6 +1684,10 @@ sys_getwindowsversion_impl(PyObject *module) // just return whatever GetVersion said. PyObject *realVersion = _sys_getwindowsversion_from_kernel32(); if (!realVersion) { + if (!PyErr_ExceptionMatches(PyExc_WindowsError)) { + return NULL; + } + PyErr_Clear(); realVersion = Py_BuildValue("(kkk)", ver.dwMajorVersion, @@ -1680,21 +1696,19 @@ sys_getwindowsversion_impl(PyObject *module) ); } - if (realVersion) { - PyStructSequence_SET_ITEM(version, pos++, realVersion); - } + SET_VERSION_INFO(realVersion); - if (PyErr_Occurred()) { - Py_DECREF(version); - return NULL; - } +#undef SET_VERSION_INFO if (PyObject_SetAttrString(module, "_cached_windows_version", version) < 0) { - Py_DECREF(version); - return NULL; + goto error; } return version; + +error: + Py_DECREF(version); + return NULL; } #pragma warning(pop) @@ -2138,7 +2152,7 @@ sys__clear_internal_caches_impl(PyObject *module) /*[clinic end generated code: output=0ee128670a4966d6 input=253e741ca744f6e8]*/ { PyInterpreterState *interp = _PyInterpreterState_GET(); - _Py_Executors_InvalidateAll(interp); + _Py_Executors_InvalidateAll(interp, 0); PyType_ClearCache(); Py_RETURN_NONE; } @@ -3034,6 +3048,7 @@ static PyStructSequence_Field flags_fields[] = { {"warn_default_encoding", "-X warn_default_encoding"}, {"safe_path", "-P"}, {"int_max_str_digits", "-X int_max_str_digits"}, + {"gil", "-X gil"}, {0} }; @@ -3083,6 +3098,16 @@ set_flags_from_config(PyInterpreterState *interp, PyObject *flags) SetFlag(config->warn_default_encoding); SetFlagObj(PyBool_FromLong(config->safe_path)); SetFlag(config->int_max_str_digits); +#ifdef Py_GIL_DISABLED + if (config->enable_gil == _PyConfig_GIL_DEFAULT) { + SetFlagObj(Py_NewRef(Py_None)); + } + else { + SetFlag(config->enable_gil); + } +#else + SetFlagObj(PyLong_FromLong(1)); +#endif #undef SetFlagObj #undef SetFlag return 0; diff --git a/Python/thread_nt.h b/Python/thread_nt.h index 7922b2d7e848454..9dca833ff203ca9 100644 --- a/Python/thread_nt.h +++ b/Python/thread_nt.h @@ -513,5 +513,10 @@ void * PyThread_tss_get(Py_tss_t *key) { assert(key != NULL); - return TlsGetValue(key->_key); + int err = GetLastError(); + void *r = TlsGetValue(key->_key); + if (r || !GetLastError()) { + SetLastError(err); + } + return r; } diff --git a/Python/thread_pthread.h b/Python/thread_pthread.h index 64cc60053e6cf7a..65d366e91c322a4 100644 --- a/Python/thread_pthread.h +++ b/Python/thread_pthread.h @@ -95,6 +95,10 @@ #endif #endif +/* Thread sanitizer doesn't currently support sem_clockwait */ +#ifdef _Py_THREAD_SANITIZER +#undef HAVE_SEM_CLOCKWAIT +#endif /* Whether or not to use semaphores directly rather than emulating them with * mutexes and condition variables: diff --git a/Python/tier2_engine.md b/Python/tier2_engine.md index df9f6c124509bdf..5ceda8e806045d6 100644 --- a/Python/tier2_engine.md +++ b/Python/tier2_engine.md @@ -52,7 +52,7 @@ The JIT compiler converts superblocks into machine code executors. These have identical behavior to interpreted executors, except that they consume more memory for the generated machine code and are a lot faster. -## Transfering control +## Transferring control There are three types of control transfer that we need to consider: * Tier 1 to tier 2 @@ -104,7 +104,7 @@ reference to the previous executor in the thread state's #### The interpreter The tier 2 interpreter has a variable `current_executor` which -points to the currently live executor. When transfering from executor +points to the currently live executor. When transferring from executor `A` to executor `B` we do the following: (Initially `current_executor` points to `A`, and the refcount of `A` is elevated by one) @@ -122,7 +122,7 @@ increment the refcount of `B` and set `current_executor` to point to `B`. #### In the JIT -Transfering control from one executor to another is done via tailcalls. +Transferring control from one executor to another is done via tailcalls. The compiled executor should do the same, except that there is no local variable `current_executor`. diff --git a/README.rst b/README.rst index fd3e6dd1771ae17..46167b38eab566d 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -This is Python version 3.13.0 alpha 4 +This is Python version 3.13.0 alpha 5 ===================================== .. image:: https://github.com/python/cpython/workflows/Tests/badge.svg diff --git a/Tools/build/generate_sbom.py b/Tools/build/generate_sbom.py index 201c81c4d14d793..6aa4946ee227e78 100644 --- a/Tools/build/generate_sbom.py +++ b/Tools/build/generate_sbom.py @@ -7,9 +7,8 @@ import pathlib import subprocess import sys +import urllib.request import typing -import zipfile -from urllib.request import urlopen CPYTHON_ROOT_DIR = pathlib.Path(__file__).parent.parent.parent @@ -125,30 +124,41 @@ def filter_gitignored_paths(paths: list[str]) -> list[str]: return sorted([line.split()[-1] for line in git_check_ignore_lines if line.startswith("::")]) -def main() -> None: - sbom_path = CPYTHON_ROOT_DIR / "Misc/sbom.spdx.json" - sbom_data = json.loads(sbom_path.read_bytes()) +def get_externals() -> list[str]: + """ + Parses 'PCbuild/get_externals.bat' for external libraries. + Returns a list of (git tag, name, version) tuples. + """ + get_externals_bat_path = CPYTHON_ROOT_DIR / "PCbuild/get_externals.bat" + externals = re.findall( + r"set\s+libraries\s*=\s*%libraries%\s+([a-zA-Z0-9.-]+)\s", + get_externals_bat_path.read_text() + ) + return externals - # We regenerate all of this information. Package information - # should be preserved though since that is edited by humans. - sbom_data["files"] = [] - sbom_data["relationships"] = [] - # Ensure all packages in this tool are represented also in the SBOM file. - actual_names = {package["name"] for package in sbom_data["packages"]} - expected_names = set(PACKAGE_TO_FILES) - error_if( - actual_names != expected_names, - f"Packages defined in SBOM tool don't match those defined in SBOM file: {actual_names}, {expected_names}", - ) +def check_sbom_packages(sbom_data: dict[str, typing.Any]) -> None: + """Make a bunch of assertions about the SBOM package data to ensure it's consistent.""" - # Make a bunch of assertions about the SBOM data to ensure it's consistent. for package in sbom_data["packages"]: # Properties and ID must be properly formed. error_if( "name" not in package, "Package is missing the 'name' field" ) + + # Verify that the checksum matches the expected value + # and that the download URL is valid. + if "checksums" not in package or "CI" in os.environ: + download_location = package["downloadLocation"] + resp = urllib.request.urlopen(download_location) + error_if(resp.status != 200, f"Couldn't access URL: {download_location}'") + + package["checksums"] = [{ + "algorithm": "SHA256", + "checksumValue": hashlib.sha256(resp.read()).hexdigest() + }] + missing_required_keys = REQUIRED_PROPERTIES_PACKAGE - set(package.keys()) error_if( bool(missing_required_keys), @@ -180,6 +190,26 @@ def main() -> None: f"License identifier must be 'NOASSERTION'" ) + +def create_source_sbom() -> None: + sbom_path = CPYTHON_ROOT_DIR / "Misc/sbom.spdx.json" + sbom_data = json.loads(sbom_path.read_bytes()) + + # We regenerate all of this information. Package information + # should be preserved though since that is edited by humans. + sbom_data["files"] = [] + sbom_data["relationships"] = [] + + # Ensure all packages in this tool are represented also in the SBOM file. + actual_names = {package["name"] for package in sbom_data["packages"]} + expected_names = set(PACKAGE_TO_FILES) + error_if( + actual_names != expected_names, + f"Packages defined in SBOM tool don't match those defined in SBOM file: {actual_names}, {expected_names}", + ) + + check_sbom_packages(sbom_data) + # We call 'sorted()' here a lot to avoid filesystem scan order issues. for name, files in sorted(PACKAGE_TO_FILES.items()): package_spdx_id = spdx_id(f"SPDXRef-PACKAGE-{name}") @@ -224,5 +254,49 @@ def main() -> None: sbom_path.write_text(json.dumps(sbom_data, indent=2, sort_keys=True)) +def create_externals_sbom() -> None: + sbom_path = CPYTHON_ROOT_DIR / "Misc/externals.spdx.json" + sbom_data = json.loads(sbom_path.read_bytes()) + + externals = get_externals() + externals_name_to_version = {} + externals_name_to_git_tag = {} + for git_tag in externals: + name, _, version = git_tag.rpartition("-") + externals_name_to_version[name] = version + externals_name_to_git_tag[name] = git_tag + + # Ensure all packages in this tool are represented also in the SBOM file. + actual_names = {package["name"] for package in sbom_data["packages"]} + expected_names = set(externals_name_to_version) + error_if( + actual_names != expected_names, + f"Packages defined in SBOM tool don't match those defined in SBOM file: {actual_names}, {expected_names}", + ) + + # Set the versionInfo and downloadLocation fields for all packages. + for package in sbom_data["packages"]: + package["versionInfo"] = externals_name_to_version[package["name"]] + download_location = ( + f"https://github.com/python/cpython-source-deps/archive/refs/tags/{externals_name_to_git_tag[package['name']]}.tar.gz" + ) + download_location_changed = download_location != package["downloadLocation"] + package["downloadLocation"] = download_location + + # If the download URL has changed we want one to get recalulated. + if download_location_changed: + package.pop("checksums", None) + + check_sbom_packages(sbom_data) + + # Update the SBOM on disk + sbom_path.write_text(json.dumps(sbom_data, indent=2, sort_keys=True)) + + +def main() -> None: + create_source_sbom() + create_externals_sbom() + + if __name__ == "__main__": main() diff --git a/Tools/build/generate_stdlib_module_names.py b/Tools/build/generate_stdlib_module_names.py index 5dce4e042d1eb44..69dc74e7f25ec98 100644 --- a/Tools/build/generate_stdlib_module_names.py +++ b/Tools/build/generate_stdlib_module_names.py @@ -32,8 +32,10 @@ '_testconsole', '_testimportmultiple', '_testinternalcapi', + '_testlimitedcapi', '_testmultiphase', '_testsinglephase', + '_testexternalinspection', '_xxsubinterpreters', '_xxinterpchannels', '_xxinterpqueues', diff --git a/Tools/build/stable_abi.py b/Tools/build/stable_abi.py index 83146622c74f941..95fc4dfbf11e49f 100644 --- a/Tools/build/stable_abi.py +++ b/Tools/build/stable_abi.py @@ -601,7 +601,7 @@ def check_private_names(manifest): if name.startswith('_') and not item.abi_only: raise ValueError( f'`{name}` is private (underscore-prefixed) and should be ' - + 'removed from the stable ABI list or or marked `abi_only`') + + 'removed from the stable ABI list or marked `abi_only`') def check_dump(manifest, filename): """Check that manifest.dump() corresponds to the data. diff --git a/Tools/c-analyzer/README b/Tools/c-analyzer/README index 86bf1e77e0bfea8..41ea13280347a76 100644 --- a/Tools/c-analyzer/README +++ b/Tools/c-analyzer/README @@ -11,9 +11,8 @@ falls into one of several categories: * module state * Python runtime state -The ignored-globals.txt file is organized similarly. Of the different -categories, the last two are problematic and generally should not exist -in the codebase. +Of the different categories, the last two are problematic and +generally should not exist in the codebase. Globals that hold module state (i.e. in Modules/*.c) cause problems when multiple interpreters are in use. For more info, see PEP 3121, @@ -42,4 +41,3 @@ You can also use the more generic tool: If it reports any globals then they should be resolved. If the globals are runtime state then they should be folded into _PyRuntimeState. -Otherwise they should be added to ignored-globals.txt. diff --git a/Tools/c-analyzer/c_parser/preprocessor/gcc.py b/Tools/c-analyzer/c_parser/preprocessor/gcc.py index 6ece70c77fd55c3..bb5ec5ae087a749 100644 --- a/Tools/c-analyzer/c_parser/preprocessor/gcc.py +++ b/Tools/c-analyzer/c_parser/preprocessor/gcc.py @@ -7,6 +7,7 @@ FILES_WITHOUT_INTERNAL_CAPI = frozenset(( # Modules/ '_testcapimodule.c', + '_testlimitedcapi.c', '_testclinic_limited.c', 'xxlimited.c', 'xxlimited_35.c', @@ -15,8 +16,8 @@ # C files in the fhe following directories must not be built with # Py_BUILD_CORE. DIRS_WITHOUT_INTERNAL_CAPI = frozenset(( - # Modules/_testcapi/ - '_testcapi', + '_testcapi', # Modules/_testcapi/ + '_testlimitedcapi', # Modules/_testlimitedcapi/ )) TOOL = 'gcc' diff --git a/Tools/c-analyzer/cpython/_parser.py b/Tools/c-analyzer/cpython/_parser.py index 8482bf849aaacd3..12010f0e9c05491 100644 --- a/Tools/c-analyzer/cpython/_parser.py +++ b/Tools/c-analyzer/cpython/_parser.py @@ -83,11 +83,11 @@ def clean_lines(text): Python/frozen_modules/*.h Python/generated_cases.c.h Python/executor_cases.c.h -Python/tier2_redundancy_eliminator_cases.c.h +Python/optimizer_cases.c.h # not actually source Python/bytecodes.c -Python/tier2_redundancy_eliminator_bytecodes.c +Python/optimizer_bytecodes.c # mimalloc Objects/mimalloc/*.c diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 45119664af4362a..686a3d3160cc906 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -482,3 +482,4 @@ Modules/readline.c - sigwinch_ohandler - Modules/readline.c - completed_input_string - Modules/rotatingtree.c - random_stream - Modules/rotatingtree.c - random_value - +Modules/rotatingtree.c - random_mutex - diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index e9b81cf4c7d6531..0024e2683052c86 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -444,7 +444,6 @@ Modules/_testcapi/heaptype.c - _testcapimodule - Modules/_testcapi/mem.c - FmData - Modules/_testcapi/mem.c - FmHook - Modules/_testcapi/structmember.c - test_structmembersType_OldAPI - -Modules/_testcapi/unicode.c - _testcapimodule - Modules/_testcapi/watchers.c - g_dict_watch_events - Modules/_testcapi/watchers.c - g_dict_watchers_installed - Modules/_testcapi/watchers.c - g_type_modified_events - @@ -740,6 +739,6 @@ Modules/expat/xmlrole.c - error - ## other Modules/_io/_iomodule.c - _PyIO_Module - Modules/_sqlite/module.c - _sqlite3module - -Python/optimizer_analysis.c - _Py_UOpsAbstractFrame_Type - -Python/optimizer_analysis.c - _Py_UOpsAbstractInterpContext_Type - Modules/clinic/md5module.c.h _md5_md5 _keywords - +Modules/clinic/grpmodule.c.h grp_getgrgid _keywords - +Modules/clinic/grpmodule.c.h grp_getgrnam _keywords - diff --git a/Tools/cases_generator/README.md b/Tools/cases_generator/README.md index d35a868b42ea9ec..fb512c4646b851a 100644 --- a/Tools/cases_generator/README.md +++ b/Tools/cases_generator/README.md @@ -13,9 +13,9 @@ What's currently here: - `parser.py` helper for interactions with `parsing.py` - `tierN_generator.py`: a couple of driver scripts to read `Python/bytecodes.c` and write `Python/generated_cases.c.h` (and several other files) -- `tier2_abstract_generator.py`: reads `Python/bytecodes.c` and - `Python/tier2_redundancy_eliminator_bytecodes.c` and writes - `Python/tier2_redundancy_eliminator_cases.c.h` +- `optimizer_generator.py`: reads `Python/bytecodes.c` and + `Python/optimizer_bytecodes.c` and writes + `Python/optimizer_cases.c.h` - `stack.py`: code to handle generalized stack effects - `cwriter.py`: code which understands tokens and how to format C code; main class: `CWriter` diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index 49b8b426444d248..27e6ba2b3fdedf8 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -17,7 +17,6 @@ class Properties: needs_this: bool always_exits: bool stores_sp: bool - tier_one_only: bool uses_co_consts: bool uses_co_names: bool uses_locals: bool @@ -25,6 +24,7 @@ class Properties: side_exit: bool pure: bool passthrough: bool + tier: int | None = None oparg_and_1: bool = False const_oparg: int = -1 @@ -46,7 +46,6 @@ def from_list(properties: list["Properties"]) -> "Properties": needs_this=any(p.needs_this for p in properties), always_exits=any(p.always_exits for p in properties), stores_sp=any(p.stores_sp for p in properties), - tier_one_only=any(p.tier_one_only for p in properties), uses_co_consts=any(p.uses_co_consts for p in properties), uses_co_names=any(p.uses_co_names for p in properties), uses_locals=any(p.uses_locals for p in properties), @@ -68,7 +67,6 @@ def from_list(properties: list["Properties"]) -> "Properties": needs_this=False, always_exits=False, stores_sp=False, - tier_one_only=False, uses_co_consts=False, uses_co_names=False, uses_locals=False, @@ -312,6 +310,15 @@ def variable_used(node: parser.InstDef, name: str) -> bool: token.kind == "IDENTIFIER" and token.text == name for token in node.tokens ) +def tier_variable(node: parser.InstDef) -> int | None: + """Determine whether a tier variable is used in a node.""" + for token in node.tokens: + if token.kind == "ANNOTATION": + if token.text == "specializing": + return 1 + if re.fullmatch(r"tier\d", token.text): + return int(token.text[-1]) + return None def is_infallible(op: parser.InstDef) -> bool: return not ( @@ -328,6 +335,7 @@ def is_infallible(op: parser.InstDef) -> bool: "_PyDictOrValues_IsValues", "_PyObject_DictOrValuesPointer", "_PyDictOrValues_GetValues", + "_PyDictValues_AddToInsertionOrder", "_PyObject_MakeInstanceAttributesFromDict", "Py_DECREF", "_Py_DECREF_SPECIALIZED", @@ -348,8 +356,10 @@ def is_infallible(op: parser.InstDef) -> bool: "_PyLong_IsCompact", "_PyLong_IsNonNegativeCompact", "_PyLong_CompactValue", + "_PyLong_DigitCount", "_Py_NewRef", "_Py_IsImmortal", + "PyLong_FromLong", "_Py_STR", "_PyLong_Add", "_PyLong_Multiply", @@ -361,6 +371,17 @@ def is_infallible(op: parser.InstDef) -> bool: "_Py_atomic_load_uintptr_relaxed", "_PyFrame_GetCode", "_PyThreadState_HasStackSpace", + "_PyUnicode_Equal", + "_PyFrame_SetStackPointer", + "_PyType_HasFeature", + "PyUnicode_Concat", + "_PyList_FromArraySteal", + "_PyTuple_FromArraySteal", + "PySlice_New", + "_Py_LeaveRecursiveCallPy", + "CALL_STAT_INC", + "maybe_lltrace_resume_frame", + "_PyUnicode_JoinArray", ) ESCAPING_FUNCTIONS = ( @@ -372,6 +393,8 @@ def is_infallible(op: parser.InstDef) -> bool: def makes_escaping_api_call(instr: parser.InstDef) -> bool: if "CALL_INTRINSIC" in instr.name: return True + if instr.name == "_BINARY_OP": + return True tkns = iter(instr.tokens) for tkn in tkns: if tkn.kind != lexer.IDENTIFIER: @@ -498,7 +521,6 @@ def compute_properties(op: parser.InstDef) -> Properties: needs_this=variable_used(op, "this_instr"), always_exits=always_exits(op), stores_sp=variable_used(op, "SYNC_SP"), - tier_one_only=variable_used(op, "TIER_ONE_ONLY"), uses_co_consts=variable_used(op, "FRAME_CO_CONSTS"), uses_co_names=variable_used(op, "FRAME_CO_NAMES"), uses_locals=(variable_used(op, "GETLOCAL") or variable_used(op, "SETLOCAL")) @@ -506,6 +528,7 @@ def compute_properties(op: parser.InstDef) -> Properties: has_free=has_free, pure="pure" in op.annotations, passthrough=passthrough, + tier=tier_variable(op), ) diff --git a/Tools/cases_generator/interpreter_definition.md b/Tools/cases_generator/interpreter_definition.md index 9b5733562f77b40..889f58fc3e1a75d 100644 --- a/Tools/cases_generator/interpreter_definition.md +++ b/Tools/cases_generator/interpreter_definition.md @@ -168,6 +168,7 @@ list of annotations and their meanings are as follows: * `override`. For external use by other interpreter definitions to override the current instruction definition. * `pure`. This instruction has no side effects. +* 'tierN'. This instruction only used by tier N interpreter. ### Special functions/macros diff --git a/Tools/cases_generator/lexer.py b/Tools/cases_generator/lexer.py index 0077921e7d7fa12..13aee94f2b957c6 100644 --- a/Tools/cases_generator/lexer.py +++ b/Tools/cases_generator/lexer.py @@ -224,6 +224,8 @@ def choice(*opts: str) -> str: "pure", "split", "replicate", + "tier1", + "tier2", } __all__ = [] diff --git a/Tools/cases_generator/opcode_metadata_generator.py b/Tools/cases_generator/opcode_metadata_generator.py index 24fbea6cfcb77ac..ab597834a8892fa 100644 --- a/Tools/cases_generator/opcode_metadata_generator.py +++ b/Tools/cases_generator/opcode_metadata_generator.py @@ -284,7 +284,7 @@ def is_viable_expansion(inst: Instruction) -> bool: continue if "replaced" in part.annotations: continue - if part.properties.tier_one_only or not part.is_viable(): + if part.properties.tier == 1 or not part.is_viable(): return False return True diff --git a/Tools/cases_generator/tier2_abstract_generator.py b/Tools/cases_generator/optimizer_generator.py similarity index 85% rename from Tools/cases_generator/tier2_abstract_generator.py rename to Tools/cases_generator/optimizer_generator.py index 47a862643d987e1..1c6b708e82321a4 100644 --- a/Tools/cases_generator/tier2_abstract_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -1,19 +1,15 @@ -"""Generate the cases for the tier 2 redundancy eliminator/abstract interpreter. -Reads the instruction definitions from bytecodes.c. and tier2_redundancy_eliminator.bytecodes.c -Writes the cases to tier2_redundancy_eliminator_cases.c.h, which is #included in Python/optimizer_analysis.c. +"""Generate the cases for the tier 2 optimizer. +Reads the instruction definitions from bytecodes.c and optimizer_bytecodes.c +Writes the cases to optimizer_cases.c.h, which is #included in Python/optimizer_analysis.c. """ import argparse -import os.path -import sys from analyzer import ( Analysis, Instruction, Uop, - Part, analyze_files, - Skip, StackItem, analysis_error, ) @@ -28,10 +24,10 @@ from cwriter import CWriter from typing import TextIO, Iterator from lexer import Token -from stack import StackOffset, Stack, SizeMismatch, UNUSED +from stack import Stack, SizeMismatch, UNUSED -DEFAULT_OUTPUT = ROOT / "Python/tier2_redundancy_eliminator_cases.c.h" -DEFAULT_ABSTRACT_INPUT = ROOT / "Python/tier2_redundancy_eliminator_bytecodes.c" +DEFAULT_OUTPUT = ROOT / "Python/optimizer_cases.c.h" +DEFAULT_ABSTRACT_INPUT = (ROOT / "Python/optimizer_bytecodes.c").absolute().as_posix() def validate_uop(override: Uop, uop: Uop) -> None: @@ -41,10 +37,10 @@ def validate_uop(override: Uop, uop: Uop) -> None: def type_name(var: StackItem) -> str: if var.is_array(): - return f"_Py_UOpsSymType **" + return f"_Py_UopsSymbol **" if var.type: return var.type - return f"_Py_UOpsSymType *" + return f"_Py_UopsSymbol *" def declare_variables(uop: Uop, out: CWriter, skip_inputs: bool) -> None: @@ -87,14 +83,14 @@ def emit_default(out: CWriter, uop: Uop) -> None: if var.name != "unused" and not var.peek: if var.is_array(): out.emit(f"for (int _i = {var.size}; --_i >= 0;) {{\n") - out.emit(f"{var.name}[_i] = sym_new_unknown(ctx);\n") + out.emit(f"{var.name}[_i] = sym_new_not_null(ctx);\n") out.emit(f"if ({var.name}[_i] == NULL) goto out_of_space;\n") out.emit("}\n") elif var.name == "null": out.emit(f"{var.name} = sym_new_null(ctx);\n") out.emit(f"if ({var.name} == NULL) goto out_of_space;\n") else: - out.emit(f"{var.name} = sym_new_unknown(ctx);\n") + out.emit(f"{var.name} = sym_new_not_null(ctx);\n") out.emit(f"if ({var.name} == NULL) goto out_of_space;\n") @@ -148,7 +144,7 @@ def write_uop( if not var.peek or is_override: out.emit(stack.push(var)) out.start_line() - stack.flush(out, cast_type="_Py_UOpsSymType *") + stack.flush(out, cast_type="_Py_UopsSymbol *") except SizeMismatch as ex: raise analysis_error(ex.args[0], uop.body[0]) @@ -176,7 +172,7 @@ def generate_abstract_interpreter( if uop.name in abstract.uops: override = abstract.uops[uop.name] validate_uop(override, uop) - if uop.properties.tier_one_only: + if uop.properties.tier == 1: continue if uop.replicates: continue @@ -218,19 +214,22 @@ def generate_tier2_abstract_from_files( ) -arg_parser.add_argument("input", nargs=1, help="Abstract interpreter definition file") +arg_parser.add_argument("input", nargs='*', help="Abstract interpreter definition file") arg_parser.add_argument( - "base", nargs=argparse.REMAINDER, help="The base instruction definition file(s)" + "base", nargs="*", help="The base instruction definition file(s)" ) arg_parser.add_argument("-d", "--debug", help="Insert debug calls", action="store_true") if __name__ == "__main__": args = arg_parser.parse_args() - if len(args.base) == 0: - args.input.append(DEFAULT_INPUT) + if not args.input: + args.base.append(DEFAULT_INPUT) args.input.append(DEFAULT_ABSTRACT_INPUT) + else: + args.base.append(args.input[-1]) + args.input.pop() abstract = analyze_files(args.input) base = analyze_files(args.base) with open(args.output, "w") as outfile: diff --git a/Tools/cases_generator/tier1_generator.py b/Tools/cases_generator/tier1_generator.py index aba36ec74e5766e..fb2ab931b1c1080 100644 --- a/Tools/cases_generator/tier1_generator.py +++ b/Tools/cases_generator/tier1_generator.py @@ -87,6 +87,8 @@ def write_uop( out.emit( f"{type}{cache.name} = {reader}(&this_instr[{offset}].cache);\n" ) + if inst.family is None: + out.emit(f"(void){cache.name};\n") offset += cache.size emit_tokens(out, uop, stack, inst) if uop.properties.stores_sp: @@ -131,8 +133,10 @@ def generate_tier1( needs_this = uses_this(inst) out.emit("\n") out.emit(f"TARGET({name}) {{\n") + unused_guard = "(void)this_instr;\n" if inst.family is None else "" if needs_this and not inst.is_target: out.emit(f"_Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr;\n") + out.emit(unused_guard) else: out.emit(f"frame->instr_ptr = next_instr;\n") out.emit(f"next_instr += {inst.size};\n") @@ -141,6 +145,7 @@ def generate_tier1( out.emit(f"PREDICTED({name});\n") if needs_this: out.emit(f"_Py_CODEUNIT *this_instr = next_instr - {inst.size};\n") + out.emit(unused_guard) if inst.family is not None: out.emit( f"static_assert({inst.family.size} == {inst.size-1}" diff --git a/Tools/cases_generator/tier2_generator.py b/Tools/cases_generator/tier2_generator.py index 6fbe5c355c90831..d8eed1078b09149 100644 --- a/Tools/cases_generator/tier2_generator.py +++ b/Tools/cases_generator/tier2_generator.py @@ -194,7 +194,7 @@ def generate_tier2( out = CWriter(outfile, 2, lines) out.emit("\n") for name, uop in analysis.uops.items(): - if uop.properties.tier_one_only: + if uop.properties.tier == 1: continue if uop.properties.oparg_and_1: out.emit(f"/* {uop.name} is split on (oparg & 1) */\n\n") diff --git a/Tools/cases_generator/uop_id_generator.py b/Tools/cases_generator/uop_id_generator.py index 907158f27959597..eb5e3f4a324735e 100644 --- a/Tools/cases_generator/uop_id_generator.py +++ b/Tools/cases_generator/uop_id_generator.py @@ -43,7 +43,7 @@ def generate_uop_ids( for name, uop in sorted(uops): if name in PRE_DEFINED: continue - if uop.properties.tier_one_only: + if uop.properties.tier == 1: continue if uop.implicitly_created and not distinct_namespace and not uop.replicated: out.emit(f"#define {name} {name[1:]}\n") diff --git a/Tools/cases_generator/uop_metadata_generator.py b/Tools/cases_generator/uop_metadata_generator.py index f85f1c6ce9c817a..72eed3041c55c9c 100644 --- a/Tools/cases_generator/uop_metadata_generator.py +++ b/Tools/cases_generator/uop_metadata_generator.py @@ -29,7 +29,7 @@ def generate_names_and_flags(analysis: Analysis, out: CWriter) -> None: out.emit("#ifdef NEED_OPCODE_METADATA\n") out.emit("const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {\n") for uop in analysis.uops.values(): - if uop.is_viable() and not uop.properties.tier_one_only: + if uop.is_viable() and uop.properties.tier != 1: out.emit(f"[{uop.name}] = {cflags(uop.properties)},\n") out.emit("};\n\n") @@ -41,7 +41,7 @@ def generate_names_and_flags(analysis: Analysis, out: CWriter) -> None: out.emit("};\n\n") out.emit("const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = {\n") for uop in sorted(analysis.uops.values(), key=lambda t: t.name): - if uop.is_viable() and not uop.properties.tier_one_only: + if uop.is_viable() and uop.properties.tier != 1: out.emit(f'[{uop.name}] = "{uop.name}",\n') out.emit("};\n") out.emit("#endif // NEED_OPCODE_METADATA\n\n") diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 7906e7c95d17ba1..c9641cb9c82bf79 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -6,13 +6,10 @@ # from __future__ import annotations -import abc import argparse import ast import builtins as bltns -import collections import contextlib -import copy import dataclasses as dc import enum import functools @@ -35,23 +32,32 @@ from operator import attrgetter from types import FunctionType, NoneType from typing import ( - TYPE_CHECKING, Any, Final, Literal, NamedTuple, NoReturn, Protocol, - TypeVar, - cast, - overload, ) # Local imports. import libclinic import libclinic.cpp -from libclinic import ClinicError +from libclinic import ( + ClinicError, Sentinels, VersionTuple, + fail, warn, unspecified, unknown) +from libclinic.function import ( + Module, Class, Function, Parameter, + ClassDict, ModuleDict, FunctionKind, + CALLABLE, STATIC_METHOD, CLASS_METHOD, METHOD_INIT, METHOD_NEW, + GETTER, SETTER) +from libclinic.language import Language, PythonLanguage +from libclinic.block_parser import Block, BlockParser +from libclinic.crenderdata import CRenderData, Include, TemplateDict +from libclinic.converter import ( + CConverter, CConverterClassT, + converters, legacy_converters) # TODO: @@ -66,20 +72,9 @@ # -# match '#define Py_LIMITED_API' -LIMITED_CAPI_REGEX = re.compile(r'#define +Py_LIMITED_API') - - -class Sentinels(enum.Enum): - unspecified = "unspecified" - unknown = "unknown" - - def __repr__(self) -> str: - return f"<{self.value.capitalize()}>" - - -unspecified: Final = Sentinels.unspecified -unknown: Final = Sentinels.unknown +# Match '#define Py_LIMITED_API'. +# Match '# define Py_LIMITED_API 0x030d0000' (without the version). +LIMITED_CAPI_REGEX = re.compile(r'# *define +Py_LIMITED_API') # This one needs to be a distinct class, unlike the other two @@ -90,200 +85,6 @@ def __repr__(self) -> str: NULL = Null() -TemplateDict = dict[str, str] - - -@overload -def warn_or_fail( - *args: object, - fail: Literal[True], - filename: str | None = None, - line_number: int | None = None, -) -> NoReturn: ... - -@overload -def warn_or_fail( - *args: object, - fail: Literal[False] = False, - filename: str | None = None, - line_number: int | None = None, -) -> None: ... - -def warn_or_fail( - *args: object, - fail: bool = False, - filename: str | None = None, - line_number: int | None = None, -) -> None: - joined = " ".join([str(a) for a in args]) - error = ClinicError(joined, filename=filename, lineno=line_number) - if fail: - raise error - else: - print(error.report(warn_only=True)) - - -def warn( - *args: object, - filename: str | None = None, - line_number: int | None = None, -) -> None: - return warn_or_fail(*args, filename=filename, line_number=line_number, fail=False) - -def fail( - *args: object, - filename: str | None = None, - line_number: int | None = None, -) -> NoReturn: - warn_or_fail(*args, filename=filename, line_number=line_number, fail=True) - - -class CRenderData: - def __init__(self) -> None: - - # The C statements to declare variables. - # Should be full lines with \n eol characters. - self.declarations: list[str] = [] - - # The C statements required to initialize the variables before the parse call. - # Should be full lines with \n eol characters. - self.initializers: list[str] = [] - - # The C statements needed to dynamically modify the values - # parsed by the parse call, before calling the impl. - self.modifications: list[str] = [] - - # The entries for the "keywords" array for PyArg_ParseTuple. - # Should be individual strings representing the names. - self.keywords: list[str] = [] - - # The "format units" for PyArg_ParseTuple. - # Should be individual strings that will get - self.format_units: list[str] = [] - - # The varargs arguments for PyArg_ParseTuple. - self.parse_arguments: list[str] = [] - - # The parameter declarations for the impl function. - self.impl_parameters: list[str] = [] - - # The arguments to the impl function at the time it's called. - self.impl_arguments: list[str] = [] - - # For return converters: the name of the variable that - # should receive the value returned by the impl. - self.return_value = "return_value" - - # For return converters: the code to convert the return - # value from the parse function. This is also where - # you should check the _return_value for errors, and - # "goto exit" if there are any. - self.return_conversion: list[str] = [] - self.converter_retval = "_return_value" - - # The C statements required to do some operations - # after the end of parsing but before cleaning up. - # These operations may be, for example, memory deallocations which - # can only be done without any error happening during argument parsing. - self.post_parsing: list[str] = [] - - # The C statements required to clean up after the impl call. - self.cleanup: list[str] = [] - - # The C statements to generate critical sections (per-object locking). - self.lock: list[str] = [] - self.unlock: list[str] = [] - - -class Language(metaclass=abc.ABCMeta): - - start_line = "" - body_prefix = "" - stop_line = "" - checksum_line = "" - - def __init__(self, filename: str) -> None: - self.filename = filename - - @abc.abstractmethod - def render( - self, - clinic: Clinic, - signatures: Iterable[Module | Class | Function] - ) -> str: - ... - - def parse_line(self, line: str) -> None: - ... - - def validate(self) -> None: - def assert_only_one( - attr: str, - *additional_fields: str - ) -> None: - """ - Ensures that the string found at getattr(self, attr) - contains exactly one formatter replacement string for - each valid field. The list of valid fields is - ['dsl_name'] extended by additional_fields. - - e.g. - self.fmt = "{dsl_name} {a} {b}" - - # this passes - self.assert_only_one('fmt', 'a', 'b') - - # this fails, the format string has a {b} in it - self.assert_only_one('fmt', 'a') - - # this fails, the format string doesn't have a {c} in it - self.assert_only_one('fmt', 'a', 'b', 'c') - - # this fails, the format string has two {a}s in it, - # it must contain exactly one - self.fmt2 = '{dsl_name} {a} {a}' - self.assert_only_one('fmt2', 'a') - - """ - fields = ['dsl_name'] - fields.extend(additional_fields) - line: str = getattr(self, attr) - fcf = libclinic.FormatCounterFormatter() - fcf.format(line) - def local_fail(should_be_there_but_isnt: bool) -> None: - if should_be_there_but_isnt: - fail("{} {} must contain {{{}}} exactly once!".format( - self.__class__.__name__, attr, name)) - else: - fail("{} {} must not contain {{{}}}!".format( - self.__class__.__name__, attr, name)) - - for name, count in fcf.counts.items(): - if name in fields: - if count > 1: - local_fail(True) - else: - local_fail(False) - for name in fields: - if fcf.counts.get(name) != 1: - local_fail(True) - - assert_only_one('start_line') - assert_only_one('stop_line') - - field = "arguments" if "{arguments}" in self.checksum_line else "checksum" - assert_only_one('checksum_line', field) - - - -class PythonLanguage(Language): - - language = 'Python' - start_line = "#/*[{dsl_name} input]" - body_prefix = "#" - stop_line = "#[{dsl_name} start generated code]*/" - checksum_line = "#/*[{dsl_name} end generated code: {arguments}]*/" - ParamTuple = tuple["Parameter", ...] @@ -859,9 +660,6 @@ def parser_body( limited_capi = False parsearg: str | None - if f.kind in {GETTER, SETTER} and parameters: - fail(f"@{f.kind.name.lower()} method cannot define parameters") - if not parameters: parser_code: list[str] | None if f.kind is GETTER: @@ -932,6 +730,7 @@ def parser_body( displayname = parameters[0].get_displayname(0) parsearg = converters[0].parse_arg(argname, displayname, limited_capi=limited_capi) if parsearg is None: + converters[0].use_converter() parsearg = """ if (!PyArg_Parse(%s, "{format_units}:{name}", {parse_arguments})) {{ goto exit; @@ -1064,6 +863,9 @@ def parser_body( if has_optional: parser_code.append("skip_optional:") else: + for parameter in parameters: + parameter.converter.use_converter() + if limited_capi: fastcall = False if fastcall: @@ -1232,6 +1034,9 @@ def parser_body( if add_label: parser_code.append("%s:" % add_label) else: + for parameter in parameters: + parameter.converter.use_converter() + declarations = declare_parser(f, clinic=clinic, hasformat=True, limited_capi=limited_capi) @@ -1615,12 +1420,9 @@ def render_function( for converter in converters: converter.set_template_dict(template_dict) - f.return_converter.render(f, data) - if f.kind is SETTER: - # All setters return an int. - template_dict['impl_return_type'] = 'int' - else: - template_dict['impl_return_type'] = f.return_converter.type + if f.kind not in {SETTER, METHOD_INIT}: + f.return_converter.render(f, data) + template_dict['impl_return_type'] = f.return_converter.type template_dict['declarations'] = libclinic.format_escape("\n".join(data.declarations)) template_dict['initializers'] = "\n\n".join(data.initializers) @@ -1696,270 +1498,6 @@ def render_function( return clinic.get_destination('block').dump() -@dc.dataclass(slots=True, repr=False) -class Block: - r""" - Represents a single block of text embedded in - another file. If dsl_name is None, the block represents - verbatim text, raw original text from the file, in - which case "input" will be the only non-false member. - If dsl_name is not None, the block represents a Clinic - block. - - input is always str, with embedded \n characters. - input represents the original text from the file; - if it's a Clinic block, it is the original text with - the body_prefix and redundant leading whitespace removed. - - dsl_name is either str or None. If str, it's the text - found on the start line of the block between the square - brackets. - - signatures is a list. - It may only contain clinic.Module, clinic.Class, and - clinic.Function objects. At the moment it should - contain at most one of each. - - output is either str or None. If str, it's the output - from this block, with embedded '\n' characters. - - indent is a str. It's the leading whitespace - that was found on every line of input. (If body_prefix is - not empty, this is the indent *after* removing the - body_prefix.) - - "indent" is different from the concept of "preindent" - (which is not stored as state on Block objects). - "preindent" is the whitespace that - was found in front of every line of input *before* the - "body_prefix" (see the Language object). If body_prefix - is empty, preindent must always be empty too. - - To illustrate the difference between "indent" and "preindent": - - Assume that '_' represents whitespace. - If the block processed was in a Python file, and looked like this: - ____#/*[python] - ____#__for a in range(20): - ____#____print(a) - ____#[python]*/ - "preindent" would be "____" and "indent" would be "__". - - """ - input: str - dsl_name: str | None = None - signatures: list[Module | Class | Function] = dc.field(default_factory=list) - output: Any = None # TODO: Very dynamic; probably untypeable in its current form? - indent: str = '' - - def __repr__(self) -> str: - dsl_name = self.dsl_name or "text" - def summarize(s: object) -> str: - s = repr(s) - if len(s) > 30: - return s[:26] + "..." + s[0] - return s - parts = ( - repr(dsl_name), - f"input={summarize(self.input)}", - f"output={summarize(self.output)}" - ) - return f"" - - -class BlockParser: - """ - Block-oriented parser for Argument Clinic. - Iterator, yields Block objects. - """ - - def __init__( - self, - input: str, - language: Language, - *, - verify: bool = True - ) -> None: - """ - "input" should be a str object - with embedded \n characters. - - "language" should be a Language object. - """ - language.validate() - - self.input = collections.deque(reversed(input.splitlines(keepends=True))) - self.block_start_line_number = self.line_number = 0 - - self.language = language - before, _, after = language.start_line.partition('{dsl_name}') - assert _ == '{dsl_name}' - self.find_start_re = libclinic.create_regex(before, after, - whole_line=False) - self.start_re = libclinic.create_regex(before, after) - self.verify = verify - self.last_checksum_re: re.Pattern[str] | None = None - self.last_dsl_name: str | None = None - self.dsl_name: str | None = None - self.first_block = True - - def __iter__(self) -> BlockParser: - return self - - def __next__(self) -> Block: - while True: - if not self.input: - raise StopIteration - - if self.dsl_name: - try: - return_value = self.parse_clinic_block(self.dsl_name) - except ClinicError as exc: - exc.filename = self.language.filename - exc.lineno = self.line_number - raise - self.dsl_name = None - self.first_block = False - return return_value - block = self.parse_verbatim_block() - if self.first_block and not block.input: - continue - self.first_block = False - return block - - - def is_start_line(self, line: str) -> str | None: - match = self.start_re.match(line.lstrip()) - return match.group(1) if match else None - - def _line(self, lookahead: bool = False) -> str: - self.line_number += 1 - line = self.input.pop() - if not lookahead: - self.language.parse_line(line) - return line - - def parse_verbatim_block(self) -> Block: - lines = [] - self.block_start_line_number = self.line_number - - while self.input: - line = self._line() - dsl_name = self.is_start_line(line) - if dsl_name: - self.dsl_name = dsl_name - break - lines.append(line) - - return Block("".join(lines)) - - def parse_clinic_block(self, dsl_name: str) -> Block: - in_lines = [] - self.block_start_line_number = self.line_number + 1 - stop_line = self.language.stop_line.format(dsl_name=dsl_name) - body_prefix = self.language.body_prefix.format(dsl_name=dsl_name) - - def is_stop_line(line: str) -> bool: - # make sure to recognize stop line even if it - # doesn't end with EOL (it could be the very end of the file) - if line.startswith(stop_line): - remainder = line.removeprefix(stop_line) - if remainder and not remainder.isspace(): - fail(f"Garbage after stop line: {remainder!r}") - return True - else: - # gh-92256: don't allow incorrectly formatted stop lines - if line.lstrip().startswith(stop_line): - fail(f"Whitespace is not allowed before the stop line: {line!r}") - return False - - # consume body of program - while self.input: - line = self._line() - if is_stop_line(line) or self.is_start_line(line): - break - if body_prefix: - line = line.lstrip() - assert line.startswith(body_prefix) - line = line.removeprefix(body_prefix) - in_lines.append(line) - - # consume output and checksum line, if present. - if self.last_dsl_name == dsl_name: - checksum_re = self.last_checksum_re - else: - before, _, after = self.language.checksum_line.format(dsl_name=dsl_name, arguments='{arguments}').partition('{arguments}') - assert _ == '{arguments}' - checksum_re = libclinic.create_regex(before, after, word=False) - self.last_dsl_name = dsl_name - self.last_checksum_re = checksum_re - assert checksum_re is not None - - # scan forward for checksum line - out_lines = [] - arguments = None - while self.input: - line = self._line(lookahead=True) - match = checksum_re.match(line.lstrip()) - arguments = match.group(1) if match else None - if arguments: - break - out_lines.append(line) - if self.is_start_line(line): - break - - output: str | None - output = "".join(out_lines) - if arguments: - d = {} - for field in shlex.split(arguments): - name, equals, value = field.partition('=') - if not equals: - fail(f"Mangled Argument Clinic marker line: {line!r}") - d[name.strip()] = value.strip() - - if self.verify: - if 'input' in d: - checksum = d['output'] - else: - checksum = d['checksum'] - - computed = libclinic.compute_checksum(output, len(checksum)) - if checksum != computed: - fail("Checksum mismatch! " - f"Expected {checksum!r}, computed {computed!r}. " - "Suggested fix: remove all generated code including " - "the end marker, or use the '-f' option.") - else: - # put back output - output_lines = output.splitlines(keepends=True) - self.line_number -= len(output_lines) - self.input.extend(reversed(output_lines)) - output = None - - return Block("".join(in_lines), dsl_name, output=output) - - -@dc.dataclass(slots=True, frozen=True) -class Include: - """ - An include like: #include "pycore_long.h" // _Py_ID() - """ - # Example: "pycore_long.h". - filename: str - - # Example: "_Py_ID()". - reason: str - - # None means unconditional include. - # Example: "#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)". - condition: str | None - - def sort_key(self) -> tuple[str, str]: - # order: '#if' comes before 'NO_CONDITION' - return (self.condition or 'NO_CONDITION', self.filename) - - @dc.dataclass(slots=True) class BlockPrinter: language: Language @@ -2140,9 +1678,7 @@ def dump(self) -> str: extensions['py'] = PythonLanguage -ClassDict = dict[str, "Class"] DestinationDict = dict[str, Destination] -ModuleDict = dict[str, "Module"] class Parser(Protocol): @@ -2462,38 +1998,6 @@ def parse(self, block: Block) -> None: block.output = s.getvalue() -@dc.dataclass(repr=False) -class Module: - name: str - module: Module | Clinic - - def __post_init__(self) -> None: - self.parent = self.module - self.modules: ModuleDict = {} - self.classes: ClassDict = {} - self.functions: list[Function] = [] - - def __repr__(self) -> str: - return "" - - -@dc.dataclass(repr=False) -class Class: - name: str - module: Module | Clinic - cls: Class | None - typedef: str - type_object: str - - def __post_init__(self) -> None: - self.parent = self.cls or self.module - self.classes: ClassDict = {} - self.functions: list[Function] = [] - - def __repr__(self) -> str: - return "" - - unsupported_special_methods: set[str] = set(""" __abs__ @@ -2566,224 +2070,9 @@ def __repr__(self) -> str: """.strip().split()) -class FunctionKind(enum.Enum): - INVALID = enum.auto() - CALLABLE = enum.auto() - STATIC_METHOD = enum.auto() - CLASS_METHOD = enum.auto() - METHOD_INIT = enum.auto() - METHOD_NEW = enum.auto() - GETTER = enum.auto() - SETTER = enum.auto() - - @functools.cached_property - def new_or_init(self) -> bool: - return self in {FunctionKind.METHOD_INIT, FunctionKind.METHOD_NEW} - - def __repr__(self) -> str: - return f"" - - -INVALID: Final = FunctionKind.INVALID -CALLABLE: Final = FunctionKind.CALLABLE -STATIC_METHOD: Final = FunctionKind.STATIC_METHOD -CLASS_METHOD: Final = FunctionKind.CLASS_METHOD -METHOD_INIT: Final = FunctionKind.METHOD_INIT -METHOD_NEW: Final = FunctionKind.METHOD_NEW -GETTER: Final = FunctionKind.GETTER -SETTER: Final = FunctionKind.SETTER - -ParamDict = dict[str, "Parameter"] ReturnConverterType = Callable[..., "CReturnConverter"] -@dc.dataclass(repr=False) -class Function: - """ - Mutable duck type for inspect.Function. - - docstring - a str containing - * embedded line breaks - * text outdented to the left margin - * no trailing whitespace. - It will always be true that - (not docstring) or ((not docstring[0].isspace()) and (docstring.rstrip() == docstring)) - """ - parameters: ParamDict = dc.field(default_factory=dict) - _: dc.KW_ONLY - name: str - module: Module | Clinic - cls: Class | None - c_basename: str - full_name: str - return_converter: CReturnConverter - kind: FunctionKind - coexist: bool - return_annotation: object = inspect.Signature.empty - docstring: str = '' - # docstring_only means "don't generate a machine-readable - # signature, just a normal docstring". it's True for - # functions with optional groups because we can't represent - # those accurately with inspect.Signature in 3.4. - docstring_only: bool = False - critical_section: bool = False - target_critical_section: list[str] = dc.field(default_factory=list) - - def __post_init__(self) -> None: - self.parent = self.cls or self.module - self.self_converter: self_converter | None = None - self.__render_parameters__: list[Parameter] | None = None - - @functools.cached_property - def displayname(self) -> str: - """Pretty-printable name.""" - if self.kind.new_or_init: - assert isinstance(self.cls, Class) - return self.cls.name - else: - return self.name - - @functools.cached_property - def fulldisplayname(self) -> str: - parent: Class | Module | Clinic | None - if self.kind.new_or_init: - parent = getattr(self.cls, "parent", None) - else: - parent = self.parent - name = self.displayname - while isinstance(parent, (Module, Class)): - name = f"{parent.name}.{name}" - parent = parent.parent - return name - - @property - def render_parameters(self) -> list[Parameter]: - if not self.__render_parameters__: - l: list[Parameter] = [] - self.__render_parameters__ = l - for p in self.parameters.values(): - p = p.copy() - p.converter.pre_render() - l.append(p) - return self.__render_parameters__ - - @property - def methoddef_flags(self) -> str | None: - if self.kind.new_or_init: - return None - flags = [] - match self.kind: - case FunctionKind.CLASS_METHOD: - flags.append('METH_CLASS') - case FunctionKind.STATIC_METHOD: - flags.append('METH_STATIC') - case _ as kind: - acceptable_kinds = {FunctionKind.CALLABLE, FunctionKind.GETTER, FunctionKind.SETTER} - assert kind in acceptable_kinds, f"unknown kind: {kind!r}" - if self.coexist: - flags.append('METH_COEXIST') - return '|'.join(flags) - - def __repr__(self) -> str: - return f'' - - def copy(self, **overrides: Any) -> Function: - f = dc.replace(self, **overrides) - f.parameters = { - name: value.copy(function=f) - for name, value in f.parameters.items() - } - return f - - -VersionTuple = tuple[int, int] - - -@dc.dataclass(repr=False, slots=True) -class Parameter: - """ - Mutable duck type of inspect.Parameter. - """ - name: str - kind: inspect._ParameterKind - _: dc.KW_ONLY - default: object = inspect.Parameter.empty - function: Function - converter: CConverter - annotation: object = inspect.Parameter.empty - docstring: str = '' - group: int = 0 - # (`None` signifies that there is no deprecation) - deprecated_positional: VersionTuple | None = None - deprecated_keyword: VersionTuple | None = None - right_bracket_count: int = dc.field(init=False, default=0) - - def __repr__(self) -> str: - return f'' - - def is_keyword_only(self) -> bool: - return self.kind == inspect.Parameter.KEYWORD_ONLY - - def is_positional_only(self) -> bool: - return self.kind == inspect.Parameter.POSITIONAL_ONLY - - def is_vararg(self) -> bool: - return self.kind == inspect.Parameter.VAR_POSITIONAL - - def is_optional(self) -> bool: - return not self.is_vararg() and (self.default is not unspecified) - - def copy( - self, - /, - *, - converter: CConverter | None = None, - function: Function | None = None, - **overrides: Any - ) -> Parameter: - function = function or self.function - if not converter: - converter = copy.copy(self.converter) - converter.function = function - return dc.replace(self, **overrides, function=function, converter=converter) - - def get_displayname(self, i: int) -> str: - if i == 0: - return 'argument' - if not self.is_positional_only(): - return f'argument {self.name!r}' - else: - return f'argument {i}' - - def render_docstring(self) -> str: - lines = [f" {self.name}"] - lines.extend(f" {line}" for line in self.docstring.split("\n")) - return "\n".join(lines).rstrip() - - -CConverterClassT = TypeVar("CConverterClassT", bound=type["CConverter"]) - -def add_c_converter( - f: CConverterClassT, - name: str | None = None -) -> CConverterClassT: - if not name: - name = f.__name__ - if not name.endswith('_converter'): - return f - name = name.removesuffix('_converter') - converters[name] = f - return f - -def add_default_legacy_c_converter(cls: CConverterClassT) -> CConverterClassT: - # automatically add converter for default format unit - # (but without stomping on the existing one if it's already - # set, in case you subclass) - if ((cls.format_unit not in ('O&', '')) and - (cls.format_unit not in legacy_converters)): - legacy_converters[cls.format_unit] = cls - return cls - def add_legacy_c_converter( format_unit: str, **kwargs: Any @@ -2802,496 +2091,6 @@ def closure(f: CConverterClassT) -> CConverterClassT: return f return closure -class CConverterAutoRegister(type): - def __init__( - cls, name: str, bases: tuple[type[object], ...], classdict: dict[str, Any] - ) -> None: - converter_cls = cast(type["CConverter"], cls) - add_c_converter(converter_cls) - add_default_legacy_c_converter(converter_cls) - -class CConverter(metaclass=CConverterAutoRegister): - """ - For the init function, self, name, function, and default - must be keyword-or-positional parameters. All other - parameters must be keyword-only. - """ - - # The C name to use for this variable. - name: str - - # The Python name to use for this variable. - py_name: str - - # The C type to use for this variable. - # 'type' should be a Python string specifying the type, e.g. "int". - # If this is a pointer type, the type string should end with ' *'. - type: str | None = None - - # The Python default value for this parameter, as a Python value. - # Or the magic value "unspecified" if there is no default. - # Or the magic value "unknown" if this value is a cannot be evaluated - # at Argument-Clinic-preprocessing time (but is presumed to be valid - # at runtime). - default: object = unspecified - - # If not None, default must be isinstance() of this type. - # (You can also specify a tuple of types.) - default_type: bltns.type[object] | tuple[bltns.type[object], ...] | None = None - - # "default" converted into a C value, as a string. - # Or None if there is no default. - c_default: str | None = None - - # "default" converted into a Python value, as a string. - # Or None if there is no default. - py_default: str | None = None - - # The default value used to initialize the C variable when - # there is no default, but not specifying a default may - # result in an "uninitialized variable" warning. This can - # easily happen when using option groups--although - # properly-written code won't actually use the variable, - # the variable does get passed in to the _impl. (Ah, if - # only dataflow analysis could inline the static function!) - # - # This value is specified as a string. - # Every non-abstract subclass should supply a valid value. - c_ignored_default: str = 'NULL' - - # If true, wrap with Py_UNUSED. - unused = False - - # The C converter *function* to be used, if any. - # (If this is not None, format_unit must be 'O&'.) - converter: str | None = None - - # Should Argument Clinic add a '&' before the name of - # the variable when passing it into the _impl function? - impl_by_reference = False - - # Should Argument Clinic add a '&' before the name of - # the variable when passing it into PyArg_ParseTuple (AndKeywords)? - parse_by_reference = True - - ############################################################# - ############################################################# - ## You shouldn't need to read anything below this point to ## - ## write your own converter functions. ## - ############################################################# - ############################################################# - - # The "format unit" to specify for this variable when - # parsing arguments using PyArg_ParseTuple (AndKeywords). - # Custom converters should always use the default value of 'O&'. - format_unit = 'O&' - - # What encoding do we want for this variable? Only used - # by format units starting with 'e'. - encoding: str | None = None - - # Should this object be required to be a subclass of a specific type? - # If not None, should be a string representing a pointer to a - # PyTypeObject (e.g. "&PyUnicode_Type"). - # Only used by the 'O!' format unit (and the "object" converter). - subclass_of: str | None = None - - # See also the 'length_name' property. - # Only used by format units ending with '#'. - length = False - - # Should we show this parameter in the generated - # __text_signature__? This is *almost* always True. - # (It's only False for __new__, __init__, and METH_STATIC functions.) - show_in_signature = True - - # Overrides the name used in a text signature. - # The name used for a "self" parameter must be one of - # self, type, or module; however users can set their own. - # This lets the self_converter overrule the user-settable - # name, *just* for the text signature. - # Only set by self_converter. - signature_name: str | None = None - - broken_limited_capi: bool = False - - # keep in sync with self_converter.__init__! - def __init__(self, - # Positional args: - name: str, - py_name: str, - function: Function, - default: object = unspecified, - *, # Keyword only args: - c_default: str | None = None, - py_default: str | None = None, - annotation: str | Literal[Sentinels.unspecified] = unspecified, - unused: bool = False, - **kwargs: Any - ) -> None: - self.name = libclinic.ensure_legal_c_identifier(name) - self.py_name = py_name - self.unused = unused - self.includes: list[Include] = [] - - if default is not unspecified: - if (self.default_type - and default is not unknown - and not isinstance(default, self.default_type) - ): - if isinstance(self.default_type, type): - types_str = self.default_type.__name__ - else: - names = [cls.__name__ for cls in self.default_type] - types_str = ', '.join(names) - cls_name = self.__class__.__name__ - fail(f"{cls_name}: default value {default!r} for field " - f"{name!r} is not of type {types_str!r}") - self.default = default - - if c_default: - self.c_default = c_default - if py_default: - self.py_default = py_default - - if annotation is not unspecified: - fail("The 'annotation' parameter is not currently permitted.") - - # Make sure not to set self.function until after converter_init() has been called. - # This prevents you from caching information - # about the function in converter_init(). - # (That breaks if we get cloned.) - self.converter_init(**kwargs) - self.function = function - - # Add a custom __getattr__ method to improve the error message - # if somebody tries to access self.function in converter_init(). - # - # mypy will assume arbitrary access is okay for a class with a __getattr__ method, - # and that's not what we want, - # so put it inside an `if not TYPE_CHECKING` block - if not TYPE_CHECKING: - def __getattr__(self, attr): - if attr == "function": - fail( - f"{self.__class__.__name__!r} object has no attribute 'function'.\n" - f"Note: accessing self.function inside converter_init is disallowed!" - ) - return super().__getattr__(attr) - # this branch is just here for coverage reporting - else: # pragma: no cover - pass - - def converter_init(self) -> None: - pass - - def is_optional(self) -> bool: - return (self.default is not unspecified) - - def _render_self(self, parameter: Parameter, data: CRenderData) -> None: - self.parameter = parameter - name = self.parser_name - - # impl_arguments - s = ("&" if self.impl_by_reference else "") + name - data.impl_arguments.append(s) - if self.length: - data.impl_arguments.append(self.length_name) - - # impl_parameters - data.impl_parameters.append(self.simple_declaration(by_reference=self.impl_by_reference)) - if self.length: - data.impl_parameters.append(f"Py_ssize_t {self.length_name}") - - def _render_non_self( - self, - parameter: Parameter, - data: CRenderData - ) -> None: - self.parameter = parameter - name = self.name - - # declarations - d = self.declaration(in_parser=True) - data.declarations.append(d) - - # initializers - initializers = self.initialize() - if initializers: - data.initializers.append('/* initializers for ' + name + ' */\n' + initializers.rstrip()) - - # modifications - modifications = self.modify() - if modifications: - data.modifications.append('/* modifications for ' + name + ' */\n' + modifications.rstrip()) - - # keywords - if parameter.is_vararg(): - pass - elif parameter.is_positional_only(): - data.keywords.append('') - else: - data.keywords.append(parameter.name) - - # format_units - if self.is_optional() and '|' not in data.format_units: - data.format_units.append('|') - if parameter.is_keyword_only() and '$' not in data.format_units: - data.format_units.append('$') - data.format_units.append(self.format_unit) - - # parse_arguments - self.parse_argument(data.parse_arguments) - - # post_parsing - if post_parsing := self.post_parsing(): - data.post_parsing.append('/* Post parse cleanup for ' + name + ' */\n' + post_parsing.rstrip() + '\n') - - # cleanup - cleanup = self.cleanup() - if cleanup: - data.cleanup.append('/* Cleanup for ' + name + ' */\n' + cleanup.rstrip() + "\n") - - def render(self, parameter: Parameter, data: CRenderData) -> None: - """ - parameter is a clinic.Parameter instance. - data is a CRenderData instance. - """ - self._render_self(parameter, data) - self._render_non_self(parameter, data) - - @functools.cached_property - def length_name(self) -> str: - """Computes the name of the associated "length" variable.""" - assert self.length is not None - return self.parser_name + "_length" - - # Why is this one broken out separately? - # For "positional-only" function parsing, - # which generates a bunch of PyArg_ParseTuple calls. - def parse_argument(self, args: list[str]) -> None: - assert not (self.converter and self.encoding) - if self.format_unit == 'O&': - assert self.converter - args.append(self.converter) - - if self.encoding: - args.append(libclinic.c_repr(self.encoding)) - elif self.subclass_of: - args.append(self.subclass_of) - - s = ("&" if self.parse_by_reference else "") + self.parser_name - args.append(s) - - if self.length: - args.append(f"&{self.length_name}") - - # - # All the functions after here are intended as extension points. - # - - def simple_declaration( - self, - by_reference: bool = False, - *, - in_parser: bool = False - ) -> str: - """ - Computes the basic declaration of the variable. - Used in computing the prototype declaration and the - variable declaration. - """ - assert isinstance(self.type, str) - prototype = [self.type] - if by_reference or not self.type.endswith('*'): - prototype.append(" ") - if by_reference: - prototype.append('*') - if in_parser: - name = self.parser_name - else: - name = self.name - if self.unused: - name = f"Py_UNUSED({name})" - prototype.append(name) - return "".join(prototype) - - def declaration(self, *, in_parser: bool = False) -> str: - """ - The C statement to declare this variable. - """ - declaration = [self.simple_declaration(in_parser=True)] - default = self.c_default - if not default and self.parameter.group: - default = self.c_ignored_default - if default: - declaration.append(" = ") - declaration.append(default) - declaration.append(";") - if self.length: - declaration.append('\n') - declaration.append(f"Py_ssize_t {self.length_name};") - return "".join(declaration) - - def initialize(self) -> str: - """ - The C statements required to set up this variable before parsing. - Returns a string containing this code indented at column 0. - If no initialization is necessary, returns an empty string. - """ - return "" - - def modify(self) -> str: - """ - The C statements required to modify this variable after parsing. - Returns a string containing this code indented at column 0. - If no modification is necessary, returns an empty string. - """ - return "" - - def post_parsing(self) -> str: - """ - The C statements required to do some operations after the end of parsing but before cleaning up. - Return a string containing this code indented at column 0. - If no operation is necessary, return an empty string. - """ - return "" - - def cleanup(self) -> str: - """ - The C statements required to clean up after this variable. - Returns a string containing this code indented at column 0. - If no cleanup is necessary, returns an empty string. - """ - return "" - - def pre_render(self) -> None: - """ - A second initialization function, like converter_init, - called just before rendering. - You are permitted to examine self.function here. - """ - pass - - def bad_argument(self, displayname: str, expected: str, *, limited_capi: bool, expected_literal: bool = True) -> str: - assert '"' not in expected - if limited_capi: - if expected_literal: - return (f'PyErr_Format(PyExc_TypeError, ' - f'"{{{{name}}}}() {displayname} must be {expected}, not %.50s", ' - f'{{argname}} == Py_None ? "None" : Py_TYPE({{argname}})->tp_name);') - else: - return (f'PyErr_Format(PyExc_TypeError, ' - f'"{{{{name}}}}() {displayname} must be %.50s, not %.50s", ' - f'"{expected}", ' - f'{{argname}} == Py_None ? "None" : Py_TYPE({{argname}})->tp_name);') - else: - if expected_literal: - expected = f'"{expected}"' - self.add_include('pycore_modsupport.h', '_PyArg_BadArgument()') - return f'_PyArg_BadArgument("{{{{name}}}}", "{displayname}", {expected}, {{argname}});' - - def format_code(self, fmt: str, *, - argname: str, - bad_argument: str | None = None, - bad_argument2: str | None = None, - **kwargs: Any) -> str: - if '{bad_argument}' in fmt: - if not bad_argument: - raise TypeError("required 'bad_argument' argument") - fmt = fmt.replace('{bad_argument}', bad_argument) - if '{bad_argument2}' in fmt: - if not bad_argument2: - raise TypeError("required 'bad_argument2' argument") - fmt = fmt.replace('{bad_argument2}', bad_argument2) - return fmt.format(argname=argname, paramname=self.parser_name, **kwargs) - - def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: - if self.format_unit == 'O&': - return self.format_code(""" - if (!{converter}({argname}, &{paramname})) {{{{ - goto exit; - }}}} - """, - argname=argname, - converter=self.converter) - if self.format_unit == 'O!': - cast = '(%s)' % self.type if self.type != 'PyObject *' else '' - if self.subclass_of in type_checks: - typecheck, typename = type_checks[self.subclass_of] - return self.format_code(""" - if (!{typecheck}({argname})) {{{{ - {bad_argument} - goto exit; - }}}} - {paramname} = {cast}{argname}; - """, - argname=argname, - bad_argument=self.bad_argument(displayname, typename, limited_capi=limited_capi), - typecheck=typecheck, typename=typename, cast=cast) - return self.format_code(""" - if (!PyObject_TypeCheck({argname}, {subclass_of})) {{{{ - {bad_argument} - goto exit; - }}}} - {paramname} = {cast}{argname}; - """, - argname=argname, - bad_argument=self.bad_argument(displayname, '({subclass_of})->tp_name', - expected_literal=False, limited_capi=limited_capi), - subclass_of=self.subclass_of, cast=cast) - if self.format_unit == 'O': - cast = '(%s)' % self.type if self.type != 'PyObject *' else '' - return self.format_code(""" - {paramname} = {cast}{argname}; - """, - argname=argname, cast=cast) - return None - - def set_template_dict(self, template_dict: TemplateDict) -> None: - pass - - @property - def parser_name(self) -> str: - if self.name in libclinic.CLINIC_PREFIXED_ARGS: # bpo-39741 - return libclinic.CLINIC_PREFIX + self.name - else: - return self.name - - def add_include(self, name: str, reason: str, - *, condition: str | None = None) -> None: - include = Include(name, reason, condition) - self.includes.append(include) - -type_checks = { - '&PyLong_Type': ('PyLong_Check', 'int'), - '&PyTuple_Type': ('PyTuple_Check', 'tuple'), - '&PyList_Type': ('PyList_Check', 'list'), - '&PySet_Type': ('PySet_Check', 'set'), - '&PyFrozenSet_Type': ('PyFrozenSet_Check', 'frozenset'), - '&PyDict_Type': ('PyDict_Check', 'dict'), - '&PyUnicode_Type': ('PyUnicode_Check', 'str'), - '&PyBytes_Type': ('PyBytes_Check', 'bytes'), - '&PyByteArray_Type': ('PyByteArray_Check', 'bytearray'), -} - - -ConverterType = Callable[..., CConverter] -ConverterDict = dict[str, ConverterType] - -# maps strings to callables. -# these callables must be of the form: -# def foo(name, default, *, ...) -# The callable may have any number of keyword-only parameters. -# The callable must return a CConverter object. -# The callable should not call builtins.print. -converters: ConverterDict = {} - -# maps strings to callables. -# these callables follow the same rules as those for "converters" above. -# note however that they will never be called with keyword-only parameters. -legacy_converters: ConverterDict = {} - # maps strings to callables. # these callables must be of the form: # def foo(*, ...) @@ -3486,6 +2285,9 @@ def converter_init(self, *, bitwise: bool = False) -> None: self.format_unit = 'H' else: self.converter = '_PyLong_UnsignedShort_Converter' + + def use_converter(self) -> None: + if self.converter == '_PyLong_UnsignedShort_Converter': self.add_include('pycore_long.h', '_PyLong_UnsignedShort_Converter()') @@ -3570,6 +2372,9 @@ def converter_init(self, *, bitwise: bool = False) -> None: self.format_unit = 'I' else: self.converter = '_PyLong_UnsignedInt_Converter' + + def use_converter(self) -> None: + if self.converter == '_PyLong_UnsignedInt_Converter': self.add_include('pycore_long.h', '_PyLong_UnsignedInt_Converter()') @@ -3628,6 +2433,9 @@ def converter_init(self, *, bitwise: bool = False) -> None: self.format_unit = 'k' else: self.converter = '_PyLong_UnsignedLong_Converter' + + def use_converter(self) -> None: + if self.converter == '_PyLong_UnsignedLong_Converter': self.add_include('pycore_long.h', '_PyLong_UnsignedLong_Converter()') @@ -3681,6 +2489,9 @@ def converter_init(self, *, bitwise: bool = False) -> None: self.format_unit = 'K' else: self.converter = '_PyLong_UnsignedLongLong_Converter' + + def use_converter(self) -> None: + if self.converter == '_PyLong_UnsignedLongLong_Converter': self.add_include('pycore_long.h', '_PyLong_UnsignedLongLong_Converter()') @@ -3715,20 +2526,23 @@ def converter_init(self, *, accept: TypeSet = {int}) -> None: if accept == {int}: self.format_unit = 'n' self.default_type = int - self.add_include('pycore_abstract.h', '_PyNumber_Index()') elif accept == {int, NoneType}: self.converter = '_Py_convert_optional_to_ssize_t' - self.add_include('pycore_abstract.h', - '_Py_convert_optional_to_ssize_t()') else: fail(f"Py_ssize_t_converter: illegal 'accept' argument {accept!r}") + def use_converter(self) -> None: + if self.converter == '_Py_convert_optional_to_ssize_t': + self.add_include('pycore_abstract.h', + '_Py_convert_optional_to_ssize_t()') + def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'n': if limited_capi: PyNumber_Index = 'PyNumber_Index' else: PyNumber_Index = '_PyNumber_Index' + self.add_include('pycore_abstract.h', '_PyNumber_Index()') return self.format_code(""" {{{{ Py_ssize_t ival = -1; @@ -3822,7 +2636,7 @@ class size_t_converter(CConverter): converter = '_PyLong_Size_t_Converter' c_ignored_default = "0" - def converter_init(self, *, accept: TypeSet = {int, NoneType}) -> None: + def use_converter(self) -> None: self.add_include('pycore_long.h', '_PyLong_Size_t_Converter()') @@ -3851,14 +2665,14 @@ class fildes_converter(CConverter): type = 'int' converter = '_PyLong_FileDescriptor_Converter' - def converter_init(self, *, accept: TypeSet = {int, NoneType}) -> None: + def use_converter(self) -> None: self.add_include('pycore_fileutils.h', '_PyLong_FileDescriptor_Converter()') - def _parse_arg(self, argname: str, displayname: str) -> str | None: + def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: return self.format_code(""" {paramname} = PyObject_AsFileDescriptor({argname}); - if ({paramname} == -1) {{{{ + if ({paramname} < 0) {{{{ goto exit; }}}} """, @@ -3873,19 +2687,28 @@ class float_converter(CConverter): def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'f': - return self.format_code(""" - if (PyFloat_CheckExact({argname})) {{{{ - {paramname} = (float) (PyFloat_AS_DOUBLE({argname})); - }}}} - else - {{{{ + if not limited_capi: + return self.format_code(""" + if (PyFloat_CheckExact({argname})) {{{{ + {paramname} = (float) (PyFloat_AS_DOUBLE({argname})); + }}}} + else + {{{{ + {paramname} = (float) PyFloat_AsDouble({argname}); + if ({paramname} == -1.0 && PyErr_Occurred()) {{{{ + goto exit; + }}}} + }}}} + """, + argname=argname) + else: + return self.format_code(""" {paramname} = (float) PyFloat_AsDouble({argname}); if ({paramname} == -1.0 && PyErr_Occurred()) {{{{ goto exit; }}}} - }}}} - """, - argname=argname) + """, + argname=argname) return super().parse_arg(argname, displayname, limited_capi=limited_capi) class double_converter(CConverter): @@ -3896,19 +2719,28 @@ class double_converter(CConverter): def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: if self.format_unit == 'd': - return self.format_code(""" - if (PyFloat_CheckExact({argname})) {{{{ - {paramname} = PyFloat_AS_DOUBLE({argname}); - }}}} - else - {{{{ + if not limited_capi: + return self.format_code(""" + if (PyFloat_CheckExact({argname})) {{{{ + {paramname} = PyFloat_AS_DOUBLE({argname}); + }}}} + else + {{{{ + {paramname} = PyFloat_AsDouble({argname}); + if ({paramname} == -1.0 && PyErr_Occurred()) {{{{ + goto exit; + }}}} + }}}} + """, + argname=argname) + else: + return self.format_code(""" {paramname} = PyFloat_AsDouble({argname}); if ({paramname} == -1.0 && PyErr_Occurred()) {{{{ goto exit; }}}} - }}}} - """, - argname=argname) + """, + argname=argname) return super().parse_arg(argname, displayname, limited_capi=limited_capi) @@ -4565,20 +3397,6 @@ class int_return_converter(long_return_converter): cast = '(long)' -class init_return_converter(long_return_converter): - """ - Special return converter for __init__ functions. - """ - type = 'int' - cast = '(long)' - - def render( - self, - function: Function, - data: CRenderData - ) -> None: ... - - class unsigned_long_return_converter(long_return_converter): type = 'unsigned long' conversion_fn = 'PyLong_FromUnsignedLong' @@ -5092,6 +3910,8 @@ def resolve_return_converter( if forced_converter: if self.kind in {GETTER, SETTER}: fail(f"@{self.kind.name.lower()} method cannot define a return type") + if self.kind is METHOD_INIT: + fail("__init__ methods cannot define a return type") ast_input = f"def x() -> {forced_converter}: pass" try: module_node = ast.parse(ast_input) @@ -5109,8 +3929,8 @@ def resolve_return_converter( except ValueError: fail(f"Badly formed annotation for {full_name!r}: {forced_converter!r}") - if self.kind is METHOD_INIT: - return init_return_converter() + if self.kind in {METHOD_INIT, SETTER}: + return int_return_converter() return CReturnConverter() def parse_cloned_function(self, names: FunctionNames, existing: str) -> None: @@ -5292,6 +4112,11 @@ def state_parameters_start(self, line: str) -> None: if not self.indent.infer(line): return self.next(self.state_function_docstring, line) + assert self.function is not None + if self.function.kind in {GETTER, SETTER}: + getset = self.function.kind.name.lower() + fail(f"@{getset} methods cannot define parameters") + self.parameter_continuation = '' return self.next(self.state_parameter, line) diff --git a/Tools/clinic/libclinic/__init__.py b/Tools/clinic/libclinic/__init__.py index 738864a48c08d3f..32231b82bfdc076 100644 --- a/Tools/clinic/libclinic/__init__.py +++ b/Tools/clinic/libclinic/__init__.py @@ -2,6 +2,8 @@ from .errors import ( ClinicError, + warn, + fail, ) from .formatting import ( SIG_END_MARKER, @@ -26,12 +28,18 @@ compute_checksum, create_regex, write_file, + VersionTuple, + Sentinels, + unspecified, + unknown, ) __all__ = [ # Error handling "ClinicError", + "warn", + "fail", # Formatting helpers "SIG_END_MARKER", @@ -56,6 +64,10 @@ "compute_checksum", "create_regex", "write_file", + "VersionTuple", + "Sentinels", + "unspecified", + "unknown", ] diff --git a/Tools/clinic/libclinic/block_parser.py b/Tools/clinic/libclinic/block_parser.py new file mode 100644 index 000000000000000..4c0198b53592a94 --- /dev/null +++ b/Tools/clinic/libclinic/block_parser.py @@ -0,0 +1,256 @@ +from __future__ import annotations +import collections +import dataclasses as dc +import re +import shlex +from typing import Any + +import libclinic +from libclinic import fail, ClinicError +from libclinic.language import Language +from libclinic.function import ( + Module, Class, Function) + + +@dc.dataclass(slots=True, repr=False) +class Block: + r""" + Represents a single block of text embedded in + another file. If dsl_name is None, the block represents + verbatim text, raw original text from the file, in + which case "input" will be the only non-false member. + If dsl_name is not None, the block represents a Clinic + block. + + input is always str, with embedded \n characters. + input represents the original text from the file; + if it's a Clinic block, it is the original text with + the body_prefix and redundant leading whitespace removed. + + dsl_name is either str or None. If str, it's the text + found on the start line of the block between the square + brackets. + + signatures is a list. + It may only contain clinic.Module, clinic.Class, and + clinic.Function objects. At the moment it should + contain at most one of each. + + output is either str or None. If str, it's the output + from this block, with embedded '\n' characters. + + indent is a str. It's the leading whitespace + that was found on every line of input. (If body_prefix is + not empty, this is the indent *after* removing the + body_prefix.) + + "indent" is different from the concept of "preindent" + (which is not stored as state on Block objects). + "preindent" is the whitespace that + was found in front of every line of input *before* the + "body_prefix" (see the Language object). If body_prefix + is empty, preindent must always be empty too. + + To illustrate the difference between "indent" and "preindent": + + Assume that '_' represents whitespace. + If the block processed was in a Python file, and looked like this: + ____#/*[python] + ____#__for a in range(20): + ____#____print(a) + ____#[python]*/ + "preindent" would be "____" and "indent" would be "__". + + """ + input: str + dsl_name: str | None = None + signatures: list[Module | Class | Function] = dc.field(default_factory=list) + output: Any = None # TODO: Very dynamic; probably untypeable in its current form? + indent: str = '' + + def __repr__(self) -> str: + dsl_name = self.dsl_name or "text" + def summarize(s: object) -> str: + s = repr(s) + if len(s) > 30: + return s[:26] + "..." + s[0] + return s + parts = ( + repr(dsl_name), + f"input={summarize(self.input)}", + f"output={summarize(self.output)}" + ) + return f"" + + +class BlockParser: + """ + Block-oriented parser for Argument Clinic. + Iterator, yields Block objects. + """ + + def __init__( + self, + input: str, + language: Language, + *, + verify: bool = True + ) -> None: + """ + "input" should be a str object + with embedded \n characters. + + "language" should be a Language object. + """ + language.validate() + + self.input = collections.deque(reversed(input.splitlines(keepends=True))) + self.block_start_line_number = self.line_number = 0 + + self.language = language + before, _, after = language.start_line.partition('{dsl_name}') + assert _ == '{dsl_name}' + self.find_start_re = libclinic.create_regex(before, after, + whole_line=False) + self.start_re = libclinic.create_regex(before, after) + self.verify = verify + self.last_checksum_re: re.Pattern[str] | None = None + self.last_dsl_name: str | None = None + self.dsl_name: str | None = None + self.first_block = True + + def __iter__(self) -> BlockParser: + return self + + def __next__(self) -> Block: + while True: + if not self.input: + raise StopIteration + + if self.dsl_name: + try: + return_value = self.parse_clinic_block(self.dsl_name) + except ClinicError as exc: + exc.filename = self.language.filename + exc.lineno = self.line_number + raise + self.dsl_name = None + self.first_block = False + return return_value + block = self.parse_verbatim_block() + if self.first_block and not block.input: + continue + self.first_block = False + return block + + + def is_start_line(self, line: str) -> str | None: + match = self.start_re.match(line.lstrip()) + return match.group(1) if match else None + + def _line(self, lookahead: bool = False) -> str: + self.line_number += 1 + line = self.input.pop() + if not lookahead: + self.language.parse_line(line) + return line + + def parse_verbatim_block(self) -> Block: + lines = [] + self.block_start_line_number = self.line_number + + while self.input: + line = self._line() + dsl_name = self.is_start_line(line) + if dsl_name: + self.dsl_name = dsl_name + break + lines.append(line) + + return Block("".join(lines)) + + def parse_clinic_block(self, dsl_name: str) -> Block: + in_lines = [] + self.block_start_line_number = self.line_number + 1 + stop_line = self.language.stop_line.format(dsl_name=dsl_name) + body_prefix = self.language.body_prefix.format(dsl_name=dsl_name) + + def is_stop_line(line: str) -> bool: + # make sure to recognize stop line even if it + # doesn't end with EOL (it could be the very end of the file) + if line.startswith(stop_line): + remainder = line.removeprefix(stop_line) + if remainder and not remainder.isspace(): + fail(f"Garbage after stop line: {remainder!r}") + return True + else: + # gh-92256: don't allow incorrectly formatted stop lines + if line.lstrip().startswith(stop_line): + fail(f"Whitespace is not allowed before the stop line: {line!r}") + return False + + # consume body of program + while self.input: + line = self._line() + if is_stop_line(line) or self.is_start_line(line): + break + if body_prefix: + line = line.lstrip() + assert line.startswith(body_prefix) + line = line.removeprefix(body_prefix) + in_lines.append(line) + + # consume output and checksum line, if present. + if self.last_dsl_name == dsl_name: + checksum_re = self.last_checksum_re + else: + before, _, after = self.language.checksum_line.format(dsl_name=dsl_name, arguments='{arguments}').partition('{arguments}') + assert _ == '{arguments}' + checksum_re = libclinic.create_regex(before, after, word=False) + self.last_dsl_name = dsl_name + self.last_checksum_re = checksum_re + assert checksum_re is not None + + # scan forward for checksum line + out_lines = [] + arguments = None + while self.input: + line = self._line(lookahead=True) + match = checksum_re.match(line.lstrip()) + arguments = match.group(1) if match else None + if arguments: + break + out_lines.append(line) + if self.is_start_line(line): + break + + output: str | None + output = "".join(out_lines) + if arguments: + d = {} + for field in shlex.split(arguments): + name, equals, value = field.partition('=') + if not equals: + fail(f"Mangled Argument Clinic marker line: {line!r}") + d[name.strip()] = value.strip() + + if self.verify: + if 'input' in d: + checksum = d['output'] + else: + checksum = d['checksum'] + + computed = libclinic.compute_checksum(output, len(checksum)) + if checksum != computed: + fail("Checksum mismatch! " + f"Expected {checksum!r}, computed {computed!r}. " + "Suggested fix: remove all generated code including " + "the end marker, or use the '-f' option.") + else: + # put back output + output_lines = output.splitlines(keepends=True) + self.line_number -= len(output_lines) + self.input.extend(reversed(output_lines)) + output = None + + return Block("".join(in_lines), dsl_name, output=output) diff --git a/Tools/clinic/libclinic/converter.py b/Tools/clinic/libclinic/converter.py new file mode 100644 index 000000000000000..744d03c2c1fea4e --- /dev/null +++ b/Tools/clinic/libclinic/converter.py @@ -0,0 +1,533 @@ +from __future__ import annotations +import builtins as bltns +import functools +from typing import Any, TypeVar, Literal, TYPE_CHECKING, cast +from collections.abc import Callable + +import libclinic +from libclinic import fail +from libclinic import Sentinels, unspecified, unknown +from libclinic.crenderdata import CRenderData, Include, TemplateDict +from libclinic.function import Function, Parameter + + +CConverterClassT = TypeVar("CConverterClassT", bound=type["CConverter"]) + + +type_checks = { + '&PyLong_Type': ('PyLong_Check', 'int'), + '&PyTuple_Type': ('PyTuple_Check', 'tuple'), + '&PyList_Type': ('PyList_Check', 'list'), + '&PySet_Type': ('PySet_Check', 'set'), + '&PyFrozenSet_Type': ('PyFrozenSet_Check', 'frozenset'), + '&PyDict_Type': ('PyDict_Check', 'dict'), + '&PyUnicode_Type': ('PyUnicode_Check', 'str'), + '&PyBytes_Type': ('PyBytes_Check', 'bytes'), + '&PyByteArray_Type': ('PyByteArray_Check', 'bytearray'), +} + + +def add_c_converter( + f: CConverterClassT, + name: str | None = None +) -> CConverterClassT: + if not name: + name = f.__name__ + if not name.endswith('_converter'): + return f + name = name.removesuffix('_converter') + converters[name] = f + return f + + +def add_default_legacy_c_converter(cls: CConverterClassT) -> CConverterClassT: + # automatically add converter for default format unit + # (but without stomping on the existing one if it's already + # set, in case you subclass) + if ((cls.format_unit not in ('O&', '')) and + (cls.format_unit not in legacy_converters)): + legacy_converters[cls.format_unit] = cls + return cls + + +class CConverterAutoRegister(type): + def __init__( + cls, name: str, bases: tuple[type[object], ...], classdict: dict[str, Any] + ) -> None: + converter_cls = cast(type["CConverter"], cls) + add_c_converter(converter_cls) + add_default_legacy_c_converter(converter_cls) + +class CConverter(metaclass=CConverterAutoRegister): + """ + For the init function, self, name, function, and default + must be keyword-or-positional parameters. All other + parameters must be keyword-only. + """ + + # The C name to use for this variable. + name: str + + # The Python name to use for this variable. + py_name: str + + # The C type to use for this variable. + # 'type' should be a Python string specifying the type, e.g. "int". + # If this is a pointer type, the type string should end with ' *'. + type: str | None = None + + # The Python default value for this parameter, as a Python value. + # Or the magic value "unspecified" if there is no default. + # Or the magic value "unknown" if this value is a cannot be evaluated + # at Argument-Clinic-preprocessing time (but is presumed to be valid + # at runtime). + default: object = unspecified + + # If not None, default must be isinstance() of this type. + # (You can also specify a tuple of types.) + default_type: bltns.type[object] | tuple[bltns.type[object], ...] | None = None + + # "default" converted into a C value, as a string. + # Or None if there is no default. + c_default: str | None = None + + # "default" converted into a Python value, as a string. + # Or None if there is no default. + py_default: str | None = None + + # The default value used to initialize the C variable when + # there is no default, but not specifying a default may + # result in an "uninitialized variable" warning. This can + # easily happen when using option groups--although + # properly-written code won't actually use the variable, + # the variable does get passed in to the _impl. (Ah, if + # only dataflow analysis could inline the static function!) + # + # This value is specified as a string. + # Every non-abstract subclass should supply a valid value. + c_ignored_default: str = 'NULL' + + # If true, wrap with Py_UNUSED. + unused = False + + # The C converter *function* to be used, if any. + # (If this is not None, format_unit must be 'O&'.) + converter: str | None = None + + # Should Argument Clinic add a '&' before the name of + # the variable when passing it into the _impl function? + impl_by_reference = False + + # Should Argument Clinic add a '&' before the name of + # the variable when passing it into PyArg_ParseTuple (AndKeywords)? + parse_by_reference = True + + ############################################################# + ############################################################# + ## You shouldn't need to read anything below this point to ## + ## write your own converter functions. ## + ############################################################# + ############################################################# + + # The "format unit" to specify for this variable when + # parsing arguments using PyArg_ParseTuple (AndKeywords). + # Custom converters should always use the default value of 'O&'. + format_unit = 'O&' + + # What encoding do we want for this variable? Only used + # by format units starting with 'e'. + encoding: str | None = None + + # Should this object be required to be a subclass of a specific type? + # If not None, should be a string representing a pointer to a + # PyTypeObject (e.g. "&PyUnicode_Type"). + # Only used by the 'O!' format unit (and the "object" converter). + subclass_of: str | None = None + + # See also the 'length_name' property. + # Only used by format units ending with '#'. + length = False + + # Should we show this parameter in the generated + # __text_signature__? This is *almost* always True. + # (It's only False for __new__, __init__, and METH_STATIC functions.) + show_in_signature = True + + # Overrides the name used in a text signature. + # The name used for a "self" parameter must be one of + # self, type, or module; however users can set their own. + # This lets the self_converter overrule the user-settable + # name, *just* for the text signature. + # Only set by self_converter. + signature_name: str | None = None + + broken_limited_capi: bool = False + + # keep in sync with self_converter.__init__! + def __init__(self, + # Positional args: + name: str, + py_name: str, + function: Function, + default: object = unspecified, + *, # Keyword only args: + c_default: str | None = None, + py_default: str | None = None, + annotation: str | Literal[Sentinels.unspecified] = unspecified, + unused: bool = False, + **kwargs: Any + ) -> None: + self.name = libclinic.ensure_legal_c_identifier(name) + self.py_name = py_name + self.unused = unused + self.includes: list[Include] = [] + + if default is not unspecified: + if (self.default_type + and default is not unknown + and not isinstance(default, self.default_type) + ): + if isinstance(self.default_type, type): + types_str = self.default_type.__name__ + else: + names = [cls.__name__ for cls in self.default_type] + types_str = ', '.join(names) + cls_name = self.__class__.__name__ + fail(f"{cls_name}: default value {default!r} for field " + f"{name!r} is not of type {types_str!r}") + self.default = default + + if c_default: + self.c_default = c_default + if py_default: + self.py_default = py_default + + if annotation is not unspecified: + fail("The 'annotation' parameter is not currently permitted.") + + # Make sure not to set self.function until after converter_init() has been called. + # This prevents you from caching information + # about the function in converter_init(). + # (That breaks if we get cloned.) + self.converter_init(**kwargs) + self.function = function + + # Add a custom __getattr__ method to improve the error message + # if somebody tries to access self.function in converter_init(). + # + # mypy will assume arbitrary access is okay for a class with a __getattr__ method, + # and that's not what we want, + # so put it inside an `if not TYPE_CHECKING` block + if not TYPE_CHECKING: + def __getattr__(self, attr): + if attr == "function": + fail( + f"{self.__class__.__name__!r} object has no attribute 'function'.\n" + f"Note: accessing self.function inside converter_init is disallowed!" + ) + return super().__getattr__(attr) + # this branch is just here for coverage reporting + else: # pragma: no cover + pass + + def converter_init(self) -> None: + pass + + def is_optional(self) -> bool: + return (self.default is not unspecified) + + def _render_self(self, parameter: Parameter, data: CRenderData) -> None: + self.parameter = parameter + name = self.parser_name + + # impl_arguments + s = ("&" if self.impl_by_reference else "") + name + data.impl_arguments.append(s) + if self.length: + data.impl_arguments.append(self.length_name) + + # impl_parameters + data.impl_parameters.append(self.simple_declaration(by_reference=self.impl_by_reference)) + if self.length: + data.impl_parameters.append(f"Py_ssize_t {self.length_name}") + + def _render_non_self( + self, + parameter: Parameter, + data: CRenderData + ) -> None: + self.parameter = parameter + name = self.name + + # declarations + d = self.declaration(in_parser=True) + data.declarations.append(d) + + # initializers + initializers = self.initialize() + if initializers: + data.initializers.append('/* initializers for ' + name + ' */\n' + initializers.rstrip()) + + # modifications + modifications = self.modify() + if modifications: + data.modifications.append('/* modifications for ' + name + ' */\n' + modifications.rstrip()) + + # keywords + if parameter.is_vararg(): + pass + elif parameter.is_positional_only(): + data.keywords.append('') + else: + data.keywords.append(parameter.name) + + # format_units + if self.is_optional() and '|' not in data.format_units: + data.format_units.append('|') + if parameter.is_keyword_only() and '$' not in data.format_units: + data.format_units.append('$') + data.format_units.append(self.format_unit) + + # parse_arguments + self.parse_argument(data.parse_arguments) + + # post_parsing + if post_parsing := self.post_parsing(): + data.post_parsing.append('/* Post parse cleanup for ' + name + ' */\n' + post_parsing.rstrip() + '\n') + + # cleanup + cleanup = self.cleanup() + if cleanup: + data.cleanup.append('/* Cleanup for ' + name + ' */\n' + cleanup.rstrip() + "\n") + + def render(self, parameter: Parameter, data: CRenderData) -> None: + """ + parameter is a clinic.Parameter instance. + data is a CRenderData instance. + """ + self._render_self(parameter, data) + self._render_non_self(parameter, data) + + @functools.cached_property + def length_name(self) -> str: + """Computes the name of the associated "length" variable.""" + assert self.length is not None + return self.parser_name + "_length" + + # Why is this one broken out separately? + # For "positional-only" function parsing, + # which generates a bunch of PyArg_ParseTuple calls. + def parse_argument(self, args: list[str]) -> None: + assert not (self.converter and self.encoding) + if self.format_unit == 'O&': + assert self.converter + args.append(self.converter) + + if self.encoding: + args.append(libclinic.c_repr(self.encoding)) + elif self.subclass_of: + args.append(self.subclass_of) + + s = ("&" if self.parse_by_reference else "") + self.parser_name + args.append(s) + + if self.length: + args.append(f"&{self.length_name}") + + # + # All the functions after here are intended as extension points. + # + + def simple_declaration( + self, + by_reference: bool = False, + *, + in_parser: bool = False + ) -> str: + """ + Computes the basic declaration of the variable. + Used in computing the prototype declaration and the + variable declaration. + """ + assert isinstance(self.type, str) + prototype = [self.type] + if by_reference or not self.type.endswith('*'): + prototype.append(" ") + if by_reference: + prototype.append('*') + if in_parser: + name = self.parser_name + else: + name = self.name + if self.unused: + name = f"Py_UNUSED({name})" + prototype.append(name) + return "".join(prototype) + + def declaration(self, *, in_parser: bool = False) -> str: + """ + The C statement to declare this variable. + """ + declaration = [self.simple_declaration(in_parser=True)] + default = self.c_default + if not default and self.parameter.group: + default = self.c_ignored_default + if default: + declaration.append(" = ") + declaration.append(default) + declaration.append(";") + if self.length: + declaration.append('\n') + declaration.append(f"Py_ssize_t {self.length_name};") + return "".join(declaration) + + def initialize(self) -> str: + """ + The C statements required to set up this variable before parsing. + Returns a string containing this code indented at column 0. + If no initialization is necessary, returns an empty string. + """ + return "" + + def modify(self) -> str: + """ + The C statements required to modify this variable after parsing. + Returns a string containing this code indented at column 0. + If no modification is necessary, returns an empty string. + """ + return "" + + def post_parsing(self) -> str: + """ + The C statements required to do some operations after the end of parsing but before cleaning up. + Return a string containing this code indented at column 0. + If no operation is necessary, return an empty string. + """ + return "" + + def cleanup(self) -> str: + """ + The C statements required to clean up after this variable. + Returns a string containing this code indented at column 0. + If no cleanup is necessary, returns an empty string. + """ + return "" + + def pre_render(self) -> None: + """ + A second initialization function, like converter_init, + called just before rendering. + You are permitted to examine self.function here. + """ + pass + + def bad_argument(self, displayname: str, expected: str, *, limited_capi: bool, expected_literal: bool = True) -> str: + assert '"' not in expected + if limited_capi: + if expected_literal: + return (f'PyErr_Format(PyExc_TypeError, ' + f'"{{{{name}}}}() {displayname} must be {expected}, not %T", ' + f'{{argname}});') + else: + return (f'PyErr_Format(PyExc_TypeError, ' + f'"{{{{name}}}}() {displayname} must be %s, not %T", ' + f'"{expected}", {{argname}});') + else: + if expected_literal: + expected = f'"{expected}"' + self.add_include('pycore_modsupport.h', '_PyArg_BadArgument()') + return f'_PyArg_BadArgument("{{{{name}}}}", "{displayname}", {expected}, {{argname}});' + + def format_code(self, fmt: str, *, + argname: str, + bad_argument: str | None = None, + bad_argument2: str | None = None, + **kwargs: Any) -> str: + if '{bad_argument}' in fmt: + if not bad_argument: + raise TypeError("required 'bad_argument' argument") + fmt = fmt.replace('{bad_argument}', bad_argument) + if '{bad_argument2}' in fmt: + if not bad_argument2: + raise TypeError("required 'bad_argument2' argument") + fmt = fmt.replace('{bad_argument2}', bad_argument2) + return fmt.format(argname=argname, paramname=self.parser_name, **kwargs) + + def use_converter(self) -> None: + """Method called when self.converter is used to parse an argument.""" + pass + + def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: + if self.format_unit == 'O&': + self.use_converter() + return self.format_code(""" + if (!{converter}({argname}, &{paramname})) {{{{ + goto exit; + }}}} + """, + argname=argname, + converter=self.converter) + if self.format_unit == 'O!': + cast = '(%s)' % self.type if self.type != 'PyObject *' else '' + if self.subclass_of in type_checks: + typecheck, typename = type_checks[self.subclass_of] + return self.format_code(""" + if (!{typecheck}({argname})) {{{{ + {bad_argument} + goto exit; + }}}} + {paramname} = {cast}{argname}; + """, + argname=argname, + bad_argument=self.bad_argument(displayname, typename, limited_capi=limited_capi), + typecheck=typecheck, typename=typename, cast=cast) + return self.format_code(""" + if (!PyObject_TypeCheck({argname}, {subclass_of})) {{{{ + {bad_argument} + goto exit; + }}}} + {paramname} = {cast}{argname}; + """, + argname=argname, + bad_argument=self.bad_argument(displayname, '({subclass_of})->tp_name', + expected_literal=False, limited_capi=limited_capi), + subclass_of=self.subclass_of, cast=cast) + if self.format_unit == 'O': + cast = '(%s)' % self.type if self.type != 'PyObject *' else '' + return self.format_code(""" + {paramname} = {cast}{argname}; + """, + argname=argname, cast=cast) + return None + + def set_template_dict(self, template_dict: TemplateDict) -> None: + pass + + @property + def parser_name(self) -> str: + if self.name in libclinic.CLINIC_PREFIXED_ARGS: # bpo-39741 + return libclinic.CLINIC_PREFIX + self.name + else: + return self.name + + def add_include(self, name: str, reason: str, + *, condition: str | None = None) -> None: + include = Include(name, reason, condition) + self.includes.append(include) + + +ConverterType = Callable[..., CConverter] +ConverterDict = dict[str, ConverterType] + +# maps strings to callables. +# these callables must be of the form: +# def foo(name, default, *, ...) +# The callable may have any number of keyword-only parameters. +# The callable must return a CConverter object. +# The callable should not call builtins.print. +converters: ConverterDict = {} + +# maps strings to callables. +# these callables follow the same rules as those for "converters" above. +# note however that they will never be called with keyword-only parameters. +legacy_converters: ConverterDict = {} diff --git a/Tools/clinic/libclinic/crenderdata.py b/Tools/clinic/libclinic/crenderdata.py new file mode 100644 index 000000000000000..58976b8185ebae1 --- /dev/null +++ b/Tools/clinic/libclinic/crenderdata.py @@ -0,0 +1,81 @@ +import dataclasses as dc + + +TemplateDict = dict[str, str] + + +class CRenderData: + def __init__(self) -> None: + + # The C statements to declare variables. + # Should be full lines with \n eol characters. + self.declarations: list[str] = [] + + # The C statements required to initialize the variables before the parse call. + # Should be full lines with \n eol characters. + self.initializers: list[str] = [] + + # The C statements needed to dynamically modify the values + # parsed by the parse call, before calling the impl. + self.modifications: list[str] = [] + + # The entries for the "keywords" array for PyArg_ParseTuple. + # Should be individual strings representing the names. + self.keywords: list[str] = [] + + # The "format units" for PyArg_ParseTuple. + # Should be individual strings that will get + self.format_units: list[str] = [] + + # The varargs arguments for PyArg_ParseTuple. + self.parse_arguments: list[str] = [] + + # The parameter declarations for the impl function. + self.impl_parameters: list[str] = [] + + # The arguments to the impl function at the time it's called. + self.impl_arguments: list[str] = [] + + # For return converters: the name of the variable that + # should receive the value returned by the impl. + self.return_value = "return_value" + + # For return converters: the code to convert the return + # value from the parse function. This is also where + # you should check the _return_value for errors, and + # "goto exit" if there are any. + self.return_conversion: list[str] = [] + self.converter_retval = "_return_value" + + # The C statements required to do some operations + # after the end of parsing but before cleaning up. + # These operations may be, for example, memory deallocations which + # can only be done without any error happening during argument parsing. + self.post_parsing: list[str] = [] + + # The C statements required to clean up after the impl call. + self.cleanup: list[str] = [] + + # The C statements to generate critical sections (per-object locking). + self.lock: list[str] = [] + self.unlock: list[str] = [] + + +@dc.dataclass(slots=True, frozen=True) +class Include: + """ + An include like: #include "pycore_long.h" // _Py_ID() + """ + # Example: "pycore_long.h". + filename: str + + # Example: "_Py_ID()". + reason: str + + # None means unconditional include. + # Example: "#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)". + condition: str | None + + def sort_key(self) -> tuple[str, str]: + # order: '#if' comes before 'NO_CONDITION' + return (self.condition or 'NO_CONDITION', self.filename) diff --git a/Tools/clinic/libclinic/errors.py b/Tools/clinic/libclinic/errors.py index afb21b02386fe7f..f06bdfbd864b2c0 100644 --- a/Tools/clinic/libclinic/errors.py +++ b/Tools/clinic/libclinic/errors.py @@ -1,4 +1,5 @@ import dataclasses as dc +from typing import Literal, NoReturn, overload @dc.dataclass @@ -24,3 +25,48 @@ def report(self, *, warn_only: bool = False) -> str: class ParseError(ClinicError): pass + + +@overload +def warn_or_fail( + *args: object, + fail: Literal[True], + filename: str | None = None, + line_number: int | None = None, +) -> NoReturn: ... + +@overload +def warn_or_fail( + *args: object, + fail: Literal[False] = False, + filename: str | None = None, + line_number: int | None = None, +) -> None: ... + +def warn_or_fail( + *args: object, + fail: bool = False, + filename: str | None = None, + line_number: int | None = None, +) -> None: + joined = " ".join([str(a) for a in args]) + error = ClinicError(joined, filename=filename, lineno=line_number) + if fail: + raise error + else: + print(error.report(warn_only=True)) + + +def warn( + *args: object, + filename: str | None = None, + line_number: int | None = None, +) -> None: + return warn_or_fail(*args, filename=filename, line_number=line_number, fail=False) + +def fail( + *args: object, + filename: str | None = None, + line_number: int | None = None, +) -> NoReturn: + warn_or_fail(*args, filename=filename, line_number=line_number, fail=True) diff --git a/Tools/clinic/libclinic/function.py b/Tools/clinic/libclinic/function.py new file mode 100644 index 000000000000000..4fafedb617115c8 --- /dev/null +++ b/Tools/clinic/libclinic/function.py @@ -0,0 +1,238 @@ +from __future__ import annotations +import dataclasses as dc +import copy +import enum +import functools +import inspect +from typing import Final, Any, TYPE_CHECKING +if TYPE_CHECKING: + from clinic import Clinic, CReturnConverter, self_converter + from libclinic.converter import CConverter + +from libclinic import VersionTuple, unspecified + + +ClassDict = dict[str, "Class"] +ModuleDict = dict[str, "Module"] +ParamDict = dict[str, "Parameter"] + + +@dc.dataclass(repr=False) +class Module: + name: str + module: Module | Clinic + + def __post_init__(self) -> None: + self.parent = self.module + self.modules: ModuleDict = {} + self.classes: ClassDict = {} + self.functions: list[Function] = [] + + def __repr__(self) -> str: + return "" + + +@dc.dataclass(repr=False) +class Class: + name: str + module: Module | Clinic + cls: Class | None + typedef: str + type_object: str + + def __post_init__(self) -> None: + self.parent = self.cls or self.module + self.classes: ClassDict = {} + self.functions: list[Function] = [] + + def __repr__(self) -> str: + return "" + + +class FunctionKind(enum.Enum): + INVALID = enum.auto() + CALLABLE = enum.auto() + STATIC_METHOD = enum.auto() + CLASS_METHOD = enum.auto() + METHOD_INIT = enum.auto() + METHOD_NEW = enum.auto() + GETTER = enum.auto() + SETTER = enum.auto() + + @functools.cached_property + def new_or_init(self) -> bool: + return self in {FunctionKind.METHOD_INIT, FunctionKind.METHOD_NEW} + + def __repr__(self) -> str: + return f"" + + +INVALID: Final = FunctionKind.INVALID +CALLABLE: Final = FunctionKind.CALLABLE +STATIC_METHOD: Final = FunctionKind.STATIC_METHOD +CLASS_METHOD: Final = FunctionKind.CLASS_METHOD +METHOD_INIT: Final = FunctionKind.METHOD_INIT +METHOD_NEW: Final = FunctionKind.METHOD_NEW +GETTER: Final = FunctionKind.GETTER +SETTER: Final = FunctionKind.SETTER + + +@dc.dataclass(repr=False) +class Function: + """ + Mutable duck type for inspect.Function. + + docstring - a str containing + * embedded line breaks + * text outdented to the left margin + * no trailing whitespace. + It will always be true that + (not docstring) or ((not docstring[0].isspace()) and (docstring.rstrip() == docstring)) + """ + parameters: ParamDict = dc.field(default_factory=dict) + _: dc.KW_ONLY + name: str + module: Module | Clinic + cls: Class | None + c_basename: str + full_name: str + return_converter: CReturnConverter + kind: FunctionKind + coexist: bool + return_annotation: object = inspect.Signature.empty + docstring: str = '' + # docstring_only means "don't generate a machine-readable + # signature, just a normal docstring". it's True for + # functions with optional groups because we can't represent + # those accurately with inspect.Signature in 3.4. + docstring_only: bool = False + critical_section: bool = False + target_critical_section: list[str] = dc.field(default_factory=list) + + def __post_init__(self) -> None: + self.parent = self.cls or self.module + self.self_converter: self_converter | None = None + self.__render_parameters__: list[Parameter] | None = None + + @functools.cached_property + def displayname(self) -> str: + """Pretty-printable name.""" + if self.kind.new_or_init: + assert isinstance(self.cls, Class) + return self.cls.name + else: + return self.name + + @functools.cached_property + def fulldisplayname(self) -> str: + parent: Class | Module | Clinic | None + if self.kind.new_or_init: + parent = getattr(self.cls, "parent", None) + else: + parent = self.parent + name = self.displayname + while isinstance(parent, (Module, Class)): + name = f"{parent.name}.{name}" + parent = parent.parent + return name + + @property + def render_parameters(self) -> list[Parameter]: + if not self.__render_parameters__: + l: list[Parameter] = [] + self.__render_parameters__ = l + for p in self.parameters.values(): + p = p.copy() + p.converter.pre_render() + l.append(p) + return self.__render_parameters__ + + @property + def methoddef_flags(self) -> str | None: + if self.kind.new_or_init: + return None + flags = [] + match self.kind: + case FunctionKind.CLASS_METHOD: + flags.append('METH_CLASS') + case FunctionKind.STATIC_METHOD: + flags.append('METH_STATIC') + case _ as kind: + acceptable_kinds = {FunctionKind.CALLABLE, FunctionKind.GETTER, FunctionKind.SETTER} + assert kind in acceptable_kinds, f"unknown kind: {kind!r}" + if self.coexist: + flags.append('METH_COEXIST') + return '|'.join(flags) + + def __repr__(self) -> str: + return f'' + + def copy(self, **overrides: Any) -> Function: + f = dc.replace(self, **overrides) + f.parameters = { + name: value.copy(function=f) + for name, value in f.parameters.items() + } + return f + + +@dc.dataclass(repr=False, slots=True) +class Parameter: + """ + Mutable duck type of inspect.Parameter. + """ + name: str + kind: inspect._ParameterKind + _: dc.KW_ONLY + default: object = inspect.Parameter.empty + function: Function + converter: CConverter + annotation: object = inspect.Parameter.empty + docstring: str = '' + group: int = 0 + # (`None` signifies that there is no deprecation) + deprecated_positional: VersionTuple | None = None + deprecated_keyword: VersionTuple | None = None + right_bracket_count: int = dc.field(init=False, default=0) + + def __repr__(self) -> str: + return f'' + + def is_keyword_only(self) -> bool: + return self.kind == inspect.Parameter.KEYWORD_ONLY + + def is_positional_only(self) -> bool: + return self.kind == inspect.Parameter.POSITIONAL_ONLY + + def is_vararg(self) -> bool: + return self.kind == inspect.Parameter.VAR_POSITIONAL + + def is_optional(self) -> bool: + return not self.is_vararg() and (self.default is not unspecified) + + def copy( + self, + /, + *, + converter: CConverter | None = None, + function: Function | None = None, + **overrides: Any + ) -> Parameter: + function = function or self.function + if not converter: + converter = copy.copy(self.converter) + converter.function = function + return dc.replace(self, **overrides, function=function, converter=converter) + + def get_displayname(self, i: int) -> str: + if i == 0: + return 'argument' + if not self.is_positional_only(): + return f'argument {self.name!r}' + else: + return f'argument {i}' + + def render_docstring(self) -> str: + lines = [f" {self.name}"] + lines.extend(f" {line}" for line in self.docstring.split("\n")) + return "\n".join(lines).rstrip() diff --git a/Tools/clinic/libclinic/language.py b/Tools/clinic/libclinic/language.py new file mode 100644 index 000000000000000..a90a9bb24e22015 --- /dev/null +++ b/Tools/clinic/libclinic/language.py @@ -0,0 +1,103 @@ +from __future__ import annotations +import abc +import typing +from collections.abc import ( + Iterable, +) + +import libclinic +from libclinic import fail +from libclinic.function import ( + Module, Class, Function) + +if typing.TYPE_CHECKING: + from clinic import Clinic + + +class Language(metaclass=abc.ABCMeta): + + start_line = "" + body_prefix = "" + stop_line = "" + checksum_line = "" + + def __init__(self, filename: str) -> None: + self.filename = filename + + @abc.abstractmethod + def render( + self, + clinic: Clinic, + signatures: Iterable[Module | Class | Function] + ) -> str: + ... + + def parse_line(self, line: str) -> None: + ... + + def validate(self) -> None: + def assert_only_one( + attr: str, + *additional_fields: str + ) -> None: + """ + Ensures that the string found at getattr(self, attr) + contains exactly one formatter replacement string for + each valid field. The list of valid fields is + ['dsl_name'] extended by additional_fields. + + e.g. + self.fmt = "{dsl_name} {a} {b}" + + # this passes + self.assert_only_one('fmt', 'a', 'b') + + # this fails, the format string has a {b} in it + self.assert_only_one('fmt', 'a') + + # this fails, the format string doesn't have a {c} in it + self.assert_only_one('fmt', 'a', 'b', 'c') + + # this fails, the format string has two {a}s in it, + # it must contain exactly one + self.fmt2 = '{dsl_name} {a} {a}' + self.assert_only_one('fmt2', 'a') + + """ + fields = ['dsl_name'] + fields.extend(additional_fields) + line: str = getattr(self, attr) + fcf = libclinic.FormatCounterFormatter() + fcf.format(line) + def local_fail(should_be_there_but_isnt: bool) -> None: + if should_be_there_but_isnt: + fail("{} {} must contain {{{}}} exactly once!".format( + self.__class__.__name__, attr, name)) + else: + fail("{} {} must not contain {{{}}}!".format( + self.__class__.__name__, attr, name)) + + for name, count in fcf.counts.items(): + if name in fields: + if count > 1: + local_fail(True) + else: + local_fail(False) + for name in fields: + if fcf.counts.get(name) != 1: + local_fail(True) + + assert_only_one('start_line') + assert_only_one('stop_line') + + field = "arguments" if "{arguments}" in self.checksum_line else "checksum" + assert_only_one('checksum_line', field) + + +class PythonLanguage(Language): + + language = 'Python' + start_line = "#/*[{dsl_name} input]" + body_prefix = "#" + stop_line = "#[{dsl_name} start generated code]*/" + checksum_line = "#/*[{dsl_name} end generated code: {arguments}]*/" diff --git a/Tools/clinic/libclinic/utils.py b/Tools/clinic/libclinic/utils.py index d2d09387a73d1ea..95a69f70c5499d3 100644 --- a/Tools/clinic/libclinic/utils.py +++ b/Tools/clinic/libclinic/utils.py @@ -1,9 +1,10 @@ import collections +import enum import hashlib import os import re import string -from typing import Literal +from typing import Literal, Final def write_file(filename: str, new_contents: str) -> None: @@ -66,3 +67,18 @@ def get_value( ) -> Literal[""]: self.counts[key] += 1 return "" + + +VersionTuple = tuple[int, int] + + +class Sentinels(enum.Enum): + unspecified = "unspecified" + unknown = "unknown" + + def __repr__(self) -> str: + return f"<{self.value.capitalize()}>" + + +unspecified: Final = Sentinels.unspecified +unknown: Final = Sentinels.unknown diff --git a/Tools/jit/_schema.py b/Tools/jit/_schema.py index 8eeb78e6cd69eee..045fd502a03c12b 100644 --- a/Tools/jit/_schema.py +++ b/Tools/jit/_schema.py @@ -4,17 +4,34 @@ HoleKind: typing.TypeAlias = typing.Literal[ "ARM64_RELOC_GOT_LOAD_PAGE21", "ARM64_RELOC_GOT_LOAD_PAGEOFF12", + "ARM64_RELOC_PAGE21", + "ARM64_RELOC_PAGEOFF12", "ARM64_RELOC_UNSIGNED", - "IMAGE_REL_AMD64_ADDR64", + "IMAGE_REL_AMD64_REL32", + "IMAGE_REL_ARM64_BRANCH26", + "IMAGE_REL_ARM64_PAGEBASE_REL21", + "IMAGE_REL_ARM64_PAGEOFFSET_12A", + "IMAGE_REL_ARM64_PAGEOFFSET_12L", "IMAGE_REL_I386_DIR32", + "IMAGE_REL_I386_REL32", "R_AARCH64_ABS64", + "R_AARCH64_ADR_GOT_PAGE", "R_AARCH64_CALL26", "R_AARCH64_JUMP26", + "R_AARCH64_LD64_GOT_LO12_NC", "R_AARCH64_MOVW_UABS_G0_NC", "R_AARCH64_MOVW_UABS_G1_NC", "R_AARCH64_MOVW_UABS_G2_NC", "R_AARCH64_MOVW_UABS_G3", "R_X86_64_64", + "R_X86_64_GOTPCREL", + "R_X86_64_GOTPCRELX", + "R_X86_64_PC32", + "R_X86_64_REX_GOTPCRELX", + "X86_64_RELOC_BRANCH", + "X86_64_RELOC_GOT", + "X86_64_RELOC_GOT_LOAD", + "X86_64_RELOC_SIGNED", "X86_64_RELOC_UNSIGNED", ] diff --git a/Tools/jit/_stencils.py b/Tools/jit/_stencils.py index 71c678e04fbfd5a..78c566d9c8a7ef7 100644 --- a/Tools/jit/_stencils.py +++ b/Tools/jit/_stencils.py @@ -63,7 +63,7 @@ def as_c(self) -> str: f"HoleKind_{self.kind}", f"HoleValue_{self.value.name}", f"&{self.symbol}" if self.symbol else "NULL", - _format_addend(self.addend), + f"{_signed(self.addend):#x}", ] return f"{{{', '.join(parts)}}}" @@ -96,7 +96,7 @@ def emit_aarch64_trampoline(self, hole: Hole) -> None: instruction |= ((base - hole.offset) >> 2) & 0x03FFFFFF self.body[where] = instruction.to_bytes(4, sys.byteorder) self.disassembly += [ - f"{base + 4 * 0: x}: d2800008 mov x8, #0x0", + f"{base + 4 * 0:x}: d2800008 mov x8, #0x0", f"{base + 4 * 0:016x}: R_AARCH64_MOVW_UABS_G0_NC {hole.symbol}", f"{base + 4 * 1:x}: f2a00008 movk x8, #0x0, lsl #16", f"{base + 4 * 1:016x}: R_AARCH64_MOVW_UABS_G1_NC {hole.symbol}", @@ -124,6 +124,56 @@ def emit_aarch64_trampoline(self, hole: Hole) -> None: ): self.holes.append(hole.replace(offset=base + 4 * i, kind=kind)) + def remove_jump(self) -> None: + """Remove a zero-length continuation jump, if it exists.""" + hole = max(self.holes, key=lambda hole: hole.offset) + match hole: + case Hole( + offset=offset, + kind="IMAGE_REL_AMD64_REL32", + value=HoleValue.GOT, + symbol="_JIT_CONTINUE", + addend=-4, + ) as hole: + # jmp qword ptr [rip] + jump = b"\x48\xFF\x25\x00\x00\x00\x00" + offset -= 3 + case Hole( + offset=offset, + kind="IMAGE_REL_I386_REL32" | "X86_64_RELOC_BRANCH", + value=HoleValue.CONTINUE, + symbol=None, + addend=-4, + ) as hole: + # jmp 5 + jump = b"\xE9\x00\x00\x00\x00" + offset -= 1 + case Hole( + offset=offset, + kind="R_AARCH64_JUMP26", + value=HoleValue.CONTINUE, + symbol=None, + addend=0, + ) as hole: + # b #4 + jump = b"\x00\x00\x00\x14" + case Hole( + offset=offset, + kind="R_X86_64_GOTPCRELX", + value=HoleValue.GOT, + symbol="_JIT_CONTINUE", + addend=addend, + ) as hole: + assert _signed(addend) == -4 + # jmp qword ptr [rip] + jump = b"\xFF\x25\x00\x00\x00\x00" + offset -= 2 + case _: + return + if self.body[offset:] == jump: + self.body = self.body[:offset] + self.holes.remove(hole) + @dataclasses.dataclass class StencilGroup: @@ -142,10 +192,19 @@ class StencilGroup: def process_relocations(self, *, alignment: int = 1) -> None: """Fix up all GOT and internal relocations for this stencil group.""" + for hole in self.code.holes.copy(): + if ( + hole.kind in {"R_AARCH64_CALL26", "R_AARCH64_JUMP26"} + and hole.value is HoleValue.ZERO + ): + self.code.pad(alignment) + self.code.emit_aarch64_trampoline(hole) + self.code.pad(alignment) + self.code.holes.remove(hole) + self.code.remove_jump() self.code.pad(alignment) self.data.pad(8) for stencil in [self.code, self.data]: - holes = [] for hole in stencil.holes: if hole.value is HoleValue.GOT: assert hole.symbol is not None @@ -157,14 +216,12 @@ def process_relocations(self, *, alignment: int = 1) -> None: hole.addend += addend hole.symbol = None elif ( - hole.kind in {"R_AARCH64_CALL26", "R_AARCH64_JUMP26"} + hole.kind in {"IMAGE_REL_AMD64_REL32"} and hole.value is HoleValue.ZERO ): - self.code.emit_aarch64_trampoline(hole) - continue - holes.append(hole) - stencil.holes[:] = holes - self.code.pad(alignment) + raise ValueError( + f"Add PyAPI_FUNC(...) or PyAPI_DATA(...) to declaration of {hole.symbol}!" + ) self._emit_global_offset_table() self.code.holes.sort(key=lambda hole: hole.offset) self.data.holes.sort(key=lambda hole: hole.offset) @@ -188,8 +245,9 @@ def _emit_global_offset_table(self) -> None: if value_part and not symbol and not addend: addend_part = "" else: + signed = "+" if symbol is not None else "" addend_part = f"&{symbol}" if symbol else "" - addend_part += _format_addend(addend, signed=symbol is not None) + addend_part += f"{_signed(addend):{signed}#x}" if value_part: value_part += "+" self.data.disassembly.append( @@ -213,8 +271,8 @@ def symbol_to_value(symbol: str) -> tuple[HoleValue, str | None]: return HoleValue.ZERO, symbol -def _format_addend(addend: int, signed: bool = False) -> str: - addend %= 1 << 64 - if addend & (1 << 63): - addend -= 1 << 64 - return f"{addend:{'+#x' if signed else '#x'}}" +def _signed(value: int) -> int: + value %= 1 << 64 + if value & (1 << 63): + value -= 1 << 64 + return value diff --git a/Tools/jit/_targets.py b/Tools/jit/_targets.py index 6c1d440324c5052..417fdb56ccf7a1b 100644 --- a/Tools/jit/_targets.py +++ b/Tools/jit/_targets.py @@ -37,6 +37,7 @@ class _Target(typing.Generic[_S, _R]): triple: str _: dataclasses.KW_ONLY alignment: int = 1 + args: typing.Sequence[str] = () prefix: str = "" debug: bool = False force: bool = False @@ -105,7 +106,7 @@ async def _compile( o = tempdir / f"{opname}.o" args = [ f"--target={self.triple}", - "-DPy_BUILD_CORE", + "-DPy_BUILD_CORE_MODULE", "-D_DEBUG" if self.debug else "-DNDEBUG", f"-D_JIT_OPCODE={opname}", "-D_PyJIT_ACTIVE", @@ -117,25 +118,23 @@ async def _compile( f"-I{CPYTHON / 'Python'}", "-O3", "-c", + # This debug info isn't necessary, and bloats out the JIT'ed code. + # We *may* be able to re-enable this, process it, and JIT it for a + # nicer debugging experience... but that needs a lot more research: "-fno-asynchronous-unwind-tables", + # Don't call built-in functions that we can't find or patch: "-fno-builtin", - # SET_FUNCTION_ATTRIBUTE on 32-bit Windows debug builds: - "-fno-jump-tables", - # Position-independent code adds indirection to every load and jump: - "-fno-pic", - # Don't make calls to weird stack-smashing canaries: + # Emit relaxable 64-bit calls/jumps, so we don't have to worry about + # about emitting in-range trampolines for out-of-range targets. + # We can probably remove this and emit trampolines in the future: + "-fno-plt", + # Don't call stack-smashing canaries that we can't find or patch: "-fno-stack-protector", - # We have three options for code model: - # - "small": the default, assumes that code and data reside in the - # lowest 2GB of memory (128MB on aarch64) - # - "medium": assumes that code resides in the lowest 2GB of memory, - # and makes no assumptions about data (not available on aarch64) - # - "large": makes no assumptions about either code or data - "-mcmodel=large", "-o", f"{o}", "-std=c11", f"{c}", + *self.args, ] await _llvm.run("clang", args, echo=self.verbose) return await self._parse(o) @@ -200,12 +199,21 @@ def _handle_section( offset = base + symbol["Value"] name = symbol["Name"] name = name.removeprefix(self.prefix) - group.symbols[name] = value, offset + if name not in group.symbols: + group.symbols[name] = value, offset for wrapped_relocation in section["Relocations"]: relocation = wrapped_relocation["Relocation"] hole = self._handle_relocation(base, relocation, stencil.body) stencil.holes.append(hole) + def _unwrap_dllimport(self, name: str) -> tuple[_stencils.HoleValue, str | None]: + if name.startswith("__imp_"): + name = name.removeprefix("__imp_") + name = name.removeprefix(self.prefix) + return _stencils.HoleValue.GOT, name + name = name.removeprefix(self.prefix) + return _stencils.symbol_to_value(name) + def _handle_relocation( self, base: int, relocation: _schema.COFFRelocation, raw: bytes ) -> _stencils.Hole: @@ -213,21 +221,36 @@ def _handle_relocation( case { "Offset": offset, "Symbol": s, - "Type": {"Value": "IMAGE_REL_AMD64_ADDR64" as kind}, + "Type": {"Value": "IMAGE_REL_I386_DIR32" as kind}, }: offset += base - s = s.removeprefix(self.prefix) - value, symbol = _stencils.symbol_to_value(s) - addend = int.from_bytes(raw[offset : offset + 8], "little") + value, symbol = self._unwrap_dllimport(s) + addend = int.from_bytes(raw[offset : offset + 4], "little") case { "Offset": offset, "Symbol": s, - "Type": {"Value": "IMAGE_REL_I386_DIR32" as kind}, + "Type": { + "Value": "IMAGE_REL_AMD64_REL32" | "IMAGE_REL_I386_REL32" as kind + }, }: offset += base - s = s.removeprefix(self.prefix) - value, symbol = _stencils.symbol_to_value(s) - addend = int.from_bytes(raw[offset : offset + 4], "little") + value, symbol = self._unwrap_dllimport(s) + addend = ( + int.from_bytes(raw[offset : offset + 4], "little", signed=True) - 4 + ) + case { + "Offset": offset, + "Symbol": s, + "Type": { + "Value": "IMAGE_REL_ARM64_BRANCH26" + | "IMAGE_REL_ARM64_PAGEBASE_REL21" + | "IMAGE_REL_ARM64_PAGEOFFSET_12A" + | "IMAGE_REL_ARM64_PAGEOFFSET_12L" as kind + }, + }: + offset += base + value, symbol = self._unwrap_dllimport(s) + addend = 0 case _: raise NotImplementedError(relocation) return _stencils.Hole(offset, kind, value, symbol, addend) @@ -284,7 +307,23 @@ def _handle_section( def _handle_relocation( self, base: int, relocation: _schema.ELFRelocation, raw: bytes ) -> _stencils.Hole: + symbol: str | None match relocation: + case { + "Addend": addend, + "Offset": offset, + "Symbol": {"Value": s}, + "Type": { + "Value": "R_AARCH64_ADR_GOT_PAGE" + | "R_AARCH64_LD64_GOT_LO12_NC" + | "R_X86_64_GOTPCREL" + | "R_X86_64_GOTPCRELX" + | "R_X86_64_REX_GOTPCRELX" as kind + }, + }: + offset += base + s = s.removeprefix(self.prefix) + value, symbol = _stencils.HoleValue.GOT, s case { "Addend": addend, "Offset": offset, @@ -358,6 +397,34 @@ def _handle_relocation( s = s.removeprefix(self.prefix) value, symbol = _stencils.HoleValue.GOT, s addend = 0 + case { + "Offset": offset, + "Symbol": {"Value": s}, + "Type": {"Value": "X86_64_RELOC_GOT" | "X86_64_RELOC_GOT_LOAD" as kind}, + }: + offset += base + s = s.removeprefix(self.prefix) + value, symbol = _stencils.HoleValue.GOT, s + addend = ( + int.from_bytes(raw[offset : offset + 4], "little", signed=True) - 4 + ) + case { + "Offset": offset, + "Section": {"Value": s}, + "Type": {"Value": "X86_64_RELOC_SIGNED" as kind}, + } | { + "Offset": offset, + "Symbol": {"Value": s}, + "Type": { + "Value": "X86_64_RELOC_BRANCH" | "X86_64_RELOC_SIGNED" as kind + }, + }: + offset += base + s = s.removeprefix(self.prefix) + value, symbol = _stencils.symbol_to_value(s) + addend = ( + int.from_bytes(raw[offset : offset + 4], "little", signed=True) - 4 + ) case { "Offset": offset, "Section": {"Value": s}, @@ -379,15 +446,22 @@ def _handle_relocation( def get_target(host: str) -> _COFF | _ELF | _MachO: """Build a _Target for the given host "triple" and options.""" if re.fullmatch(r"aarch64-apple-darwin.*", host): - return _MachO(host, alignment=8, prefix="_") + args = ["-mcmodel=large"] + return _MachO(host, alignment=8, args=args, prefix="_") + if re.fullmatch(r"aarch64-pc-windows-msvc", host): + args = ["-fms-runtime-lib=dll"] + return _COFF(host, alignment=8, args=args) if re.fullmatch(r"aarch64-.*-linux-gnu", host): - return _ELF(host, alignment=8) + args = ["-mcmodel=large"] + return _ELF(host, alignment=8, args=args) if re.fullmatch(r"i686-pc-windows-msvc", host): - return _COFF(host, prefix="_") + args = ["-DPy_NO_ENABLE_SHARED"] + return _COFF(host, args=args, prefix="_") if re.fullmatch(r"x86_64-apple-darwin.*", host): return _MachO(host, prefix="_") if re.fullmatch(r"x86_64-pc-windows-msvc", host): - return _COFF(host) + args = ["-fms-runtime-lib=dll"] + return _COFF(host, args=args) if re.fullmatch(r"x86_64-.*-linux-gnu", host): return _ELF(host) raise ValueError(host) diff --git a/Tools/jit/template.c b/Tools/jit/template.c index d79c6efb8f6de4e..504e6c875525ae9 100644 --- a/Tools/jit/template.c +++ b/Tools/jit/template.c @@ -9,9 +9,11 @@ #include "pycore_long.h" #include "pycore_opcode_metadata.h" #include "pycore_opcode_utils.h" +#include "pycore_optimizer.h" #include "pycore_range.h" #include "pycore_setobject.h" #include "pycore_sliceobject.h" +#include "pycore_descrobject.h" #include "ceval_macros.h" @@ -58,11 +60,11 @@ do { \ } while (0) #define PATCH_VALUE(TYPE, NAME, ALIAS) \ - extern void ALIAS; \ + PyAPI_DATA(void) ALIAS; \ TYPE NAME = (TYPE)(uint64_t)&ALIAS; #define PATCH_JUMP(ALIAS) \ - extern void ALIAS; \ + PyAPI_DATA(void) ALIAS; \ __attribute__((musttail)) \ return ((jit_func)&ALIAS)(frame, stack_pointer, tstate); diff --git a/Tools/msi/bundle/Default.wxl b/Tools/msi/bundle/Default.wxl index 1540f050159a548..0014204e89d1bb7 100644 --- a/Tools/msi/bundle/Default.wxl +++ b/Tools/msi/bundle/Default.wxl @@ -88,6 +88,7 @@ Select Customize to review current options. Install Python [ShortVersion] for &all users for &all users (requires admin privileges) Use admin privi&leges when installing py.exe + Python Launcher is already installed &Precompile standard library Download debugging &symbols Download debu&g binaries (requires VS 2017 or later) diff --git a/Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp b/Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp index 3a17ffbaa0b6550..e0e179e3aede6d9 100644 --- a/Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp +++ b/Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp @@ -442,6 +442,14 @@ class PythonBootstrapperApplication : public CBalBaseBootstrapperApplication { ThemeControlElevates(_theme, ID_INSTALL_BUTTON, elevated); ThemeControlElevates(_theme, ID_INSTALL_SIMPLE_BUTTON, elevated); ThemeControlElevates(_theme, ID_INSTALL_UPGRADE_BUTTON, elevated); + + LONGLONG blockedLauncher; + if (SUCCEEDED(BalGetNumericVariable(L"BlockedLauncher", &blockedLauncher)) && blockedLauncher) { + LOC_STRING *pLocString = nullptr; + if (SUCCEEDED(LocGetString(_wixLoc, L"#(loc.ShortInstallLauncherBlockedLabel)", &pLocString)) && pLocString) { + ThemeSetTextControl(_theme, ID_INSTALL_LAUNCHER_ALL_USERS_CHECKBOX, pLocString->wzText); + } + } } void Custom1Page_Show() { @@ -718,25 +726,67 @@ class PythonBootstrapperApplication : public CBalBaseBootstrapperApplication { __in DWORD64 /*dw64Version*/, __in BOOTSTRAPPER_RELATED_OPERATION operation ) { - if (BOOTSTRAPPER_RELATED_OPERATION_MAJOR_UPGRADE == operation && - (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPackageId, -1, L"launcher_AllUsers", -1) || - CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPackageId, -1, L"launcher_JustForMe", -1))) { - auto hr = LoadAssociateFilesStateFromKey(_engine, fPerMachine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER); - if (hr == S_OK) { - _engine->SetVariableNumeric(L"AssociateFiles", 1); - } else if (hr == S_FALSE) { - _engine->SetVariableNumeric(L"AssociateFiles", 0); - } else if (FAILED(hr)) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Failed to load AssociateFiles state: error code 0x%08X", hr); + // Only check launcher_AllUsers because we'll find the same packages + // twice if we check launcher_JustForMe as well. + if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPackageId, -1, L"launcher_AllUsers", -1)) { + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Detected existing launcher install"); + + LONGLONG blockedLauncher, detectedLauncher; + if (FAILED(BalGetNumericVariable(L"BlockedLauncher", &blockedLauncher))) { + blockedLauncher = 0; + } + + // Get the prior DetectedLauncher value so we can see if we've + // detected more than one, and then update the stored variable + // (we use the original value later on via the local). + if (FAILED(BalGetNumericVariable(L"DetectedLauncher", &detectedLauncher))) { + detectedLauncher = 0; + } + if (!detectedLauncher) { + _engine->SetVariableNumeric(L"DetectedLauncher", 1); + } + + if (blockedLauncher) { + // Nothing else to do, we're already blocking + } + else if (BOOTSTRAPPER_RELATED_OPERATION_DOWNGRADE == operation) { + // Found a higher version, so we can't install ours. + BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Higher version launcher has been detected."); + BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Launcher will not be installed"); + _engine->SetVariableNumeric(L"BlockedLauncher", 1); + } + else if (detectedLauncher) { + if (!blockedLauncher) { + BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Multiple launcher installs have been detected."); + BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "No launcher will be installed or upgraded until one has been removed."); + _engine->SetVariableNumeric(L"BlockedLauncher", 1); + } } + else if (BOOTSTRAPPER_RELATED_OPERATION_MAJOR_UPGRADE == operation) { + // Found an older version, so let's run the equivalent as an upgrade + // This overrides "unknown" all users options, but will leave alone + // any that have already been set/detected. + // User can deselect the option to include the launcher, but cannot + // change it from the current per user/machine setting. + LONGLONG includeLauncher, includeLauncherAllUsers; + if (FAILED(BalGetNumericVariable(L"Include_launcher", &includeLauncher))) { + includeLauncher = -1; + } + if (FAILED(BalGetNumericVariable(L"InstallLauncherAllUsers", &includeLauncherAllUsers))) { + includeLauncherAllUsers = -1; + } - LONGLONG includeLauncher; - if (FAILED(BalGetNumericVariable(L"Include_launcher", &includeLauncher)) - || includeLauncher == -1) { - _engine->SetVariableNumeric(L"Include_launcher", 1); - _engine->SetVariableNumeric(L"InstallLauncherAllUsers", fPerMachine ? 1 : 0); + if (includeLauncher < 0) { + _engine->SetVariableNumeric(L"Include_launcher", 1); + } + if (includeLauncherAllUsers < 0) { + _engine->SetVariableNumeric(L"InstallLauncherAllUsers", fPerMachine ? 1 : 0); + } else if (includeLauncherAllUsers != fPerMachine ? 1 : 0) { + // Requested AllUsers option is inconsistent, so block + _engine->SetVariableNumeric(L"BlockedLauncher", 1); + } + _engine->SetVariableNumeric(L"DetectedOldLauncher", 1); } - _engine->SetVariableNumeric(L"DetectedOldLauncher", 1); } return CheckCanceled() ? IDCANCEL : IDNOACTION; } @@ -784,48 +834,7 @@ class PythonBootstrapperApplication : public CBalBaseBootstrapperApplication { __in LPCWSTR wzPackageId, __in HRESULT hrStatus, __in BOOTSTRAPPER_PACKAGE_STATE state - ) { - if (FAILED(hrStatus)) { - return; - } - - BOOL detectedLauncher = FALSE; - HKEY hkey = HKEY_LOCAL_MACHINE; - if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPackageId, -1, L"launcher_AllUsers", -1)) { - if (BOOTSTRAPPER_PACKAGE_STATE_PRESENT == state || BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE == state) { - detectedLauncher = TRUE; - _engine->SetVariableNumeric(L"InstallLauncherAllUsers", 1); - } - } else if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, wzPackageId, -1, L"launcher_JustForMe", -1)) { - if (BOOTSTRAPPER_PACKAGE_STATE_PRESENT == state || BOOTSTRAPPER_PACKAGE_STATE_OBSOLETE == state) { - detectedLauncher = TRUE; - _engine->SetVariableNumeric(L"InstallLauncherAllUsers", 0); - } - } - - LONGLONG includeLauncher; - if (SUCCEEDED(BalGetNumericVariable(L"Include_launcher", &includeLauncher)) - && includeLauncher != -1) { - detectedLauncher = FALSE; - } - - if (detectedLauncher) { - /* When we detect the current version of the launcher. */ - _engine->SetVariableNumeric(L"Include_launcher", 1); - _engine->SetVariableNumeric(L"DetectedLauncher", 1); - _engine->SetVariableString(L"Include_launcherState", L"disable"); - _engine->SetVariableString(L"InstallLauncherAllUsersState", L"disable"); - - auto hr = LoadAssociateFilesStateFromKey(_engine, hkey); - if (hr == S_OK) { - _engine->SetVariableNumeric(L"AssociateFiles", 1); - } else if (hr == S_FALSE) { - _engine->SetVariableNumeric(L"AssociateFiles", 0); - } else if (FAILED(hr)) { - BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Failed to load AssociateFiles state: error code 0x%08X", hr); - } - } - } + ) { } virtual STDMETHODIMP_(void) OnDetectComplete(__in HRESULT hrStatus) { @@ -835,19 +844,67 @@ class PythonBootstrapperApplication : public CBalBaseBootstrapperApplication { } if (SUCCEEDED(hrStatus)) { - LONGLONG includeLauncher; - if (SUCCEEDED(BalGetNumericVariable(L"Include_launcher", &includeLauncher)) - && includeLauncher == -1) { - if (BOOTSTRAPPER_ACTION_LAYOUT == _command.action || - (BOOTSTRAPPER_ACTION_INSTALL == _command.action && !_upgrading)) { - // When installing/downloading, we want to include the launcher - // by default. - _engine->SetVariableNumeric(L"Include_launcher", 1); - } else { - // Any other action, if we didn't detect the MSI then we want to - // keep it excluded - _engine->SetVariableNumeric(L"Include_launcher", 0); - _engine->SetVariableNumeric(L"AssociateFiles", 0); + // Update launcher install states + // If we didn't detect any existing installs, Include_launcher and + // InstallLauncherAllUsers will both be -1, so we will set to their + // defaults and leave the options enabled. + // Otherwise, if we detected an existing install, we disable the + // options so they remain fixed. + // The code in OnDetectRelatedMsiPackage is responsible for figuring + // out whether existing installs are compatible with the settings in + // place during detection. + LONGLONG blockedLauncher; + if (SUCCEEDED(BalGetNumericVariable(L"BlockedLauncher", &blockedLauncher)) + && blockedLauncher) { + _engine->SetVariableNumeric(L"Include_launcher", 0); + _engine->SetVariableNumeric(L"InstallLauncherAllUsers", 0); + _engine->SetVariableString(L"InstallLauncherAllUsersState", L"disable"); + _engine->SetVariableString(L"Include_launcherState", L"disable"); + } + else { + LONGLONG includeLauncher, includeLauncherAllUsers, associateFiles; + + if (FAILED(BalGetNumericVariable(L"Include_launcher", &includeLauncher))) { + includeLauncher = -1; + } + if (FAILED(BalGetNumericVariable(L"InstallLauncherAllUsers", &includeLauncherAllUsers))) { + includeLauncherAllUsers = -1; + } + if (FAILED(BalGetNumericVariable(L"AssociateFiles", &associateFiles))) { + associateFiles = -1; + } + + if (includeLauncherAllUsers < 0) { + includeLauncherAllUsers = 0; + _engine->SetVariableNumeric(L"InstallLauncherAllUsers", includeLauncherAllUsers); + } + + if (includeLauncher < 0) { + if (BOOTSTRAPPER_ACTION_LAYOUT == _command.action || + (BOOTSTRAPPER_ACTION_INSTALL == _command.action && !_upgrading)) { + // When installing/downloading, we include the launcher + // (though downloads should ignore this setting anyway) + _engine->SetVariableNumeric(L"Include_launcher", 1); + } else { + // Any other action, we should have detected an existing + // install (e.g. on remove/modify), so if we didn't, we + // assume it's not selected. + _engine->SetVariableNumeric(L"Include_launcher", 0); + _engine->SetVariableNumeric(L"AssociateFiles", 0); + } + } + + if (associateFiles < 0) { + auto hr = LoadAssociateFilesStateFromKey( + _engine, + includeLauncherAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER + ); + if (FAILED(hr)) { + BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "Failed to load AssociateFiles state: error code 0x%08X", hr); + } else if (hr == S_OK) { + associateFiles = 1; + } + _engine->SetVariableNumeric(L"AssociateFiles", associateFiles); } } } diff --git a/Tools/msi/bundle/bundle.wxs b/Tools/msi/bundle/bundle.wxs index 9b4f072152d5c00..abfeb88784890c8 100644 --- a/Tools/msi/bundle/bundle.wxs +++ b/Tools/msi/bundle/bundle.wxs @@ -28,10 +28,11 @@ - + - + + @@ -91,10 +92,11 @@ + - + diff --git a/Tools/msi/test/test_files.wxs b/Tools/msi/test/test_files.wxs index bb9b258692a62fb..6862a5899db2bac 100644 --- a/Tools/msi/test/test_files.wxs +++ b/Tools/msi/test/test_files.wxs @@ -1,6 +1,6 @@ - + diff --git a/Tools/requirements-dev.txt b/Tools/requirements-dev.txt index c0a63b40ff4155f..2fb616e894a734b 100644 --- a/Tools/requirements-dev.txt +++ b/Tools/requirements-dev.txt @@ -3,5 +3,5 @@ mypy==1.8.0 # needed for peg_generator: -types-psutil==5.9.5.20240106 -types-setuptools==69.0.0.20240125 +types-psutil==5.9.5.20240205 +types-setuptools==69.1.0.20240301 diff --git a/Tools/requirements-hypothesis.txt b/Tools/requirements-hypothesis.txt index 064731a236ee863..1a45d1c431dd11e 100644 --- a/Tools/requirements-hypothesis.txt +++ b/Tools/requirements-hypothesis.txt @@ -1,4 +1,4 @@ # Requirements file for hypothesis that # we use to run our property-based tests in CI. -hypothesis==6.97.4 +hypothesis==6.98.15 diff --git a/Tools/scripts/sortperf.py b/Tools/scripts/sortperf.py index b54681524ac1731..1978a6c83dbe2b3 100644 --- a/Tools/scripts/sortperf.py +++ b/Tools/scripts/sortperf.py @@ -130,7 +130,8 @@ def run(self, loops: int) -> float: def _prepare_data(self, loops: int) -> list[float]: bench = BENCHMARKS[self._name] - return [bench(self._size, self._random)] * loops + data = bench(self._size, self._random) + return [data.copy() for _ in range(loops)] def add_cmdline_args(cmd: list[str], args) -> None: diff --git a/Tools/scripts/summarize_stats.py b/Tools/scripts/summarize_stats.py index 6b60b59b3b0e797..6af14e1b769b803 100644 --- a/Tools/scripts/summarize_stats.py +++ b/Tools/scripts/summarize_stats.py @@ -451,6 +451,7 @@ def get_optimization_stats(self) -> dict[str, tuple[int, int | None]]: inner_loop = self._data["Optimization inner loop"] recursive_call = self._data["Optimization recursive call"] low_confidence = self._data["Optimization low confidence"] + executors_invalidated = self._data["Executors invalidated"] return { Doc( @@ -458,10 +459,7 @@ def get_optimization_stats(self) -> dict[str, tuple[int, int | None]]: "The number of times a potential trace is identified. Specifically, this " "occurs in the JUMP BACKWARD instruction when the counter reaches a " "threshold.", - ): ( - attempts, - None, - ), + ): (attempts, None), Doc( "Traces created", "The number of traces that were successfully created." ): (created, attempts), @@ -493,16 +491,44 @@ def get_optimization_stats(self) -> dict[str, tuple[int, int | None]]: "A trace is abandoned because the likelihood of the jump to top being taken " "is too low.", ): (low_confidence, attempts), + Doc( + "Executors invalidated", + "The number of executors that were invalidated due to watched " + "dictionary changes.", + ): (executors_invalidated, created), Doc("Traces executed", "The number of traces that were executed"): ( executed, None, ), - Doc("Uops executed", "The total number of uops (micro-operations) that were executed"): ( + Doc( + "Uops executed", + "The total number of uops (micro-operations) that were executed", + ): ( uops, executed, ), } + def get_optimizer_stats(self) -> dict[str, tuple[int, int | None]]: + attempts = self._data["Optimization optimizer attempts"] + successes = self._data["Optimization optimizer successes"] + no_memory = self._data["Optimization optimizer failure no memory"] + + return { + Doc( + "Optimizer attempts", + "The number of times the trace optimizer (_Py_uop_analyze_and_optimize) was run.", + ): (attempts, None), + Doc( + "Optimizer successes", + "The number of traces that were successfully optimized.", + ): (successes, attempts), + Doc( + "Optimizer no memory", + "The number of optimizations that failed due to no memory.", + ): (no_memory, attempts), + } + def get_histogram(self, prefix: str) -> list[tuple[int, int]]: rows = [] for k, v in self._data.items(): @@ -1109,6 +1135,14 @@ def calc_optimization_table(stats: Stats) -> Rows: for label, (value, den) in optimization_stats.items() ] + def calc_optimizer_table(stats: Stats) -> Rows: + optimizer_stats = stats.get_optimizer_stats() + + return [ + (label, Count(value), Ratio(value, den)) + for label, (value, den) in optimizer_stats.items() + ] + def calc_histogram_table(key: str, den: str) -> RowCalculator: def calc(stats: Stats) -> Rows: histogram = stats.get_histogram(key) @@ -1150,6 +1184,7 @@ def iter_optimization_tables(base_stats: Stats, head_stats: Stats | None = None) return yield Table(("", "Count:", "Ratio:"), calc_optimization_table, JoinMode.CHANGE) + yield Table(("", "Count:", "Ratio:"), calc_optimizer_table, JoinMode.CHANGE) for name, den in [ ("Trace length", "Optimization traces created"), ("Optimized trace length", "Optimization traces created"), diff --git a/Tools/tsan/supressions.txt b/Tools/tsan/supressions.txt new file mode 100644 index 000000000000000..448dfac8005c79f --- /dev/null +++ b/Tools/tsan/supressions.txt @@ -0,0 +1,5 @@ +## reference: https://github.com/google/sanitizers/wiki/ThreadSanitizerSuppressions +race:get_allocator_unlocked +race:set_allocator_unlocked +race:mi_heap_visit_pages +race:_mi_heap_delayed_free_partial diff --git a/Tools/wasm/README.md b/Tools/wasm/README.md index 23b38c8e93638ad..eca113d3bfabadf 100644 --- a/Tools/wasm/README.md +++ b/Tools/wasm/README.md @@ -1,6 +1,7 @@ # Python WebAssembly (WASM) build -**WARNING: WASM support is work-in-progress! Lots of features are not working yet.** +**WASI support is [tier 2](https://peps.python.org/pep-0011/#tier-2).** +**Emscripten is NOT officially supported as of Python 3.13.** This directory contains configuration and helpers to facilitate cross compilation of CPython to WebAssembly (WASM). Python supports Emscripten @@ -298,66 +299,7 @@ AddType application/wasm wasm ## WASI (wasm32-wasi) -**NOTE**: The instructions below assume a Unix-based OS due to cross-compilation for CPython being set up for `./configure`. - -### Prerequisites - -Developing for WASI requires two additional tools to be installed beyond the typical tools required to build CPython: - -1. The [WASI SDK](https://github.com/WebAssembly/wasi-sdk) 16.0+ -2. A WASI host/runtime ([wasmtime](https://wasmtime.dev) 14+ is recommended and what the instructions below assume) - -All of this is provided in the [devcontainer](https://devguide.python.org/getting-started/setup-building/#contribute-using-github-codespaces) if you don't want to install these tools locally. - -### Building - -Building for WASI requires doing a cross-build where you have a "build" Python to help produce a WASI build of CPython (technically it's a "host x host" cross-build because the build Python is also the target Python while the host build is the WASI build; yes, it's confusing terminology). In the end you should have a build Python in `cross-build/build` and a WASI build in `cross-build/wasm32-wasi`. - -The easiest way to do a build is to use the `wasi.py` script. You can either have it perform the entire build process from start to finish in one step, or you can do it in discrete steps that mirror running `configure` and `make` for each of the two builds of Python you end up producing (which are beneficial when you only need to do a specific step after getting a complete build, e.g. editing some code and you just need to run `make` for the WASI build). The script is designed to self-document what actions it is performing on your behalf, both as a way to check its work but also for educaitonal purposes. - -The discrete steps for building via `wasi.py` are: -```shell -python Tools/wasm/wasi.py configure-build-python -python Tools/wasm/wasi.py make-build-python -python Tools/wasm/wasi.py configure-host -python Tools/wasm/wasi.py make-host -``` - -To do it all in a single command, run: -```shell -python Tools/wasm/wasi.py build -``` - -That will: - -1. Run `configure` for the build Python (same as `wasi.py configure-build-python`) -2. Run `make` for the build Python (`wasi.py make-build-python`) -3. Run `configure` for the WASI build (`wasi.py configure-host`) -4. Run `make` for the WASI build (`wasi.py make-host`) - -See the `--help` for the various options available for each of the subcommands which controls things like the location of the WASI SDK, the command to use with the WASI host/runtime, etc. Also note that you can use `--` as a separator for any of the `configure`-related commands -- including `build` itself -- to pass arguments to the underlying `configure` call. For example, if you want a pydebug build that also caches the results from `configure`, you can do: -```shell -python Tools/wasm/wasi.py build -- -C --with-pydebug -``` - -The `wasi.py` script is able to infer details from the build Python, and so you only technically need to specify `--with-pydebug` once via `configure-build-python` as this will lead to `configure-host` detecting its use if you use the discrete steps: -```shell -python Tools/wasm/wasi.py configure-build-python -- -C --with-pydebug -python Tools/wasm/wasi.py make-build-python -python Tools/wasm/wasi.py configure-host -- -C -python Tools/wasm/wasi.py make-host -``` - - -### Running - -If you used `wasi.py` to do your build then there will be a `cross-build/wasm32-wasi/python.sh` file which you can use to run the `python.wasm` file (see the output from the `configure-host` subcommand): -```shell -cross-build/wasm32-wasi/python.sh --version -``` - -While you _can_ run `python.wasm` directly, Python will fail to start up without certain things being set (e.g. `PYTHONPATH` for `sysconfig` data). As such, the `python.sh` file records these details for you. - +See [the devguide on how to build and run for WASI](https://devguide.python.org/getting-started/setup-building/#wasi). ## Detecting WebAssembly builds diff --git a/Tools/wasm/config.site-wasm32-wasi b/Tools/wasm/config.site-wasm32-wasi index 5e98775400f6ead..4a1a466a4ab3f16 100644 --- a/Tools/wasm/config.site-wasm32-wasi +++ b/Tools/wasm/config.site-wasm32-wasi @@ -40,3 +40,12 @@ ac_cv_header_netpacket_packet_h=no # Disable int-conversion for wask-sdk as it triggers an error from version 17. ac_cv_disable_int_conversion=yes + +# preadv(), readv(), pwritev(), and writev() under wasmtime's WASI 0.2 support +# do not use more than the first buffer provided, failing under test_posix. +# Since wasmtime will not be changing this behaviour, disable the functions. +# https://github.com/bytecodealliance/wasmtime/issues/7830 +ac_cv_func_preadv=no +ac_cv_func_readv=no +ac_cv_func_pwritev=no +ac_cv_func_writev=no diff --git a/Tools/wasm/wasi.py b/Tools/wasm/wasi.py index 1e75db5c7b83299..efb005e53ab989b 100644 --- a/Tools/wasm/wasi.py +++ b/Tools/wasm/wasi.py @@ -283,8 +283,10 @@ def main(): # The 8388608 value comes from `ulimit -s` under Linux # which equates to 8291 KiB. "--wasm max-wasm-stack=8388608 " - # Enable thread support. - "--wasm threads=y --wasi threads=y " + # Use WASI 0.2 primitives. + "--wasi preview2 " + # Enable thread support; causes use of preview1. + #"--wasm threads=y --wasi threads=y " # Map the checkout to / to load the stdlib from /Lib. "--dir {HOST_DIR}::{GUEST_DIR} " # Set PYTHONPATH to the sysconfig data. diff --git a/Tools/wasm/wasm_build.py b/Tools/wasm/wasm_build.py index c0b9999a5dad037..47a0abb8b5feef2 100755 --- a/Tools/wasm/wasm_build.py +++ b/Tools/wasm/wasm_build.py @@ -329,8 +329,10 @@ def _check_wasi() -> None: # workaround for https://github.com/python/cpython/issues/95952 "HOSTRUNNER": ( "wasmtime run " - "--env PYTHONPATH=/{relbuilddir}/build/lib.wasi-wasm32-{version}:/Lib " - "--mapdir /::{srcdir} --" + "--wasm max-wasm-stack=8388608 " + "--wasi preview2 " + "--dir {srcdir}::/ " + "--env PYTHONPATH=/{relbuilddir}/build/lib.wasi-wasm32-{version}:/Lib" ), "PATH": [WASI_SDK_PATH / "bin", os.environ["PATH"]], }, diff --git a/aclocal.m4 b/aclocal.m4 index 09ae5d1aa8a608e..832aec19f48f178 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -150,6 +150,80 @@ AS_VAR_IF(CACHEVAR,yes, AS_VAR_POPDEF([CACHEVAR])dnl ])dnl AX_CHECK_COMPILE_FLAGS +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_check_define.html +# =========================================================================== +# +# SYNOPSIS +# +# AC_CHECK_DEFINE([symbol], [ACTION-IF-FOUND], [ACTION-IF-NOT]) +# AX_CHECK_DEFINE([includes],[symbol], [ACTION-IF-FOUND], [ACTION-IF-NOT]) +# +# DESCRIPTION +# +# Complements AC_CHECK_FUNC but it does not check for a function but for a +# define to exist. Consider a usage like: +# +# AC_CHECK_DEFINE(__STRICT_ANSI__, CFLAGS="$CFLAGS -D_XOPEN_SOURCE=500") +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 11 + +AU_ALIAS([AC_CHECK_DEFINED], [AC_CHECK_DEFINE]) +AC_DEFUN([AC_CHECK_DEFINE],[ +AS_VAR_PUSHDEF([ac_var],[ac_cv_defined_$1])dnl +AC_CACHE_CHECK([for $1 defined], ac_var, +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[ + #ifdef $1 + int ok; + (void)ok; + #else + choke me + #endif +]])],[AS_VAR_SET(ac_var, yes)],[AS_VAR_SET(ac_var, no)])) +AS_IF([test AS_VAR_GET(ac_var) != "no"], [$2], [$3])dnl +AS_VAR_POPDEF([ac_var])dnl +]) + +AU_ALIAS([AX_CHECK_DEFINED], [AX_CHECK_DEFINE]) +AC_DEFUN([AX_CHECK_DEFINE],[ +AS_VAR_PUSHDEF([ac_var],[ac_cv_defined_$2_$1])dnl +AC_CACHE_CHECK([for $2 defined in $1], ac_var, +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <$1>]], [[ + #ifdef $2 + int ok; + (void)ok; + #else + choke me + #endif +]])],[AS_VAR_SET(ac_var, yes)],[AS_VAR_SET(ac_var, no)])) +AS_IF([test AS_VAR_GET(ac_var) != "no"], [$3], [$4])dnl +AS_VAR_POPDEF([ac_var])dnl +]) + +AC_DEFUN([AX_CHECK_FUNC], +[AS_VAR_PUSHDEF([ac_var], [ac_cv_func_$2])dnl +AC_CACHE_CHECK([for $2], ac_var, +dnl AC_LANG_FUNC_LINK_TRY +[AC_LINK_IFELSE([AC_LANG_PROGRAM([$1 + #undef $2 + char $2 ();],[ + char (*f) () = $2; + return f != $2; ])], + [AS_VAR_SET(ac_var, yes)], + [AS_VAR_SET(ac_var, no)])]) +AS_IF([test AS_VAR_GET(ac_var) = yes], [$3], [$4])dnl +AS_VAR_POPDEF([ac_var])dnl +])# AC_CHECK_FUNC + # =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_check_openssl.html # =========================================================================== diff --git a/config.sub b/config.sub index 2c6a07ab3c34eab..1bb6a05dc11026c 100755 --- a/config.sub +++ b/config.sub @@ -4,6 +4,7 @@ # shellcheck disable=SC2006,SC2268 # see below for rationale +# Patched 2024-02-03 to include support for arm64_32 and iOS/tvOS/watchOS simulators timestamp='2024-01-01' # This file is free software; you can redistribute it and/or modify it @@ -1127,7 +1128,7 @@ case $cpu-$vendor in xscale-* | xscalee[bl]-*) cpu=`echo "$cpu" | sed 's/^xscale/arm/'` ;; - arm64-* | aarch64le-*) + arm64-* | aarch64le-* | arm64_32-*) cpu=aarch64 ;; @@ -1866,6 +1867,8 @@ case $kernel-$os-$obj in ;; *-eabi*- | *-gnueabi*-) ;; + ios*-simulator- | tvos*-simulator- | watchos*-simulator- ) + ;; none--*) # None (no kernel, i.e. freestanding / bare metal), # can be paired with an machine code file format diff --git a/configure b/configure index fcf34f050861bea..9b40c48979b1be7 100755 --- a/configure +++ b/configure @@ -661,6 +661,10 @@ MODULE__XXTESTFUZZ_FALSE MODULE__XXTESTFUZZ_TRUE MODULE_XXSUBTYPE_FALSE MODULE_XXSUBTYPE_TRUE +MODULE__TESTEXTERNALINSPECTION_FALSE +MODULE__TESTEXTERNALINSPECTION_TRUE +MODULE__TESTSINGLEPHASE_FALSE +MODULE__TESTSINGLEPHASE_TRUE MODULE__TESTMULTIPHASE_FALSE MODULE__TESTMULTIPHASE_TRUE MODULE__TESTIMPORTMULTIPLE_FALSE @@ -669,6 +673,8 @@ MODULE__TESTBUFFER_FALSE MODULE__TESTBUFFER_TRUE MODULE__TESTINTERNALCAPI_FALSE MODULE__TESTINTERNALCAPI_TRUE +MODULE__TESTLIMITEDCAPI_FALSE +MODULE__TESTLIMITEDCAPI_TRUE MODULE__TESTCLINIC_LIMITED_FALSE MODULE__TESTCLINIC_LIMITED_TRUE MODULE__TESTCLINIC_FALSE @@ -970,6 +976,7 @@ LDFLAGS CFLAGS CC HAS_XCRUN +IOS_DEPLOYMENT_TARGET EXPORT_MACOSX_DEPLOYMENT_TARGET CONFIGURE_MACOSX_DEPLOYMENT_TARGET _PYTHON_HOST_PLATFORM @@ -4029,6 +4036,9 @@ then *-*-cygwin*) ac_sys_system=Cygwin ;; + *-apple-ios*) + ac_sys_system=iOS + ;; *-*-vxworks*) ac_sys_system=VxWorks ;; @@ -4061,6 +4071,7 @@ then case $MACHDEP in aix*) MACHDEP="aix";; + linux-android*) MACHDEP="android";; linux*) MACHDEP="linux";; cygwin*) MACHDEP="cygwin";; darwin*) MACHDEP="darwin";; @@ -4079,6 +4090,52 @@ fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: \"$MACHDEP\"" >&5 printf "%s\n" "\"$MACHDEP\"" >&6; } +# On cross-compile builds, configure will look for a host-specific compiler by +# prepending the user-provided host triple to the required binary name. +# +# On iOS, this results in binaries like "arm64-apple-ios12.0-simulator-gcc", +# which isn't a binary that exists, and isn't very convenient, as it contains the +# iOS version. As the default cross-compiler name won't exist, configure falls +# back to gcc, which *definitely* won't work. We're providing wrapper scripts for +# these tools; the binary names of these scripts are better defaults than "gcc". +# This only requires that the user put the platform scripts folder (e.g., +# "iOS/Resources/bin") in their path, rather than defining platform-specific +# names/paths for AR, CC, CPP, and CXX explicitly; and if the user forgets to +# either put the platform scripts folder in the path, or specify CC etc, +# configure will fail. +if test -z "$AR"; then + case "$host" in + aarch64-apple-ios*-simulator) AR=arm64-apple-ios-simulator-ar ;; + aarch64-apple-ios*) AR=arm64-apple-ios-ar ;; + x86_64-apple-ios*-simulator) AR=x86_64-apple-ios-simulator-ar ;; + *) + esac +fi +if test -z "$CC"; then + case "$host" in + aarch64-apple-ios*-simulator) CC=arm64-apple-ios-simulator-clang ;; + aarch64-apple-ios*) CC=arm64-apple-ios-clang ;; + x86_64-apple-ios*-simulator) CC=x86_64-apple-ios-simulator-clang ;; + *) + esac +fi +if test -z "$CPP"; then + case "$host" in + aarch64-apple-ios*-simulator) CPP=arm64-apple-ios-simulator-cpp ;; + aarch64-apple-ios*) CPP=arm64-apple-ios-cpp ;; + x86_64-apple-ios*-simulator) CPP=x86_64-apple-ios-simulator-cpp ;; + *) + esac +fi +if test -z "$CXX"; then + case "$host" in + aarch64-apple-ios*-simulator) CXX=arm64-apple-ios-simulator-clang ;; + aarch64-apple-ios*) CXX=arm64-apple-ios-clang ;; + x86_64-apple-ios*-simulator) CXX=x86_64-apple-ios-simulator-clang ;; + *) + esac +fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for --enable-universalsdk" >&5 printf %s "checking for --enable-universalsdk... " >&6; } # Check whether --enable-universalsdk was given. @@ -4194,38 +4251,47 @@ then : enableval=$enable_framework; case $enableval in yes) - enableval=/Library/Frameworks + case $ac_sys_system in + Darwin) enableval=/Library/Frameworks ;; + iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;; + *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 + esac esac + case $enableval in no) - PYTHONFRAMEWORK= - PYTHONFRAMEWORKDIR=no-framework - PYTHONFRAMEWORKPREFIX= - PYTHONFRAMEWORKINSTALLDIR= - PYTHONFRAMEWORKINSTALLNAMEPREFIX= - RESSRCDIR= - FRAMEWORKINSTALLFIRST= - FRAMEWORKINSTALLLAST= - FRAMEWORKALTINSTALLFIRST= - FRAMEWORKALTINSTALLLAST= - FRAMEWORKPYTHONW= - INSTALLTARGETS="commoninstall bininstall maninstall" - - if test "x${prefix}" = "xNONE"; then - FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" - else - FRAMEWORKUNIXTOOLSPREFIX="${prefix}" - fi - enable_framework= + case $ac_sys_system in + iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;; + *) + PYTHONFRAMEWORK= + PYTHONFRAMEWORKDIR=no-framework + PYTHONFRAMEWORKPREFIX= + PYTHONFRAMEWORKINSTALLDIR= + PYTHONFRAMEWORKINSTALLNAMEPREFIX= + RESSRCDIR= + FRAMEWORKINSTALLFIRST= + FRAMEWORKINSTALLLAST= + FRAMEWORKALTINSTALLFIRST= + FRAMEWORKALTINSTALLLAST= + FRAMEWORKPYTHONW= + INSTALLTARGETS="commoninstall bininstall maninstall" + + if test "x${prefix}" = "xNONE"; then + FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" + else + FRAMEWORKUNIXTOOLSPREFIX="${prefix}" + fi + enable_framework= + esac ;; *) PYTHONFRAMEWORKPREFIX="${enableval}" PYTHONFRAMEWORKINSTALLDIR=$PYTHONFRAMEWORKPREFIX/$PYTHONFRAMEWORKDIR - FRAMEWORKINSTALLFIRST="frameworkinstallstructure" - FRAMEWORKALTINSTALLFIRST="frameworkinstallstructure " case $ac_sys_system in #( Darwin) : + FRAMEWORKINSTALLFIRST="frameworkinstallversionedstructure" + FRAMEWORKALTINSTALLFIRST="frameworkinstallversionedstructure " FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" FRAMEWORKPYTHONW="frameworkpythonw" @@ -4287,6 +4353,21 @@ then : ac_config_files="$ac_config_files Mac/Resources/app/Info.plist" + ;; + iOS) : + FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" + FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " + FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" + FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" + FRAMEWORKPYTHONW= + INSTALLTARGETS="libinstall inclinstall sharedinstall" + + prefix=$PYTHONFRAMEWORKPREFIX + PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" + RESSRCDIR=iOS/Resources + + ac_config_files="$ac_config_files iOS/Resources/Info.plist" + ;; *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5 @@ -4296,25 +4377,28 @@ then : else $as_nop - PYTHONFRAMEWORK= - PYTHONFRAMEWORKDIR=no-framework - PYTHONFRAMEWORKPREFIX= - PYTHONFRAMEWORKINSTALLDIR= - PYTHONFRAMEWORKINSTALLNAMEPREFIX= - RESSRCDIR= - FRAMEWORKINSTALLFIRST= - FRAMEWORKINSTALLLAST= - FRAMEWORKALTINSTALLFIRST= - FRAMEWORKALTINSTALLLAST= - FRAMEWORKPYTHONW= - INSTALLTARGETS="commoninstall bininstall maninstall" - if test "x${prefix}" = "xNONE" ; then - FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" - else - FRAMEWORKUNIXTOOLSPREFIX="${prefix}" - fi - enable_framework= - + case $ac_sys_system in + iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;; + *) + PYTHONFRAMEWORK= + PYTHONFRAMEWORKDIR=no-framework + PYTHONFRAMEWORKPREFIX= + PYTHONFRAMEWORKINSTALLDIR= + PYTHONFRAMEWORKINSTALLNAMEPREFIX= + RESSRCDIR= + FRAMEWORKINSTALLFIRST= + FRAMEWORKINSTALLLAST= + FRAMEWORKALTINSTALLFIRST= + FRAMEWORKALTINSTALLLAST= + FRAMEWORKPYTHONW= + INSTALLTARGETS="commoninstall bininstall maninstall" + if test "x${prefix}" = "xNONE" ; then + FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" + else + FRAMEWORKUNIXTOOLSPREFIX="${prefix}" + fi + enable_framework= + esac fi @@ -4353,6 +4437,23 @@ if test "$cross_compiling" = yes; then *-*-cygwin*) _host_ident= ;; + *-apple-ios*) + _host_os=`echo $host | cut -d '-' -f3` + _host_device=`echo $host | cut -d '-' -f4` + _host_device=${_host_device:=os} + + IOS_DEPLOYMENT_TARGET=${_host_os:3} + IOS_DEPLOYMENT_TARGET=${IOS_DEPLOYMENT_TARGET:=12.0} + + case "$host_cpu" in + aarch64) + _host_ident=${IOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device} + ;; + *) + _host_ident=${IOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device} + ;; + esac + ;; *-*-vxworks*) _host_ident=$host_cpu ;; @@ -4431,6 +4532,9 @@ printf "%s\n" "#define _BSD_SOURCE 1" >>confdefs.h define_xopen_source=no;; Darwin/[12][0-9].*) define_xopen_source=no;; + # On iOS, defining _POSIX_C_SOURCE also disables platform specific features. + iOS/*) + define_xopen_source=no;; # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from # defining NI_NUMERICHOST. QNX/6.3.2) @@ -4525,6 +4629,17 @@ case $host in #( ;; esac +case $ac_sys_system in #( + iOS) : + + as_fn_append CFLAGS " -mios-version-min=${IOS_DEPLOYMENT_TARGET}" + as_fn_append LDFLAGS " -mios-version-min=${IOS_DEPLOYMENT_TARGET}" + + ;; #( + *) : + ;; +esac + if test "$ac_sys_system" = "Darwin" then # Extract the first word of "xcrun", so it can be a program name with args. @@ -6787,6 +6902,8 @@ printf %s "checking for multiarch... " >&6; } case $ac_sys_system in #( Darwin*) : MULTIARCH="" ;; #( + iOS) : + MULTIARCH="" ;; #( FreeBSD*) : MULTIARCH="" ;; #( *) : @@ -6807,6 +6924,8 @@ fi printf "%s\n" "$MULTIARCH" >&6; } case $ac_sys_system in #( + iOS) : + SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2` ;; #( *) : SOABI_PLATFORM=$PLATFORM_TRIPLET ;; @@ -6852,6 +6971,10 @@ case $host/$ac_cv_cc_name in #( PY_SUPPORT_TIER=3 ;; #( x86_64-*-freebsd*/clang) : PY_SUPPORT_TIER=3 ;; #( + aarch64-apple-ios*-simulator/clang) : + PY_SUPPORT_TIER=3 ;; #( + aarch64-apple-ios*/clang) : + PY_SUPPORT_TIER=3 ;; #( *) : PY_SUPPORT_TIER=0 ;; @@ -7307,12 +7430,15 @@ printf %s "checking LDLIBRARY... " >&6; } # will find it with a -framework option). For this reason there is an # extra variable BLDLIBRARY against which Python and the extension # modules are linked, BLDLIBRARY. This is normally the same as -# LDLIBRARY, but empty for MacOSX framework builds. +# LDLIBRARY, but empty for MacOSX framework builds. iOS does the same, +# but uses a non-versioned framework layout. if test "$enable_framework" then case $ac_sys_system in Darwin) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; + iOS) + LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; *) as_fn_error $? "Unknown platform for framework build" "$LINENO" 5;; esac @@ -7348,7 +7474,13 @@ printf "%s\n" "#define Py_ENABLE_SHARED 1" >>confdefs.h LDLIBRARY='libpython$(LDVERSION).so' BLDLIBRARY='-L. -lpython$(LDVERSION)' RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} - INSTSONAME="$LDLIBRARY".$SOVERSION + + # The Android Gradle plugin will only package libraries whose names end + # with ".so". + if test "$ac_sys_system" != "Linux-android"; then + INSTSONAME="$LDLIBRARY".$SOVERSION + fi + if test "$with_pydebug" != yes then PY3LIBRARY=libpython3.so @@ -7371,6 +7503,9 @@ printf "%s\n" "#define Py_ENABLE_SHARED 1" >>confdefs.h BLDLIBRARY='-L. -lpython$(LDVERSION)' RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} ;; + iOS) + LDLIBRARY='libpython$(LDVERSION).dylib' + ;; AIX*) LDLIBRARY='libpython$(LDVERSION).so' RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} @@ -7571,7 +7706,7 @@ then : fi ;; #( WASI/*) : - HOSTRUNNER='wasmtime run --env PYTHONPATH=/$(shell realpath --relative-to $(abs_srcdir) $(abs_builddir))/$(shell cat pybuilddir.txt):/Lib --mapdir /::$(srcdir) --' ;; #( + HOSTRUNNER='wasmtime run --wasm max-wasm-stack=8388608 --wasi preview2 --env PYTHONPATH=/$(shell realpath --relative-to $(abs_srcdir) $(abs_builddir))/$(shell cat pybuilddir.txt):/Lib --dir $(srcdir)::/' ;; #( *) : HOSTRUNNER='' ;; @@ -11219,42 +11354,22 @@ then : fi -# checks for typedefs - -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for clock_t in time.h" >&5 -printf %s "checking for clock_t in time.h... " >&6; } -if test ${ac_cv_clock_t_time_h+y} +# Check for clock_t in time.h. +ac_fn_c_check_type "$LINENO" "clock_t" "ac_cv_type_clock_t" "#include +" +if test "x$ac_cv_type_clock_t" = xyes then : - printf %s "(cached) " >&6 -else $as_nop - - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -_ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "clock_t" >/dev/null 2>&1 -then : - ac_cv_clock_t_time_h=yes -else $as_nop - ac_cv_clock_t_time_h=no -fi -rm -rf conftest* +printf "%s\n" "#define HAVE_CLOCK_T 1" >>confdefs.h -fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_clock_t_time_h" >&5 -printf "%s\n" "$ac_cv_clock_t_time_h" >&6; } -if test "x$ac_cv_clock_t_time_h" = xno -then : - +else $as_nop printf "%s\n" "#define clock_t long" >>confdefs.h - fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for makedev" >&5 printf %s "checking for makedev... " >&6; } if test ${ac_cv_func_makedev+y} @@ -11456,6 +11571,7 @@ printf "%s\n" "#define size_t unsigned int" >>confdefs.h fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for uid_t in sys/types.h" >&5 printf %s "checking for uid_t in sys/types.h... " >&6; } if test ${ac_cv_type_uid_t+y} @@ -12624,6 +12740,7 @@ if test -z "$SHLIB_SUFFIX"; then esac ;; CYGWIN*) SHLIB_SUFFIX=.dll;; + iOS) SHLIB_SUFFIX=.dylib;; *) SHLIB_SUFFIX=.so;; esac fi @@ -12706,6 +12823,11 @@ then BLDSHARED="$LDSHARED" fi ;; + iOS/*) + LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' + LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' + BLDSHARED="$LDSHARED" + ;; Emscripten|WASI) LDSHARED='$(CC) -shared' LDCXXSHARED='$(CXX) -shared';; @@ -12834,30 +12956,34 @@ then Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; # -u libsys_s pulls in all symbols in libsys - Darwin/*) + Darwin/*|iOS/*) LINKFORSHARED="$extra_undefs -framework CoreFoundation" # Issue #18075: the default maximum stack size (8MBytes) is too # small for the default recursion limit. Increase the stack size # to ensure that tests don't crash - stack_size="1000000" # 16 MB - if test "$with_ubsan" = "yes" - then - # Undefined behavior sanitizer requires an even deeper stack - stack_size="4000000" # 64 MB - fi - - LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" + stack_size="1000000" # 16 MB + if test "$with_ubsan" = "yes" + then + # Undefined behavior sanitizer requires an even deeper stack + stack_size="4000000" # 64 MB + fi printf "%s\n" "#define THREAD_STACK_SIZE 0x$stack_size" >>confdefs.h - if test "$enable_framework" - then - LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' + if test $ac_sys_system = "Darwin"; then + LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" + + if test "$enable_framework"; then + LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' + fi + LINKFORSHARED="$LINKFORSHARED" + elif test $ac_sys_system = "iOS"; then + LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' fi - LINKFORSHARED="$LINKFORSHARED";; + ;; OpenUNIX*|UnixWare*) LINKFORSHARED="-Wl,-Bexport";; SCO_SV*) LINKFORSHARED="-Wl,-Bexport";; ReliantUNIX*) LINKFORSHARED="-W1 -Blargedynsym";; @@ -13657,7 +13783,14 @@ then : else $as_nop if test "$cross_compiling" = yes then : + +# "yes" changes the hash function to FNV, which causes problems with Numba +# (https://github.com/numba/numba/blob/0.59.0/numba/cpython/hashing.py#L470). +if test "$ac_sys_system" = "Linux-android"; then + ac_cv_aligned_required=no +else ac_cv_aligned_required=yes +fi else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -14246,6 +14379,10 @@ then : ctypes_malloc_closure=yes ;; #( + iOS) : + + ctypes_malloc_closure=yes + ;; #( sunos5) : as_fn_append LIBFFI_LIBS " -mimpure-text" ;; #( @@ -16092,24 +16229,47 @@ else # (e.g. gnu pth with pthread emulation) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for _POSIX_THREADS in unistd.h" >&5 printf %s "checking for _POSIX_THREADS in unistd.h... " >&6; } - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for _POSIX_THREADS defined in unistd.h" >&5 +printf %s "checking for _POSIX_THREADS defined in unistd.h... " >&6; } +if test ${ac_cv_defined__POSIX_THREADS_unistd_h+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ #include -#ifdef _POSIX_THREADS -yes -#endif +int +main (void) +{ + #ifdef _POSIX_THREADS + int ok; + (void)ok; + #else + choke me + #endif + + ; + return 0; +} _ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "yes" >/dev/null 2>&1 +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_defined__POSIX_THREADS_unistd_h=yes +else $as_nop + ac_cv_defined__POSIX_THREADS_unistd_h=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_defined__POSIX_THREADS_unistd_h" >&5 +printf "%s\n" "$ac_cv_defined__POSIX_THREADS_unistd_h" >&6; } +if test $ac_cv_defined__POSIX_THREADS_unistd_h != "no" then : unistd_defines_pthreads=yes else $as_nop unistd_defines_pthreads=no fi -rm -rf conftest* - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $unistd_defines_pthreads" >&5 printf "%s\n" "$unistd_defines_pthreads" >&6; } @@ -16610,65 +16770,135 @@ ipv6lib=none ipv6trylibc=no if test "$ipv6" = yes -a "$cross_compiling" = no; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking ipv6 stack type" >&5 -printf %s "checking ipv6 stack type... " >&6; } for i in inria kame linux-glibc linux-inet6 solaris toshiba v6d zeta; do case $i in inria) - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for IPV6_INRIA_VERSION defined in netinet/in.h" >&5 +printf %s "checking for IPV6_INRIA_VERSION defined in netinet/in.h... " >&6; } +if test ${ac_cv_defined_IPV6_INRIA_VERSION_netinet_in_h+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ #include -#ifdef IPV6_INRIA_VERSION -yes -#endif +int +main (void) +{ + + #ifdef IPV6_INRIA_VERSION + int ok; + (void)ok; + #else + choke me + #endif + + ; + return 0; +} _ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "yes" >/dev/null 2>&1 +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_defined_IPV6_INRIA_VERSION_netinet_in_h=yes +else $as_nop + ac_cv_defined_IPV6_INRIA_VERSION_netinet_in_h=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_defined_IPV6_INRIA_VERSION_netinet_in_h" >&5 +printf "%s\n" "$ac_cv_defined_IPV6_INRIA_VERSION_netinet_in_h" >&6; } +if test $ac_cv_defined_IPV6_INRIA_VERSION_netinet_in_h != "no" then : ipv6type=$i fi -rm -rf conftest* - ;; kame) - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for __KAME__ defined in netinet/in.h" >&5 +printf %s "checking for __KAME__ defined in netinet/in.h... " >&6; } +if test ${ac_cv_defined___KAME___netinet_in_h+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ #include -#ifdef __KAME__ -yes -#endif +int +main (void) +{ + + #ifdef __KAME__ + int ok; + (void)ok; + #else + choke me + #endif + + ; + return 0; +} _ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "yes" >/dev/null 2>&1 +if ac_fn_c_try_compile "$LINENO" then : - ipv6type=$i; - ipv6lib=inet6 - ipv6libdir=/usr/local/v6/lib - ipv6trylibc=yes + ac_cv_defined___KAME___netinet_in_h=yes +else $as_nop + ac_cv_defined___KAME___netinet_in_h=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_defined___KAME___netinet_in_h" >&5 +printf "%s\n" "$ac_cv_defined___KAME___netinet_in_h" >&6; } +if test $ac_cv_defined___KAME___netinet_in_h != "no" +then : + ipv6type=$i + ipv6lib=inet6 + ipv6libdir=/usr/local/v6/lib + ipv6trylibc=yes fi -rm -rf conftest* - ;; linux-glibc) - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for __GLIBC__ defined in features.h" >&5 +printf %s "checking for __GLIBC__ defined in features.h... " >&6; } +if test ${ac_cv_defined___GLIBC___features_h+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ #include -#if defined(__GLIBC__) && ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 1) || (__GLIBC__ > 2)) -yes -#endif +int +main (void) +{ + + #ifdef __GLIBC__ + int ok; + (void)ok; + #else + choke me + #endif + + ; + return 0; +} _ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "yes" >/dev/null 2>&1 +if ac_fn_c_try_compile "$LINENO" then : - ipv6type=$i; - ipv6trylibc=yes + ac_cv_defined___GLIBC___features_h=yes +else $as_nop + ac_cv_defined___GLIBC___features_h=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_defined___GLIBC___features_h" >&5 +printf "%s\n" "$ac_cv_defined___GLIBC___features_h" >&6; } +if test $ac_cv_defined___GLIBC___features_h != "no" +then : + ipv6type=$i + ipv6trylibc=yes fi -rm -rf conftest* - ;; linux-inet6) if test -d /usr/inet6; then @@ -16687,68 +16917,142 @@ rm -rf conftest* fi ;; toshiba) - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for _TOSHIBA_INET6 defined in sys/param.h" >&5 +printf %s "checking for _TOSHIBA_INET6 defined in sys/param.h... " >&6; } +if test ${ac_cv_defined__TOSHIBA_INET6_sys_param_h+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ #include -#ifdef _TOSHIBA_INET6 -yes -#endif +int +main (void) +{ + + #ifdef _TOSHIBA_INET6 + int ok; + (void)ok; + #else + choke me + #endif + + ; + return 0; +} _ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "yes" >/dev/null 2>&1 +if ac_fn_c_try_compile "$LINENO" then : - ipv6type=$i; - ipv6lib=inet6; - ipv6libdir=/usr/local/v6/lib + ac_cv_defined__TOSHIBA_INET6_sys_param_h=yes +else $as_nop + ac_cv_defined__TOSHIBA_INET6_sys_param_h=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_defined__TOSHIBA_INET6_sys_param_h" >&5 +printf "%s\n" "$ac_cv_defined__TOSHIBA_INET6_sys_param_h" >&6; } +if test $ac_cv_defined__TOSHIBA_INET6_sys_param_h != "no" +then : + ipv6type=$i + ipv6lib=inet6 + ipv6libdir=/usr/local/v6/lib fi -rm -rf conftest* - ;; v6d) - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for __V6D__ defined in /usr/local/v6/include/sys/v6config.h" >&5 +printf %s "checking for __V6D__ defined in /usr/local/v6/include/sys/v6config.h... " >&6; } +if test ${ac_cv_defined___V6D____usr_local_v6_include_sys_v6config_h+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ #include -#ifdef __V6D__ -yes -#endif +int +main (void) +{ + + #ifdef __V6D__ + int ok; + (void)ok; + #else + choke me + #endif + + ; + return 0; +} _ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "yes" >/dev/null 2>&1 +if ac_fn_c_try_compile "$LINENO" then : - ipv6type=$i; - ipv6lib=v6; - ipv6libdir=/usr/local/v6/lib; - BASECFLAGS="-I/usr/local/v6/include $BASECFLAGS" + ac_cv_defined___V6D____usr_local_v6_include_sys_v6config_h=yes +else $as_nop + ac_cv_defined___V6D____usr_local_v6_include_sys_v6config_h=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_defined___V6D____usr_local_v6_include_sys_v6config_h" >&5 +printf "%s\n" "$ac_cv_defined___V6D____usr_local_v6_include_sys_v6config_h" >&6; } +if test $ac_cv_defined___V6D____usr_local_v6_include_sys_v6config_h != "no" +then : + ipv6type=$i + ipv6lib=v6 + ipv6libdir=/usr/local/v6/lib + BASECFLAGS="-I/usr/local/v6/include $BASECFLAGS" fi -rm -rf conftest* - ;; zeta) - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for _ZETA_MINAMI_INET6 defined in sys/param.h" >&5 +printf %s "checking for _ZETA_MINAMI_INET6 defined in sys/param.h... " >&6; } +if test ${ac_cv_defined__ZETA_MINAMI_INET6_sys_param_h+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ #include -#ifdef _ZETA_MINAMI_INET6 -yes -#endif +int +main (void) +{ + + #ifdef _ZETA_MINAMI_INET6 + int ok; + (void)ok; + #else + choke me + #endif + + ; + return 0; +} _ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "yes" >/dev/null 2>&1 +if ac_fn_c_try_compile "$LINENO" then : - ipv6type=$i; - ipv6lib=inet6; - ipv6libdir=/usr/local/v6/lib + ac_cv_defined__ZETA_MINAMI_INET6_sys_param_h=yes +else $as_nop + ac_cv_defined__ZETA_MINAMI_INET6_sys_param_h=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_defined__ZETA_MINAMI_INET6_sys_param_h" >&5 +printf "%s\n" "$ac_cv_defined__ZETA_MINAMI_INET6_sys_param_h" >&6; } +if test $ac_cv_defined__ZETA_MINAMI_INET6_sys_param_h != "no" +then : + ipv6type=$i + ipv6lib=inet6 + ipv6libdir=/usr/local/v6/lib fi -rm -rf conftest* - ;; esac if test "$ipv6type" != "unknown"; then break fi done + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking ipv6 stack type" >&5 +printf %s "checking ipv6 stack type... " >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ipv6type" >&5 printf "%s\n" "$ipv6type" >&6; } fi @@ -17289,6 +17593,23 @@ else printf "%s\n" "$MACHDEP_OBJS" >&6; } fi +if test "$ac_sys_system" = "Linux-android"; then + # When these functions are used in an unprivileged process, they crash rather + # than returning an error. + privileged_funcs="chroot initgroups setegid seteuid setgid setregid setresgid + setresuid setreuid setuid" + + # These functions are unimplemented and always return an error. + unimplemented_funcs="sem_open sem_unlink" + + for name in $privileged_funcs $unimplemented_funcs; do + as_func_var=`printf "%s\n" "ac_cv_func_$name" | $as_tr_sh` + + eval "$as_func_var=no" + + done +fi + # checks for library functions ac_fn_c_check_func "$LINENO" "accept4" "ac_cv_func_accept4" if test "x$ac_cv_func_accept4" = xyes @@ -17499,12 +17820,6 @@ if test "x$ac_cv_func_getegid" = xyes then : printf "%s\n" "#define HAVE_GETEGID 1" >>confdefs.h -fi -ac_fn_c_check_func "$LINENO" "getentropy" "ac_cv_func_getentropy" -if test "x$ac_cv_func_getentropy" = xyes -then : - printf "%s\n" "#define HAVE_GETENTROPY 1" >>confdefs.h - fi ac_fn_c_check_func "$LINENO" "geteuid" "ac_cv_func_geteuid" if test "x$ac_cv_func_geteuid" = xyes @@ -17547,12 +17862,6 @@ if test "x$ac_cv_func_getgrouplist" = xyes then : printf "%s\n" "#define HAVE_GETGROUPLIST 1" >>confdefs.h -fi -ac_fn_c_check_func "$LINENO" "getgroups" "ac_cv_func_getgroups" -if test "x$ac_cv_func_getgroups" = xyes -then : - printf "%s\n" "#define HAVE_GETGROUPS 1" >>confdefs.h - fi ac_fn_c_check_func "$LINENO" "gethostname" "ac_cv_func_gethostname" if test "x$ac_cv_func_gethostname" = xyes @@ -17919,6 +18228,12 @@ if test "x$ac_cv_func_preadv2" = xyes then : printf "%s\n" "#define HAVE_PREADV2 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "process_vm_readv" "ac_cv_func_process_vm_readv" +if test "x$ac_cv_func_process_vm_readv" = xyes +then : + printf "%s\n" "#define HAVE_PROCESS_VM_READV 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "pthread_cond_timedwait_relative_np" "ac_cv_func_pthread_cond_timedwait_relative_np" if test "x$ac_cv_func_pthread_cond_timedwait_relative_np" = xyes @@ -18279,12 +18594,6 @@ if test "x$ac_cv_func_sysconf" = xyes then : printf "%s\n" "#define HAVE_SYSCONF 1" >>confdefs.h -fi -ac_fn_c_check_func "$LINENO" "system" "ac_cv_func_system" -if test "x$ac_cv_func_system" = xyes -then : - printf "%s\n" "#define HAVE_SYSTEM 1" >>confdefs.h - fi ac_fn_c_check_func "$LINENO" "tcgetpgrp" "ac_cv_func_tcgetpgrp" if test "x$ac_cv_func_tcgetpgrp" = xyes @@ -18463,6 +18772,32 @@ fi fi +# iOS defines some system methods that can be linked (so they are +# found by configure), but either raise a compilation error (because the +# header definition prevents usage - autoconf doesn't use the headers), or +# raise an error if used at runtime. Force these symbols off. +if test "$ac_sys_system" != "iOS" ; then + ac_fn_c_check_func "$LINENO" "getentropy" "ac_cv_func_getentropy" +if test "x$ac_cv_func_getentropy" = xyes +then : + printf "%s\n" "#define HAVE_GETENTROPY 1" >>confdefs.h + +fi +ac_fn_c_check_func "$LINENO" "getgroups" "ac_cv_func_getgroups" +if test "x$ac_cv_func_getgroups" = xyes +then : + printf "%s\n" "#define HAVE_GETGROUPS 1" >>confdefs.h + +fi +ac_fn_c_check_func "$LINENO" "system" "ac_cv_func_system" +if test "x$ac_cv_func_system" = xyes +then : + printf "%s\n" "#define HAVE_SYSTEM 1" >>confdefs.h + +fi + +fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC options needed to detect all undeclared functions" >&5 printf %s "checking for $CC options needed to detect all undeclared functions... " >&6; } if test ${ac_cv_c_undeclared_builtin_options+y} @@ -21758,6 +22093,11 @@ fi done +# On Android and iOS, clock_settime can be linked (so it is found by +# configure), but when used in an unprivileged process, it crashes rather than +# returning an error. Force the symbol off. +if test "$ac_sys_system" != "Linux-android" && test "$ac_sys_system" != "iOS" +then for ac_func in clock_settime do : @@ -21768,7 +22108,7 @@ then : else $as_nop - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for clock_settime in -lrt" >&5 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for clock_settime in -lrt" >&5 printf %s "checking for clock_settime in -lrt... " >&6; } if test ${ac_cv_lib_rt_clock_settime+y} then : @@ -21806,7 +22146,7 @@ printf "%s\n" "$ac_cv_lib_rt_clock_settime" >&6; } if test "x$ac_cv_lib_rt_clock_settime" = xyes then : - printf "%s\n" "#define HAVE_CLOCK_SETTIME 1" >>confdefs.h + printf "%s\n" "#define HAVE_CLOCK_SETTIME 1" >>confdefs.h fi @@ -21815,6 +22155,7 @@ fi fi done +fi for ac_func in clock_nanosleep @@ -22036,7 +22377,9 @@ else $as_nop if test "$cross_compiling" = yes then : -if test "${enable_ipv6+set}" = set; then +if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS"; then + ac_cv_buggy_getaddrinfo="no" +elif test "${enable_ipv6+set}" = set; then ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6" else ac_cv_buggy_getaddrinfo=yes @@ -23963,16 +24306,23 @@ LDVERSION='$(VERSION)$(ABIFLAGS)' { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LDVERSION" >&5 printf "%s\n" "$LDVERSION" >&6; } -# On Android and Cygwin the shared libraries must be linked with libpython. +# Configure the flags and dependencies used when compiling shared modules MODULE_DEPS_SHARED='$(MODULE_DEPS_STATIC) $(EXPORTSYMS)' MODULE_LDFLAGS='' + +# On Android and Cygwin the shared libraries must be linked with libpython. if test "$PY_ENABLE_SHARED" = "1" && ( test -n "$ANDROID_API_LEVEL" || test "$MACHDEP" = "cygwin"); then MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(LDLIBRARY)" MODULE_LDFLAGS="\$(BLDLIBRARY)" fi +# On iOS the shared libraries must be linked with the Python framework +if test "$ac_sys_system" == "iOS"; then + MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" +fi + BINLIBDEST='$(LIBDIR)/python$(VERSION)' @@ -26702,24 +27052,28 @@ CPPFLAGS=$ac_save_cppflags { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for device files" >&5 printf "%s\n" "$as_me: checking for device files" >&6;} -if test "x$cross_compiling" = xyes; then - if test "${ac_cv_file__dev_ptmx+set}" != set; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 +if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS"; then + ac_cv_file__dev_ptmx=no + ac_cv_file__dev_ptc=no +else + if test "x$cross_compiling" = xyes; then + if test "${ac_cv_file__dev_ptmx+set}" != set; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 printf %s "checking for /dev/ptmx... " >&6; } - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not set" >&5 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not set" >&5 printf "%s\n" "not set" >&6; } - as_fn_error $? "set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 - fi - if test "${ac_cv_file__dev_ptc+set}" != set; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 + as_fn_error $? "set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 + fi + if test "${ac_cv_file__dev_ptc+set}" != set; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 printf %s "checking for /dev/ptc... " >&6; } - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not set" >&5 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not set" >&5 printf "%s\n" "not set" >&6; } - as_fn_error $? "set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 + as_fn_error $? "set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling" "$LINENO" 5 + fi fi -fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptmx" >&5 printf %s "checking for /dev/ptmx... " >&6; } if test ${ac_cv_file__dev_ptmx+y} then : @@ -26740,12 +27094,12 @@ then : fi -if test "x$ac_cv_file__dev_ptmx" = xyes; then + if test "x$ac_cv_file__dev_ptmx" = xyes; then printf "%s\n" "#define HAVE_DEV_PTMX 1" >>confdefs.h -fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 + fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for /dev/ptc" >&5 printf %s "checking for /dev/ptc... " >&6; } if test ${ac_cv_file__dev_ptc+y} then : @@ -26766,10 +27120,11 @@ then : fi -if test "x$ac_cv_file__dev_ptc" = xyes; then + if test "x$ac_cv_file__dev_ptc" = xyes; then printf "%s\n" "#define HAVE_DEV_PTC 1" >>confdefs.h + fi fi if test $ac_sys_system = Darwin @@ -26951,6 +27306,7 @@ SRCDIRS="\ Modules/_sre \ Modules/_testcapi \ Modules/_testinternalcapi \ + Modules/_testlimitedcapi \ Modules/_xxtestfuzz \ Modules/cjkcodecs \ Modules/expat \ @@ -28155,6 +28511,27 @@ case $ac_sys_system in #( ;; #( Darwin) : ;; #( + iOS) : + + + + py_cv_module__curses=n/a + py_cv_module__curses_panel=n/a + py_cv_module__gdbm=n/a + py_cv_module__multiprocessing=n/a + py_cv_module__posixshmem=n/a + py_cv_module__posixsubprocess=n/a + py_cv_module__scproxy=n/a + py_cv_module__tkinter=n/a + py_cv_module_grp=n/a + py_cv_module_nis=n/a + py_cv_module_readline=n/a + py_cv_module_pwd=n/a + py_cv_module_spwd=n/a + py_cv_module_syslog=n/a + py_cv_module_=n/a + + ;; #( CYGWIN*) : @@ -28210,9 +28587,15 @@ case $ac_sys_system in #( py_cv_module__ctypes_test=n/a + py_cv_module__testexternalinspection=n/a + py_cv_module__testimportmultiple=n/a + py_cv_module__testmultiphase=n/a + py_cv_module__testsinglephase=n/a py_cv_module_fcntl=n/a py_cv_module_mmap=n/a py_cv_module_termios=n/a + py_cv_module_xxlimited=n/a + py_cv_module_xxlimited_35=n/a py_cv_module_=n/a @@ -30400,6 +30783,44 @@ fi printf "%s\n" "$py_cv_module__testclinic_limited" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module _testlimitedcapi" >&5 +printf %s "checking for stdlib extension module _testlimitedcapi... " >&6; } + if test "$py_cv_module__testlimitedcapi" != "n/a" +then : + + if test "$TEST_MODULES" = yes +then : + if true +then : + py_cv_module__testlimitedcapi=yes +else $as_nop + py_cv_module__testlimitedcapi=missing +fi +else $as_nop + py_cv_module__testlimitedcapi=disabled +fi + +fi + as_fn_append MODULE_BLOCK "MODULE__TESTLIMITEDCAPI_STATE=$py_cv_module__testlimitedcapi$as_nl" + if test "x$py_cv_module__testlimitedcapi" = xyes +then : + + + + +fi + if test "$py_cv_module__testlimitedcapi" = yes; then + MODULE__TESTLIMITEDCAPI_TRUE= + MODULE__TESTLIMITEDCAPI_FALSE='#' +else + MODULE__TESTLIMITEDCAPI_TRUE='#' + MODULE__TESTLIMITEDCAPI_FALSE= +fi + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $py_cv_module__testlimitedcapi" >&5 +printf "%s\n" "$py_cv_module__testlimitedcapi" >&6; } + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module _testinternalcapi" >&5 printf %s "checking for stdlib extension module _testinternalcapi... " >&6; } if test "$py_cv_module__testinternalcapi" != "n/a" @@ -30552,6 +30973,82 @@ fi printf "%s\n" "$py_cv_module__testmultiphase" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module _testsinglephase" >&5 +printf %s "checking for stdlib extension module _testsinglephase... " >&6; } + if test "$py_cv_module__testsinglephase" != "n/a" +then : + + if test "$TEST_MODULES" = yes +then : + if test "$ac_cv_func_dlopen" = yes +then : + py_cv_module__testsinglephase=yes +else $as_nop + py_cv_module__testsinglephase=missing +fi +else $as_nop + py_cv_module__testsinglephase=disabled +fi + +fi + as_fn_append MODULE_BLOCK "MODULE__TESTSINGLEPHASE_STATE=$py_cv_module__testsinglephase$as_nl" + if test "x$py_cv_module__testsinglephase" = xyes +then : + + + + +fi + if test "$py_cv_module__testsinglephase" = yes; then + MODULE__TESTSINGLEPHASE_TRUE= + MODULE__TESTSINGLEPHASE_FALSE='#' +else + MODULE__TESTSINGLEPHASE_TRUE='#' + MODULE__TESTSINGLEPHASE_FALSE= +fi + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $py_cv_module__testsinglephase" >&5 +printf "%s\n" "$py_cv_module__testsinglephase" >&6; } + + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module _testexternalinspection" >&5 +printf %s "checking for stdlib extension module _testexternalinspection... " >&6; } + if test "$py_cv_module__testexternalinspection" != "n/a" +then : + + if test "$TEST_MODULES" = yes +then : + if true +then : + py_cv_module__testexternalinspection=yes +else $as_nop + py_cv_module__testexternalinspection=missing +fi +else $as_nop + py_cv_module__testexternalinspection=disabled +fi + +fi + as_fn_append MODULE_BLOCK "MODULE__TESTEXTERNALINSPECTION_STATE=$py_cv_module__testexternalinspection$as_nl" + if test "x$py_cv_module__testexternalinspection" = xyes +then : + + + + +fi + if test "$py_cv_module__testexternalinspection" = yes; then + MODULE__TESTEXTERNALINSPECTION_TRUE= + MODULE__TESTEXTERNALINSPECTION_FALSE='#' +else + MODULE__TESTEXTERNALINSPECTION_TRUE='#' + MODULE__TESTEXTERNALINSPECTION_FALSE= +fi + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $py_cv_module__testexternalinspection" >&5 +printf "%s\n" "$py_cv_module__testexternalinspection" >&6; } + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module xxsubtype" >&5 printf %s "checking for stdlib extension module xxsubtype... " >&6; } if test "$py_cv_module_xxsubtype" != "n/a" @@ -31148,6 +31645,10 @@ if test -z "${MODULE__TESTCLINIC_LIMITED_TRUE}" && test -z "${MODULE__TESTCLINIC as_fn_error $? "conditional \"MODULE__TESTCLINIC_LIMITED\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi +if test -z "${MODULE__TESTLIMITEDCAPI_TRUE}" && test -z "${MODULE__TESTLIMITEDCAPI_FALSE}"; then + as_fn_error $? "conditional \"MODULE__TESTLIMITEDCAPI\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi if test -z "${MODULE__TESTINTERNALCAPI_TRUE}" && test -z "${MODULE__TESTINTERNALCAPI_FALSE}"; then as_fn_error $? "conditional \"MODULE__TESTINTERNALCAPI\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 @@ -31164,6 +31665,14 @@ if test -z "${MODULE__TESTMULTIPHASE_TRUE}" && test -z "${MODULE__TESTMULTIPHASE as_fn_error $? "conditional \"MODULE__TESTMULTIPHASE\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi +if test -z "${MODULE__TESTSINGLEPHASE_TRUE}" && test -z "${MODULE__TESTSINGLEPHASE_FALSE}"; then + as_fn_error $? "conditional \"MODULE__TESTSINGLEPHASE\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${MODULE__TESTEXTERNALINSPECTION_TRUE}" && test -z "${MODULE__TESTEXTERNALINSPECTION_FALSE}"; then + as_fn_error $? "conditional \"MODULE__TESTEXTERNALINSPECTION\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi if test -z "${MODULE_XXSUBTYPE_TRUE}" && test -z "${MODULE_XXSUBTYPE_FALSE}"; then as_fn_error $? "conditional \"MODULE_XXSUBTYPE\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 @@ -31766,6 +32275,7 @@ do "Mac/PythonLauncher/Makefile") CONFIG_FILES="$CONFIG_FILES Mac/PythonLauncher/Makefile" ;; "Mac/Resources/framework/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/framework/Info.plist" ;; "Mac/Resources/app/Info.plist") CONFIG_FILES="$CONFIG_FILES Mac/Resources/app/Info.plist" ;; + "iOS/Resources/Info.plist") CONFIG_FILES="$CONFIG_FILES iOS/Resources/Info.plist" ;; "Makefile.pre") CONFIG_FILES="$CONFIG_FILES Makefile.pre" ;; "Misc/python.pc") CONFIG_FILES="$CONFIG_FILES Misc/python.pc" ;; "Misc/python-embed.pc") CONFIG_FILES="$CONFIG_FILES Misc/python-embed.pc" ;; diff --git a/configure.ac b/configure.ac index f6df9b8bb41cc98..02913cef06a5f9d 100644 --- a/configure.ac +++ b/configure.ac @@ -327,6 +327,9 @@ then *-*-cygwin*) ac_sys_system=Cygwin ;; + *-apple-ios*) + ac_sys_system=iOS + ;; *-*-vxworks*) ac_sys_system=VxWorks ;; @@ -359,6 +362,7 @@ then case $MACHDEP in aix*) MACHDEP="aix";; + linux-android*) MACHDEP="android";; linux*) MACHDEP="linux";; cygwin*) MACHDEP="cygwin";; darwin*) MACHDEP="darwin";; @@ -375,6 +379,52 @@ then fi AC_MSG_RESULT(["$MACHDEP"]) +# On cross-compile builds, configure will look for a host-specific compiler by +# prepending the user-provided host triple to the required binary name. +# +# On iOS, this results in binaries like "arm64-apple-ios12.0-simulator-gcc", +# which isn't a binary that exists, and isn't very convenient, as it contains the +# iOS version. As the default cross-compiler name won't exist, configure falls +# back to gcc, which *definitely* won't work. We're providing wrapper scripts for +# these tools; the binary names of these scripts are better defaults than "gcc". +# This only requires that the user put the platform scripts folder (e.g., +# "iOS/Resources/bin") in their path, rather than defining platform-specific +# names/paths for AR, CC, CPP, and CXX explicitly; and if the user forgets to +# either put the platform scripts folder in the path, or specify CC etc, +# configure will fail. +if test -z "$AR"; then + case "$host" in + aarch64-apple-ios*-simulator) AR=arm64-apple-ios-simulator-ar ;; + aarch64-apple-ios*) AR=arm64-apple-ios-ar ;; + x86_64-apple-ios*-simulator) AR=x86_64-apple-ios-simulator-ar ;; + *) + esac +fi +if test -z "$CC"; then + case "$host" in + aarch64-apple-ios*-simulator) CC=arm64-apple-ios-simulator-clang ;; + aarch64-apple-ios*) CC=arm64-apple-ios-clang ;; + x86_64-apple-ios*-simulator) CC=x86_64-apple-ios-simulator-clang ;; + *) + esac +fi +if test -z "$CPP"; then + case "$host" in + aarch64-apple-ios*-simulator) CPP=arm64-apple-ios-simulator-cpp ;; + aarch64-apple-ios*) CPP=arm64-apple-ios-cpp ;; + x86_64-apple-ios*-simulator) CPP=x86_64-apple-ios-simulator-cpp ;; + *) + esac +fi +if test -z "$CXX"; then + case "$host" in + aarch64-apple-ios*-simulator) CXX=arm64-apple-ios-simulator-clang ;; + aarch64-apple-ios*) CXX=arm64-apple-ios-clang ;; + x86_64-apple-ios*-simulator) CXX=x86_64-apple-ios-simulator-clang ;; + *) + esac +fi + AC_MSG_CHECKING([for --enable-universalsdk]) AC_ARG_ENABLE([universalsdk], AS_HELP_STRING([--enable-universalsdk@<:@=SDKDIR@:>@], @@ -484,38 +534,47 @@ AC_ARG_ENABLE([framework], [ case $enableval in yes) - enableval=/Library/Frameworks + case $ac_sys_system in + Darwin) enableval=/Library/Frameworks ;; + iOS) enableval=iOS/Frameworks/\$\(MULTIARCH\) ;; + *) AC_MSG_ERROR([Unknown platform for framework build]) + esac esac + case $enableval in no) - PYTHONFRAMEWORK= - PYTHONFRAMEWORKDIR=no-framework - PYTHONFRAMEWORKPREFIX= - PYTHONFRAMEWORKINSTALLDIR= - PYTHONFRAMEWORKINSTALLNAMEPREFIX= - RESSRCDIR= - FRAMEWORKINSTALLFIRST= - FRAMEWORKINSTALLLAST= - FRAMEWORKALTINSTALLFIRST= - FRAMEWORKALTINSTALLLAST= - FRAMEWORKPYTHONW= - INSTALLTARGETS="commoninstall bininstall maninstall" - - if test "x${prefix}" = "xNONE"; then - FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" - else - FRAMEWORKUNIXTOOLSPREFIX="${prefix}" - fi - enable_framework= + case $ac_sys_system in + iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;; + *) + PYTHONFRAMEWORK= + PYTHONFRAMEWORKDIR=no-framework + PYTHONFRAMEWORKPREFIX= + PYTHONFRAMEWORKINSTALLDIR= + PYTHONFRAMEWORKINSTALLNAMEPREFIX= + RESSRCDIR= + FRAMEWORKINSTALLFIRST= + FRAMEWORKINSTALLLAST= + FRAMEWORKALTINSTALLFIRST= + FRAMEWORKALTINSTALLLAST= + FRAMEWORKPYTHONW= + INSTALLTARGETS="commoninstall bininstall maninstall" + + if test "x${prefix}" = "xNONE"; then + FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" + else + FRAMEWORKUNIXTOOLSPREFIX="${prefix}" + fi + enable_framework= + esac ;; *) PYTHONFRAMEWORKPREFIX="${enableval}" PYTHONFRAMEWORKINSTALLDIR=$PYTHONFRAMEWORKPREFIX/$PYTHONFRAMEWORKDIR - FRAMEWORKINSTALLFIRST="frameworkinstallstructure" - FRAMEWORKALTINSTALLFIRST="frameworkinstallstructure " case $ac_sys_system in #( Darwin) : + FRAMEWORKINSTALLFIRST="frameworkinstallversionedstructure" + FRAMEWORKALTINSTALLFIRST="frameworkinstallversionedstructure " FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools" FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools" FRAMEWORKPYTHONW="frameworkpythonw" @@ -574,31 +633,48 @@ AC_ARG_ENABLE([framework], AC_CONFIG_FILES([Mac/Resources/framework/Info.plist]) AC_CONFIG_FILES([Mac/Resources/app/Info.plist]) ;; + iOS) : + FRAMEWORKINSTALLFIRST="frameworkinstallunversionedstructure" + FRAMEWORKALTINSTALLFIRST="frameworkinstallunversionedstructure " + FRAMEWORKINSTALLLAST="frameworkinstallmobileheaders" + FRAMEWORKALTINSTALLLAST="frameworkinstallmobileheaders" + FRAMEWORKPYTHONW= + INSTALLTARGETS="libinstall inclinstall sharedinstall" + + prefix=$PYTHONFRAMEWORKPREFIX + PYTHONFRAMEWORKINSTALLNAMEPREFIX="@rpath/$PYTHONFRAMEWORKDIR" + RESSRCDIR=iOS/Resources + + AC_CONFIG_FILES([iOS/Resources/Info.plist]) + ;; *) AC_MSG_ERROR([Unknown platform for framework build]) ;; esac esac ],[ - PYTHONFRAMEWORK= - PYTHONFRAMEWORKDIR=no-framework - PYTHONFRAMEWORKPREFIX= - PYTHONFRAMEWORKINSTALLDIR= - PYTHONFRAMEWORKINSTALLNAMEPREFIX= - RESSRCDIR= - FRAMEWORKINSTALLFIRST= - FRAMEWORKINSTALLLAST= - FRAMEWORKALTINSTALLFIRST= - FRAMEWORKALTINSTALLLAST= - FRAMEWORKPYTHONW= - INSTALLTARGETS="commoninstall bininstall maninstall" - if test "x${prefix}" = "xNONE" ; then - FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" - else - FRAMEWORKUNIXTOOLSPREFIX="${prefix}" - fi - enable_framework= - + case $ac_sys_system in + iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;; + *) + PYTHONFRAMEWORK= + PYTHONFRAMEWORKDIR=no-framework + PYTHONFRAMEWORKPREFIX= + PYTHONFRAMEWORKINSTALLDIR= + PYTHONFRAMEWORKINSTALLNAMEPREFIX= + RESSRCDIR= + FRAMEWORKINSTALLFIRST= + FRAMEWORKINSTALLLAST= + FRAMEWORKALTINSTALLFIRST= + FRAMEWORKALTINSTALLLAST= + FRAMEWORKPYTHONW= + INSTALLTARGETS="commoninstall bininstall maninstall" + if test "x${prefix}" = "xNONE" ; then + FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" + else + FRAMEWORKUNIXTOOLSPREFIX="${prefix}" + fi + enable_framework= + esac ]) AC_SUBST([PYTHONFRAMEWORK]) AC_SUBST([PYTHONFRAMEWORKIDENTIFIER]) @@ -634,6 +710,24 @@ if test "$cross_compiling" = yes; then *-*-cygwin*) _host_ident= ;; + *-apple-ios*) + _host_os=`echo $host | cut -d '-' -f3` + _host_device=`echo $host | cut -d '-' -f4` + _host_device=${_host_device:=os} + + dnl IOS_DEPLOYMENT_TARGET is the minimum supported iOS version + IOS_DEPLOYMENT_TARGET=${_host_os:3} + IOS_DEPLOYMENT_TARGET=${IOS_DEPLOYMENT_TARGET:=12.0} + + case "$host_cpu" in + aarch64) + _host_ident=${IOS_DEPLOYMENT_TARGET}-arm64-iphone${_host_device} + ;; + *) + _host_ident=${IOS_DEPLOYMENT_TARGET}-$host_cpu-iphone${_host_device} + ;; + esac + ;; *-*-vxworks*) _host_ident=$host_cpu ;; @@ -711,6 +805,9 @@ case $ac_sys_system/$ac_sys_release in define_xopen_source=no;; Darwin/@<:@[12]@:>@@<:@0-9@:>@.*) define_xopen_source=no;; + # On iOS, defining _POSIX_C_SOURCE also disables platform specific features. + iOS/*) + define_xopen_source=no;; # On QNX 6.3.2, defining _XOPEN_SOURCE prevents netdb.h from # defining NI_NUMERICHOST. QNX/6.3.2) @@ -801,6 +898,15 @@ AS_CASE([$host], ], ) +dnl Add the compiler flag for the iOS minimum supported OS version. +AS_CASE([$ac_sys_system], + [iOS], [ + AS_VAR_APPEND([CFLAGS], [" -mios-version-min=${IOS_DEPLOYMENT_TARGET}"]) + AS_VAR_APPEND([LDFLAGS], [" -mios-version-min=${IOS_DEPLOYMENT_TARGET}"]) + AC_SUBST([IOS_DEPLOYMENT_TARGET]) + ], +) + if test "$ac_sys_system" = "Darwin" then dnl look for SDKROOT @@ -967,6 +1073,7 @@ dnl platforms. AC_MSG_CHECKING([for multiarch]) AS_CASE([$ac_sys_system], [Darwin*], [MULTIARCH=""], + [iOS], [MULTIARCH=""], [FreeBSD*], [MULTIARCH=""], [MULTIARCH=$($CC --print-multiarch 2>/dev/null)] ) @@ -988,6 +1095,7 @@ dnl will have multiple sysconfig modules (one for each CPU architecture), but dnl use a single "fat" binary at runtime. SOABI_PLATFORM is the component of dnl the PLATFORM_TRIPLET that will be used in binary module extensions. AS_CASE([$ac_sys_system], + [iOS], [SOABI_PLATFORM=`echo "$PLATFORM_TRIPLET" | cut -d '-' -f2`], [SOABI_PLATFORM=$PLATFORM_TRIPLET] ) @@ -1019,6 +1127,8 @@ AS_CASE([$host/$ac_cv_cc_name], [powerpc64le-*-linux-gnu/clang], [PY_SUPPORT_TIER=3], dnl Linux on PPC64 little endian, glibc, clang [s390x-*-linux-gnu/gcc], [PY_SUPPORT_TIER=3], dnl Linux on 64bit s390x (big endian), glibc, gcc [x86_64-*-freebsd*/clang], [PY_SUPPORT_TIER=3], dnl FreeBSD on AMD64 + [aarch64-apple-ios*-simulator/clang], [PY_SUPPORT_TIER=3], dnl iOS Simulator on arm64 + [aarch64-apple-ios*/clang], [PY_SUPPORT_TIER=3], dnl iOS on ARM64 [PY_SUPPORT_TIER=0] ) @@ -1337,12 +1447,15 @@ AC_MSG_CHECKING([LDLIBRARY]) # will find it with a -framework option). For this reason there is an # extra variable BLDLIBRARY against which Python and the extension # modules are linked, BLDLIBRARY. This is normally the same as -# LDLIBRARY, but empty for MacOSX framework builds. +# LDLIBRARY, but empty for MacOSX framework builds. iOS does the same, +# but uses a non-versioned framework layout. if test "$enable_framework" then case $ac_sys_system in Darwin) LDLIBRARY='$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)';; + iOS) + LDLIBRARY='$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)';; *) AC_MSG_ERROR([Unknown platform for framework build]);; esac @@ -1377,7 +1490,13 @@ if test $enable_shared = "yes"; then LDLIBRARY='libpython$(LDVERSION).so' BLDLIBRARY='-L. -lpython$(LDVERSION)' RUNSHARED=LD_LIBRARY_PATH=`pwd`${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}} - INSTSONAME="$LDLIBRARY".$SOVERSION + + # The Android Gradle plugin will only package libraries whose names end + # with ".so". + if test "$ac_sys_system" != "Linux-android"; then + INSTSONAME="$LDLIBRARY".$SOVERSION + fi + if test "$with_pydebug" != yes then PY3LIBRARY=libpython3.so @@ -1400,6 +1519,9 @@ if test $enable_shared = "yes"; then BLDLIBRARY='-L. -lpython$(LDVERSION)' RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} ;; + iOS) + LDLIBRARY='libpython$(LDVERSION).dylib' + ;; AIX*) LDLIBRARY='libpython$(LDVERSION).so' RUNSHARED=LIBPATH=`pwd`${LIBPATH:+:${LIBPATH}} @@ -1462,7 +1584,7 @@ then dnl TODO: support other WASI runtimes dnl wasmtime starts the proces with "/" as CWD. For OOT builds add the dnl directory containing _sysconfigdata to PYTHONPATH. - [WASI/*], [HOSTRUNNER='wasmtime run --env PYTHONPATH=/$(shell realpath --relative-to $(abs_srcdir) $(abs_builddir))/$(shell cat pybuilddir.txt):/Lib --mapdir /::$(srcdir) --'], + [WASI/*], [HOSTRUNNER='wasmtime run --wasm max-wasm-stack=8388608 --wasi preview2 --env PYTHONPATH=/$(shell realpath --relative-to $(abs_srcdir) $(abs_builddir))/$(shell cat pybuilddir.txt):/Lib --dir $(srcdir)::/'], [HOSTRUNNER=''] ) fi @@ -2826,15 +2948,11 @@ AC_CHECK_HEADERS( #endif ]) -# checks for typedefs -AC_CACHE_CHECK([for clock_t in time.h], [ac_cv_clock_t_time_h], [ - AC_EGREP_HEADER([clock_t], [time.h], [ac_cv_clock_t_time_h=yes], [ac_cv_clock_t_time_h=no]) -]) -dnl checks for "no" -AS_VAR_IF([ac_cv_clock_t_time_h], [no], [ - AC_DEFINE([clock_t], [long], - [Define to 'long' if doesn't define.]) -]) +# Check for clock_t in time.h. +AC_CHECK_TYPES([clock_t], [], + [AC_DEFINE([clock_t], [long], + [Define to 'long' if does not define clock_t.])], + [@%:@include ]) AC_CACHE_CHECK([for makedev], [ac_cv_func_makedev], [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[ @@ -3167,6 +3285,7 @@ if test -z "$SHLIB_SUFFIX"; then esac ;; CYGWIN*) SHLIB_SUFFIX=.dll;; + iOS) SHLIB_SUFFIX=.dylib;; *) SHLIB_SUFFIX=.so;; esac fi @@ -3247,6 +3366,11 @@ then BLDSHARED="$LDSHARED" fi ;; + iOS/*) + LDSHARED='$(CC) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' + LDCXXSHARED='$(CXX) -dynamiclib -F . -framework $(PYTHONFRAMEWORK)' + BLDSHARED="$LDSHARED" + ;; Emscripten|WASI) LDSHARED='$(CC) -shared' LDCXXSHARED='$(CXX) -shared';; @@ -3366,30 +3490,34 @@ then Linux-android*) LINKFORSHARED="-pie -Xlinker -export-dynamic";; Linux*|GNU*) LINKFORSHARED="-Xlinker -export-dynamic";; # -u libsys_s pulls in all symbols in libsys - Darwin/*) + Darwin/*|iOS/*) LINKFORSHARED="$extra_undefs -framework CoreFoundation" # Issue #18075: the default maximum stack size (8MBytes) is too # small for the default recursion limit. Increase the stack size # to ensure that tests don't crash - stack_size="1000000" # 16 MB - if test "$with_ubsan" = "yes" - then - # Undefined behavior sanitizer requires an even deeper stack - stack_size="4000000" # 64 MB - fi + stack_size="1000000" # 16 MB + if test "$with_ubsan" = "yes" + then + # Undefined behavior sanitizer requires an even deeper stack + stack_size="4000000" # 64 MB + fi - LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" + AC_DEFINE_UNQUOTED([THREAD_STACK_SIZE], + [0x$stack_size], + [Custom thread stack size depending on chosen sanitizer runtimes.]) - AC_DEFINE_UNQUOTED([THREAD_STACK_SIZE], - [0x$stack_size], - [Custom thread stack size depending on chosen sanitizer runtimes.]) + if test $ac_sys_system = "Darwin"; then + LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" - if test "$enable_framework" - then - LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' + if test "$enable_framework"; then + LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/Versions/$(VERSION)/$(PYTHONFRAMEWORK)' + fi + LINKFORSHARED="$LINKFORSHARED" + elif test $ac_sys_system = "iOS"; then + LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' fi - LINKFORSHARED="$LINKFORSHARED";; + ;; OpenUNIX*|UnixWare*) LINKFORSHARED="-Wl,-Bexport";; SCO_SV*) LINKFORSHARED="-Wl,-Bexport";; ReliantUNIX*) LINKFORSHARED="-W1 -Blargedynsym";; @@ -3600,7 +3728,14 @@ int main(void) }]])], [ac_cv_aligned_required=no], [ac_cv_aligned_required=yes], -[ac_cv_aligned_required=yes]) +[ +# "yes" changes the hash function to FNV, which causes problems with Numba +# (https://github.com/numba/numba/blob/0.59.0/numba/cpython/hashing.py#L470). +if test "$ac_sys_system" = "Linux-android"; then + ac_cv_aligned_required=no +else + ac_cv_aligned_required=yes +fi]) ]) if test "$ac_cv_aligned_required" = yes ; then AC_DEFINE([HAVE_ALIGNED_REQUIRED], [1], @@ -3763,6 +3898,9 @@ AS_VAR_IF([have_libffi], [yes], [ dnl when do we need USING_APPLE_OS_LIBFFI? ctypes_malloc_closure=yes ], + [iOS], [ + ctypes_malloc_closure=yes + ], [sunos5], [AS_VAR_APPEND([LIBFFI_LIBS], [" -mimpure-text"])] ) AS_VAR_IF([ctypes_malloc_closure], [yes], [ @@ -4249,13 +4387,9 @@ else # define _POSIX_THREADS in unistd.h. Some apparently don't # (e.g. gnu pth with pthread emulation) AC_MSG_CHECKING([for _POSIX_THREADS in unistd.h]) - AC_EGREP_CPP([yes], - [ -#include -#ifdef _POSIX_THREADS -yes -#endif - ], unistd_defines_pthreads=yes, unistd_defines_pthreads=no) + AX_CHECK_DEFINE([unistd.h], [_POSIX_THREADS], + [unistd_defines_pthreads=yes], + [unistd_defines_pthreads=no]) AC_MSG_RESULT([$unistd_defines_pthreads]) AC_DEFINE([_REENTRANT]) @@ -4429,40 +4563,26 @@ ipv6lib=none ipv6trylibc=no if test "$ipv6" = yes -a "$cross_compiling" = no; then - AC_MSG_CHECKING([ipv6 stack type]) for i in inria kame linux-glibc linux-inet6 solaris toshiba v6d zeta; do case $i in inria) dnl http://www.kame.net/ - AC_EGREP_CPP([yes], [ -#include -#ifdef IPV6_INRIA_VERSION -yes -@%:@endif], - [ipv6type=$i]) + AX_CHECK_DEFINE([netinet/in.h], [IPV6_INRIA_VERSION], [ipv6type=$i]) ;; kame) dnl http://www.kame.net/ - AC_EGREP_CPP([yes], [ -#include -#ifdef __KAME__ -yes -@%:@endif], - [ipv6type=$i; - ipv6lib=inet6 - ipv6libdir=/usr/local/v6/lib - ipv6trylibc=yes]) + AX_CHECK_DEFINE([netinet/in.h], [__KAME__], + [ipv6type=$i + ipv6lib=inet6 + ipv6libdir=/usr/local/v6/lib + ipv6trylibc=yes]) ;; linux-glibc) - dnl http://www.v6.linux.or.jp/ - AC_EGREP_CPP([yes], [ -#include -#if defined(__GLIBC__) && ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 1) || (__GLIBC__ > 2)) -yes -@%:@endif], - [ipv6type=$i; - ipv6trylibc=yes]) + dnl Advanced IPv6 support was added to glibc 2.1 in 1999. + AX_CHECK_DEFINE([features.h], [__GLIBC__], + [ipv6type=$i + ipv6trylibc=yes]) ;; linux-inet6) dnl http://www.v6.linux.or.jp/ @@ -4482,41 +4602,30 @@ yes fi ;; toshiba) - AC_EGREP_CPP([yes], [ -#include -#ifdef _TOSHIBA_INET6 -yes -@%:@endif], - [ipv6type=$i; - ipv6lib=inet6; - ipv6libdir=/usr/local/v6/lib]) + AX_CHECK_DEFINE([sys/param.h], [_TOSHIBA_INET6], + [ipv6type=$i + ipv6lib=inet6 + ipv6libdir=/usr/local/v6/lib]) ;; v6d) - AC_EGREP_CPP([yes], [ -#include -#ifdef __V6D__ -yes -@%:@endif], - [ipv6type=$i; - ipv6lib=v6; - ipv6libdir=/usr/local/v6/lib; - BASECFLAGS="-I/usr/local/v6/include $BASECFLAGS"]) + AX_CHECK_DEFINE([/usr/local/v6/include/sys/v6config.h], [__V6D__], + [ipv6type=$i + ipv6lib=v6 + ipv6libdir=/usr/local/v6/lib + BASECFLAGS="-I/usr/local/v6/include $BASECFLAGS"]) ;; zeta) - AC_EGREP_CPP([yes], [ -#include -#ifdef _ZETA_MINAMI_INET6 -yes -@%:@endif], - [ipv6type=$i; - ipv6lib=inet6; - ipv6libdir=/usr/local/v6/lib]) + AX_CHECK_DEFINE([sys/param.h], [_ZETA_MINAMI_INET6], + [ipv6type=$i + ipv6lib=inet6 + ipv6libdir=/usr/local/v6/lib]) ;; esac if test "$ipv6type" != "unknown"; then break fi done + AC_MSG_CHECKING([ipv6 stack type]) AC_MSG_RESULT([$ipv6type]) fi @@ -4823,14 +4932,30 @@ else AC_MSG_RESULT([$MACHDEP_OBJS]) fi +if test "$ac_sys_system" = "Linux-android"; then + # When these functions are used in an unprivileged process, they crash rather + # than returning an error. + privileged_funcs="chroot initgroups setegid seteuid setgid setregid setresgid + setresuid setreuid setuid" + + # These functions are unimplemented and always return an error. + unimplemented_funcs="sem_open sem_unlink" + + for name in $privileged_funcs $unimplemented_funcs; do + AS_VAR_PUSHDEF([func_var], [ac_cv_func_$name]) + AS_VAR_SET([func_var], [no]) + AS_VAR_POPDEF([func_var]) + done +fi + # checks for library functions AC_CHECK_FUNCS([ \ accept4 alarm bind_textdomain_codeset chmod chown clock closefrom close_range confstr \ copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \ faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ - gai_strerror getegid getentropy geteuid getgid getgrent getgrgid getgrgid_r \ - getgrnam_r getgrouplist getgroups gethostname getitimer getloadavg getlogin \ + gai_strerror getegid geteuid getgid getgrent getgrgid getgrgid_r \ + getgrnam_r getgrouplist gethostname getitimer getloadavg getlogin \ getpeername getpgid getpid getppid getpriority _getpty \ getpwent getpwnam_r getpwuid getpwuid_r getresgid getresuid getrusage getsid getspent \ getspnam getuid getwd grantpt if_nameindex initgroups kill killpg lchown linkat \ @@ -4838,7 +4963,7 @@ AC_CHECK_FUNCS([ \ mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \ pipe2 plock poll posix_fadvise posix_fallocate posix_openpt posix_spawn posix_spawnp \ posix_spawn_file_actions_addclosefrom_np \ - pread preadv preadv2 pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \ + pread preadv preadv2 process_vm_readv pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \ pthread_kill ptsname ptsname_r pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \ rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \ sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \ @@ -4847,7 +4972,7 @@ AC_CHECK_FUNCS([ \ setresuid setreuid setsid setuid setvbuf shutdown sigaction sigaltstack \ sigfillset siginterrupt sigpending sigrelse sigtimedwait sigwait \ sigwaitinfo snprintf splice strftime strlcpy strsignal symlinkat sync \ - sysconf system tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ + sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile \ tmpnam tmpnam_r truncate ttyname umask uname unlinkat unlockpt utimensat utimes vfork \ wait wait3 wait4 waitid waitpid wcscoll wcsftime wcsxfrm wmemcmp writev \ ]) @@ -4859,6 +4984,14 @@ if test "$MACHDEP" != linux; then AC_CHECK_FUNCS([lchmod]) fi +# iOS defines some system methods that can be linked (so they are +# found by configure), but either raise a compilation error (because the +# header definition prevents usage - autoconf doesn't use the headers), or +# raise an error if used at runtime. Force these symbols off. +if test "$ac_sys_system" != "iOS" ; then + AC_CHECK_FUNCS([getentropy getgroups system]) +fi + AC_CHECK_DECL([dirfd], [AC_DEFINE([HAVE_DIRFD], [1], [Define if you have the 'dirfd' function or macro.])], @@ -5159,11 +5292,17 @@ AC_CHECK_FUNCS([clock_getres], [], [ ]) ]) -AC_CHECK_FUNCS([clock_settime], [], [ - AC_CHECK_LIB([rt], [clock_settime], [ - AC_DEFINE([HAVE_CLOCK_SETTIME], [1]) - ]) -]) +# On Android and iOS, clock_settime can be linked (so it is found by +# configure), but when used in an unprivileged process, it crashes rather than +# returning an error. Force the symbol off. +if test "$ac_sys_system" != "Linux-android" && test "$ac_sys_system" != "iOS" +then + AC_CHECK_FUNCS([clock_settime], [], [ + AC_CHECK_LIB([rt], [clock_settime], [ + AC_DEFINE([HAVE_CLOCK_SETTIME], [1]) + ]) + ]) +fi AC_CHECK_FUNCS([clock_nanosleep], [], [ AC_CHECK_LIB([rt], [clock_nanosleep], [ @@ -5309,7 +5448,9 @@ int main(void) [ac_cv_buggy_getaddrinfo=no], [ac_cv_buggy_getaddrinfo=yes], [ -if test "${enable_ipv6+set}" = set; then +if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS"; then + ac_cv_buggy_getaddrinfo="no" +elif test "${enable_ipv6+set}" = set; then ac_cv_buggy_getaddrinfo="no -- configured with --(en|dis)able-ipv6" else ac_cv_buggy_getaddrinfo=yes @@ -5885,16 +6026,23 @@ AC_MSG_CHECKING([LDVERSION]) LDVERSION='$(VERSION)$(ABIFLAGS)' AC_MSG_RESULT([$LDVERSION]) -# On Android and Cygwin the shared libraries must be linked with libpython. +# Configure the flags and dependencies used when compiling shared modules AC_SUBST([MODULE_DEPS_SHARED]) AC_SUBST([MODULE_LDFLAGS]) MODULE_DEPS_SHARED='$(MODULE_DEPS_STATIC) $(EXPORTSYMS)' MODULE_LDFLAGS='' + +# On Android and Cygwin the shared libraries must be linked with libpython. if test "$PY_ENABLE_SHARED" = "1" && ( test -n "$ANDROID_API_LEVEL" || test "$MACHDEP" = "cygwin"); then MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(LDLIBRARY)" MODULE_LDFLAGS="\$(BLDLIBRARY)" fi +# On iOS the shared libraries must be linked with the Python framework +if test "$ac_sys_system" == "iOS"; then + MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" +fi + AC_SUBST([BINLIBDEST]) BINLIBDEST='$(LIBDIR)/python$(VERSION)' @@ -6520,28 +6668,35 @@ CPPFLAGS=$ac_save_cppflags AC_MSG_NOTICE([checking for device files]) dnl NOTE: Inform user how to proceed with files when cross compiling. -if test "x$cross_compiling" = xyes; then - if test "${ac_cv_file__dev_ptmx+set}" != set; then - AC_MSG_CHECKING([for /dev/ptmx]) - AC_MSG_RESULT([not set]) - AC_MSG_ERROR([set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling]) - fi - if test "${ac_cv_file__dev_ptc+set}" != set; then - AC_MSG_CHECKING([for /dev/ptc]) - AC_MSG_RESULT([not set]) - AC_MSG_ERROR([set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling]) +dnl Some cross-compile builds are predictable; they won't ever +dnl have /dev/ptmx or /dev/ptc, so we can set them explicitly. +if test "$ac_sys_system" = "Linux-android" || test "$ac_sys_system" = "iOS"; then + ac_cv_file__dev_ptmx=no + ac_cv_file__dev_ptc=no +else + if test "x$cross_compiling" = xyes; then + if test "${ac_cv_file__dev_ptmx+set}" != set; then + AC_MSG_CHECKING([for /dev/ptmx]) + AC_MSG_RESULT([not set]) + AC_MSG_ERROR([set ac_cv_file__dev_ptmx to yes/no in your CONFIG_SITE file when cross compiling]) + fi + if test "${ac_cv_file__dev_ptc+set}" != set; then + AC_MSG_CHECKING([for /dev/ptc]) + AC_MSG_RESULT([not set]) + AC_MSG_ERROR([set ac_cv_file__dev_ptc to yes/no in your CONFIG_SITE file when cross compiling]) + fi fi -fi -AC_CHECK_FILE([/dev/ptmx], [], []) -if test "x$ac_cv_file__dev_ptmx" = xyes; then - AC_DEFINE([HAVE_DEV_PTMX], [1], - [Define to 1 if you have the /dev/ptmx device file.]) -fi -AC_CHECK_FILE([/dev/ptc], [], []) -if test "x$ac_cv_file__dev_ptc" = xyes; then - AC_DEFINE([HAVE_DEV_PTC], [1], - [Define to 1 if you have the /dev/ptc device file.]) + AC_CHECK_FILE([/dev/ptmx], [], []) + if test "x$ac_cv_file__dev_ptmx" = xyes; then + AC_DEFINE([HAVE_DEV_PTMX], [1], + [Define to 1 if you have the /dev/ptmx device file.]) + fi + AC_CHECK_FILE([/dev/ptc], [], []) + if test "x$ac_cv_file__dev_ptc" = xyes; then + AC_DEFINE([HAVE_DEV_PTC], [1], + [Define to 1 if you have the /dev/ptc device file.]) + fi fi if test $ac_sys_system = Darwin @@ -6659,6 +6814,7 @@ SRCDIRS="\ Modules/_sre \ Modules/_testcapi \ Modules/_testinternalcapi \ + Modules/_testlimitedcapi \ Modules/_xxtestfuzz \ Modules/cjkcodecs \ Modules/expat \ @@ -7181,6 +7337,28 @@ AS_CASE([$ac_sys_system], [VxWorks*], [PY_STDLIB_MOD_SET_NA([_scproxy], [termios], [grp])], dnl The _scproxy module is available on macOS [Darwin], [], + [iOS], [ + dnl subprocess and multiprocessing are not supported (no fork syscall). + dnl curses and tkinter user interface are not available. + dnl gdbm and nis aren't available + dnl Stub implementations are provided for pwd, grp etc APIs + PY_STDLIB_MOD_SET_NA( + [_curses], + [_curses_panel], + [_gdbm], + [_multiprocessing], + [_posixshmem], + [_posixsubprocess], + [_scproxy], + [_tkinter], + [grp], + [nis], + [readline], + [pwd], + [spwd], + [syslog], + ) + ], [CYGWIN*], [PY_STDLIB_MOD_SET_NA([_scproxy])], [QNX*], [PY_STDLIB_MOD_SET_NA([_scproxy])], [FreeBSD*], [PY_STDLIB_MOD_SET_NA([_scproxy])], @@ -7220,11 +7398,19 @@ AS_CASE([$ac_sys_system], [Emscripten/node*], [], [WASI/*], [ dnl WASI SDK 15.0 does not support file locking, mmap, and more. + dnl Test modules that must be compiled as shared libraries are not supported + dnl (see Modules/Setup.stdlib.in). PY_STDLIB_MOD_SET_NA( [_ctypes_test], + [_testexternalinspection], + [_testimportmultiple], + [_testmultiphase], + [_testsinglephase], [fcntl], [mmap], [termios], + [xxlimited], + [xxlimited_35], ) ] ) @@ -7446,10 +7632,13 @@ PY_STDLIB_MOD([_testcapi], [], [], [$LIBATOMIC]) PY_STDLIB_MOD([_testclinic], [test "$TEST_MODULES" = yes]) PY_STDLIB_MOD([_testclinic_limited], [test "$TEST_MODULES" = yes]) +PY_STDLIB_MOD([_testlimitedcapi], [test "$TEST_MODULES" = yes]) PY_STDLIB_MOD([_testinternalcapi], [test "$TEST_MODULES" = yes]) PY_STDLIB_MOD([_testbuffer], [test "$TEST_MODULES" = yes]) PY_STDLIB_MOD([_testimportmultiple], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes]) PY_STDLIB_MOD([_testmultiphase], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes]) +PY_STDLIB_MOD([_testsinglephase], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes]) +PY_STDLIB_MOD([_testexternalinspection], [test "$TEST_MODULES" = yes]) PY_STDLIB_MOD([xxsubtype], [test "$TEST_MODULES" = yes]) PY_STDLIB_MOD([_xxtestfuzz], [test "$TEST_MODULES" = yes]) PY_STDLIB_MOD([_ctypes_test], diff --git a/iOS/README.rst b/iOS/README.rst new file mode 100644 index 000000000000000..b67199e66f95b33 --- /dev/null +++ b/iOS/README.rst @@ -0,0 +1,344 @@ +==================== +Python on iOS README +==================== + +:Authors: + Russell Keith-Magee (2023-11) + +This document provides a quick overview of some iOS specific features in the +Python distribution. + +These instructions are only needed if you're planning to compile Python for iOS +yourself. Most users should *not* need to do this. If you're looking to +experiment with writing an iOS app in Python, tools such as `BeeWare's Briefcase +`__ and `Kivy's Buildozer +`__ will provide a much more approachable +user experience. + +Compilers for building on iOS +============================= + +Building for iOS requires the use of Apple's Xcode tooling. It is strongly +recommended that you use the most recent stable release of Xcode. This will +require the use of the most (or second-most) recently released macOS version, +as Apple does not maintain Xcode for older macOS versions. The Xcode Command +Line Tools are not sufficient for iOS development; you need a *full* Xcode +install. + +If you want to run your code on the iOS simulator, you'll also need to install +an iOS Simulator Platform. You should be prompted to select an iOS Simulator +Platform when you first run Xcode. Alternatively, you can add an iOS Simulator +Platform by selecting an open the Platforms tab of the Xcode Settings panel. + +iOS specific arguments to configure +=================================== + +* ``--enable-framework[=DIR]`` + + This argument specifies the location where the Python.framework will be + installed. If ``DIR`` is not specified, the framework will be installed into + a subdirectory of the ``iOS/Frameworks`` folder. + + This argument *must* be provided when configuring iOS builds. iOS does not + support non-framework builds. + +* ``--with-framework-name=NAME`` + + Specify the name for the Python framework; defaults to ``Python``. + + .. admonition:: Use this option with care! + + Unless you know what you're doing, changing the name of the Python + framework on iOS is not advised. If you use this option, you won't be able + to run the ``make testios`` target without making signficant manual + alterations, and you won't be able to use any binary packages unless you + compile them yourself using your own framework name. + +Building Python on iOS +====================== + +ABIs and Architectures +---------------------- + +iOS apps can be deployed on physical devices, and on the iOS simulator. Although +the API used on these devices is identical, the ABI is different - you need to +link against different libraries for an iOS device build (``iphoneos``) or an +iOS simulator build (``iphonesimulator``). + +Apple uses the ``XCframework`` format to allow specifying a single dependency +that supports multiple ABIs. An ``XCframework`` is a wrapper around multiple +ABI-specific frameworks that share a common API. + +iOS can also support different CPU architectures within each ABI. At present, +there is only a single supported architecture on physical devices - ARM64. +However, the *simulator* supports 2 architectures - ARM64 (for running on Apple +Silicon machines), and x86_64 (for running on older Intel-based machines). + +To support multiple CPU architectures on a single platform, Apple uses a "fat +binary" format - a single physical file that contains support for multiple +architectures. It is possible to compile and use a "thin" single architecture +version of a binary for testing purposes; however, the "thin" binary will not be +portable to machines using other architectures. + +Building a single-architecture framework +---------------------------------------- + +The Python build system will create a ``Python.framework`` that supports a +*single* ABI with a *single* architecture. Unlike macOS, iOS does not allow a +framework to contain non-library content, so the iOS build will produce a +``bin`` and ``lib`` folder in the same output folder as ``Python.framework``. +The ``lib`` folder will be needed at runtime to support the Python library. + +If you want to use Python in a real iOS project, you need to produce multiple +``Python.framework`` builds, one for each ABI and architecture. iOS builds of +Python *must* be constructed as framework builds. To support this, you must +provide the ``--enable-framework`` flag when configuring the build. The build +also requires the use of cross-compilation. The minimal commands for building +Python for the ARM64 iOS simulator will look something like:: + + $ export PATH="$(pwd)/iOS/Resources/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" + $ ./configure \ + --enable-framework \ + --host=arm64-apple-ios-simulator \ + --build=arm64-apple-darwin \ + --with-build-python=/path/to/python.exe + $ make + $ make install + +In this invocation: + +* ``iOS/Resources/bin`` has been added to the path, providing some shims for the + compilers and linkers needed by the build. Xcode requires the use of ``xcrun`` + to invoke compiler tooling. However, if ``xcrun`` is pre-evaluated and the + result passed to ``configure``, these results can embed user- and + version-specific paths into the sysconfig data, which limits the portability + of the compiled Python. Alternatively, if ``xcrun`` is used *as* the compiler, + it requires that compiler variables like ``CC`` include spaces, which can + cause significant problems with many C configuration systems which assume that + ``CC`` will be a single executable. + + To work around this problem, the ``iOS/Resources/bin`` folder contains some + wrapper scripts that present as simple compilers and linkers, but wrap + underlying calls to ``xcrun``. This allows configure to use a ``CC`` + definition without spaces, and without user- or version-specific paths, while + retaining the ability to adapt to the local Xcode install. These scripts are + included in the ``bin`` directory of an iOS install. + + These scripts will, by default, use the currently active Xcode installation. + If you want to use a different Xcode installation, you can use + ``xcode-select`` to set a new default Xcode globally, or you can use the + ``DEVELOPER_DIR`` environment variable to specify an Xcode install. The + scripts will use the default ``iphoneos``/``iphonesimulator`` SDK version for + the select Xcode install; if you want to use a different SDK, you can set the + ``IOS_SDK_VERSION`` environment variable. (e.g, setting + ``IOS_SDK_VERSION=17.1`` would cause the scripts to use the ``iphoneos17.1`` + and ``iphonesimulator17.1`` SDKs, regardless of the Xcode default.) + + The path has also been cleared of any user customizations. A common source of + bugs is for tools like Homebrew to accidentally leak macOS binaries into an iOS + build. Resetting the path to a known "bare bones" value is the easiest way to + avoid these problems. + +* ``--host`` is the architecture and ABI that you want to build, in GNU compiler + triple format. This will be one of: + + - ``arm64-apple-ios`` for ARM64 iOS devices. + - ``arm64-apple-ios-simulator`` for the iOS simulator running on Apple + Silicon devices. + - ``x86_64-apple-ios-simulator`` for the iOS simulator running on Intel + devices. + +* ``--build`` is the GNU compiler triple for the machine that will be running + the compiler. This is one of: + + - ``arm64-apple-darwin`` for Apple Silicon devices. + - ``x86_64-apple-darwin`` for Intel devices. + +* ``/path/to/python.exe`` is the path to a Python binary on the machine that + will be running the compiler. This is needed because the Python compilation + process involves running some Python code. On a normal desktop build of + Python, you can compile a python interpreter and then use that interpreter to + run Python code. However, the binaries produced for iOS won't run on macOS, so + you need to provide an external Python interpreter. This interpreter must be + the same version as the Python that is being compiled. To be completely safe, + this should be the *exact* same commit hash. However, the longer a Python + release has been stable, the more likely it is that this constraint can be + relaxed - the same micro version will often be sufficient. + +* The ``install`` target for iOS builds is slightly different to other + platforms. On most platforms, ``make install`` will install the build into + the final runtime location. This won't be the case for iOS, as the final + runtime location will be on a physical device. + + However, you still need to run the ``install`` target for iOS builds, as it + performs some final framework assembly steps. The location specified with + ``--enable-framework`` will be the location where ``make install`` will + assemble the complete iOS framework. This completed framework can then + be copied and relocated as required. + +For a full CPython build, you also need to specify the paths to iOS builds of +the binary libraries that CPython depends on (XZ, BZip2, LibFFI and OpenSSL). +This can be done by defining the ``LIBLZMA_CFLAGS``, ``LIBLZMA_LIBS``, +``BZIP2_CFLAGS``, ``BZIP2_LIBS``, ``LIBFFI_CFLAGS``, and ``LIBFFI_LIBS`` +environment variables, and the ``--with-openssl`` configure option. Versions of +these libraries pre-compiled for iOS can be found in `this repository +`__. + +By default, Python will be compiled with an iOS deployment target (i.e., the +minimum supported iOS version) of 12.0. To specify a different deployment +target, provide the version number as part of the ``--host`` argument - for +example, ``--host=arm64-apple-ios15.4-simulator`` would compile an ARM64 +simulator build with a deployment target of 15.4. + +Merge thin frameworks into fat frameworks +----------------------------------------- + +Once you've built a ``Python.framework`` for each ABI and and architecture, you +must produce a "fat" framework for each ABI that contains all the architectures +for that ABI. + +The ``iphoneos`` build only needs to support a single architecture, so it can be +used without modification. + +If you only want to support a single simulator architecture, (e.g., only support +ARM64 simulators), you can use a single architecture ``Python.framework`` build. +However, if you want to create ``Python.xcframework`` that supports *all* +architectures, you'll need to merge the ``iphonesimulator`` builds for ARM64 and +x86_64 into a single "fat" framework. + +The "fat" framework can be constructed by performing a directory merge of the +content of the two "thin" ``Python.framework`` directories, plus the ``bin`` and +``lib`` folders for each thin framework. When performing this merge: + +* The pure Python standard library content is identical for each architecture, + except for a handful of platform-specific files (such as the ``sysconfig`` + module). Ensure that the "fat" framework has the union of all standard library + files. + +* Any binary files in the standard library, plus the main + ``libPython3.X.dylib``, can be merged using the ``lipo`` tool, provide by + Xcode:: + + $ lipo -create -output module.dylib path/to/x86_64/module.dylib path/to/arm64/module.dylib + +* The header files will be indentical on both architectures, except for + ``pyconfig.h``. Copy all the headers from one platform (say, arm64), rename + ``pyconfig.h`` to ``pyconfig-arm64.h``, and copy the ``pyconfig.h`` for the + other architecture into the merged header folder as ``pyconfig-x86_64.h``. + Then copy the ``iOS/Resources/pyconfig.h`` file from the CPython sources into + the merged headers folder. This will allow the two Python architectures to + share a common ``pyconfig.h`` header file. + +At this point, you should have 2 Python.framework folders - one for ``iphoneos``, +and one for ``iphonesimulator`` that is a merge of x86+64 and ARM64 content. + +Merge frameworks into an XCframework +------------------------------------ + +Now that we have 2 (potentially fat) ABI-specific frameworks, we can merge those +frameworks into a single ``XCframework``. + +The initial skeleton of an ``XCframework`` is built using:: + + xcodebuild -create-xcframework -output Python.xcframework -framework path/to/iphoneos/Python.framework -framework path/to/iphonesimulator/Python.framework + +Then, copy the ``bin`` and ``lib`` folders into the architecture-specific slices of +the XCframework:: + + cp path/to/iphoneos/bin Python.xcframework/ios-arm64 + cp path/to/iphoneos/lib Python.xcframework/ios-arm64 + + cp path/to/iphonesimulator/bin Python.xcframework/ios-arm64_x86-64-simulator + cp path/to/iphonesimulator/lib Python.xcframework/ios-arm64_x86-64-simulator + +Note that the name of the architecture-specific slice for the simulator will +depend on the CPU architecture that you build. + +Then, add symbolic links to "common" platform names for each slice:: + + ln -si ios-arm64 Python.xcframework/iphoneos + ln -si ios-arm64_x86-64-simulator Python.xcframework/iphonesimulator + +You now have a Python.xcframework that can be used in a project. + +Testing Python on iOS +===================== + +The ``iOS/testbed`` folder that contains an Xcode project that is able to run +the iOS test suite. This project converts the Python test suite into a single +test case in Xcode's XCTest framework. The single XCTest passes if the test +suite passes. + +To run the test suite, configure a Python build for an iOS simulator (i.e., +``--host=arm64-apple-ios-simulator`` or ``--host=x86_64-apple-ios-simulator`` +), specifying a framework build (i.e. ``--enable-framework``). Ensure that your +``PATH`` has been configured to include the ``iOS/Resources/bin`` folder and +exclude any non-iOS tools, then run:: + + $ make all + $ make install + $ make testios + +This will: + +* Build an iOS framework for your chosen architecture; +* Finalize the single-platform framework; +* Make a clean copy of the testbed project; +* Install the Python iOS framework into the copy of the testbed project; and +* Run the test suite on an "iPhone SE (3rd generation)" simulator. + +While the test suite is running, Xcode does not display any console output. +After showing some Xcode build commands, the console output will print ``Testing +started``, and then appear to stop. It will remain in this state until the test +suite completes. On a 2022 M1 MacBook Pro, the test suite takes approximately 12 +minutes to run; a couple of extra minutes is required to boot and prepare the +iOS simulator. + +On success, the test suite will exit and report successful completion of the +test suite. No output of the Python test suite will be displayed. + +On failure, the output of the Python test suite *will* be displayed. This will +show the details of the tests that failed. + +Debugging test failures +----------------------- + +The easiest way to diagnose a single test failure is to open the testbed project +in Xcode and run the tests from there using the "Product > Test" menu item. + +Running specific tests +^^^^^^^^^^^^^^^^^^^^^^ + +As the test suite is being executed on an iOS simulator, it is not possible to +pass in command line arguments to configure test suite operation. To work around +this limitation, the arguments that would normally be passed as command line +arguments are configured as a static string at the start of the XCTest method +``- (void)testPython`` in ``iOSTestbedTests.m``. To pass an argument to the test +suite, add a a string to the ``argv`` defintion. These arguments will be passed +to the test suite as if they had been passed to ``python -m test`` at the +command line. + +Disabling automated breakpoints +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +By default, Xcode will inserts an automatic breakpoint whenever a signal is +raised. The Python test suite raises many of these signals as part of normal +operation; unless you are trying to diagnose an issue with signals, the +automatic breakpoints can be inconvenient. However, they can be disabled by +creating a symbolic breakpoint that is triggered at the start of the test run. + +Select "Debug > Breakpoints > Create Symbolic Breakpoint" from the Xcode menu, and +populate the new brewpoint with the following details: + +* **Name**: IgnoreSignals +* **Symbol**: UIApplicationMain +* **Action**: Add debugger commands for: + - ``process handle SIGINT -n true -p true -s false`` + - ``process handle SIGUSR1 -n true -p true -s false`` + - ``process handle SIGUSR2 -n true -p true -s false`` + - ``process handle SIGXFSZ -n true -p true -s false`` +* Check the "Automatically continue after evaluating" box. + +All other details can be left blank. When the process executes the +``UIApplicationMain`` entry point, the breakpoint will trigger, run the debugger +commands to disable the automatic breakpoints, and automatically resume. diff --git a/iOS/Resources/Info.plist.in b/iOS/Resources/Info.plist.in new file mode 100644 index 000000000000000..3ecdc894f0a285e --- /dev/null +++ b/iOS/Resources/Info.plist.in @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + Python + CFBundleGetInfoString + Python Runtime and Library + CFBundleIdentifier + @PYTHONFRAMEWORKIDENTIFIER@ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Python + CFBundlePackageType + FMWK + CFBundleShortVersionString + %VERSION% + CFBundleLongVersionString + %VERSION%, (c) 2001-2024 Python Software Foundation. + CFBundleSignature + ???? + CFBundleVersion + 1 + CFBundleSupportedPlatforms + + iPhoneOS + + MinimumOSVersion + @IOS_DEPLOYMENT_TARGET@ + + diff --git a/iOS/Resources/bin/arm64-apple-ios-ar b/iOS/Resources/bin/arm64-apple-ios-ar new file mode 100755 index 000000000000000..8122332b9c1de04 --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-ar @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} ar $@ diff --git a/iOS/Resources/bin/arm64-apple-ios-clang b/iOS/Resources/bin/arm64-apple-ios-clang new file mode 100755 index 000000000000000..4d525751eba798c --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-clang @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios $@ diff --git a/iOS/Resources/bin/arm64-apple-ios-cpp b/iOS/Resources/bin/arm64-apple-ios-cpp new file mode 100755 index 000000000000000..891bb25bb4318c0 --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-cpp @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios -E $@ diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-ar b/iOS/Resources/bin/arm64-apple-ios-simulator-ar new file mode 100755 index 000000000000000..74ed3bc6df1c2bf --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-simulator-ar @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar $@ diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-clang b/iOS/Resources/bin/arm64-apple-ios-simulator-clang new file mode 100755 index 000000000000000..32574cad2844415 --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios-simulator $@ diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-cpp b/iOS/Resources/bin/arm64-apple-ios-simulator-cpp new file mode 100755 index 000000000000000..6aaf6fbe188c326 --- /dev/null +++ b/iOS/Resources/bin/arm64-apple-ios-simulator-cpp @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios-simulator -E $@ diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-ar b/iOS/Resources/bin/x86_64-apple-ios-simulator-ar new file mode 100755 index 000000000000000..74ed3bc6df1c2bf --- /dev/null +++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-ar @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar $@ diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-clang b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang new file mode 100755 index 000000000000000..bcbe91f6061e160 --- /dev/null +++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios-simulator $@ diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp b/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp new file mode 100755 index 000000000000000..e6a42d9b85dec7d --- /dev/null +++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp @@ -0,0 +1,2 @@ +#!/bin/sh +xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios-simulator -E $@ diff --git a/iOS/Resources/dylib-Info-template.plist b/iOS/Resources/dylib-Info-template.plist new file mode 100644 index 000000000000000..f652e272f71c88a --- /dev/null +++ b/iOS/Resources/dylib-Info-template.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + + CFBundleIdentifier + + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSupportedPlatforms + + iPhoneOS + + MinimumOSVersion + 12.0 + CFBundleVersion + 1 + + diff --git a/iOS/Resources/pyconfig.h b/iOS/Resources/pyconfig.h new file mode 100644 index 000000000000000..4acff2c60516377 --- /dev/null +++ b/iOS/Resources/pyconfig.h @@ -0,0 +1,7 @@ +#ifdef __arm64__ +#include "pyconfig-arm64.h" +#endif + +#ifdef __x86_64__ +#include "pyconfig-x86_64.h" +#endif diff --git a/iOS/testbed/Python.xcframework/Info.plist b/iOS/testbed/Python.xcframework/Info.plist new file mode 100644 index 000000000000000..c6418de6e74a4e9 --- /dev/null +++ b/iOS/testbed/Python.xcframework/Info.plist @@ -0,0 +1,44 @@ + + + + + AvailableLibraries + + + BinaryPath + Python.framework/Python + LibraryIdentifier + ios-arm64 + LibraryPath + Python.framework + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + BinaryPath + Python.framework/Python + LibraryIdentifier + ios-arm64_x86_64-simulator + LibraryPath + Python.framework + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/iOS/testbed/Python.xcframework/ios-arm64/README b/iOS/testbed/Python.xcframework/ios-arm64/README new file mode 100644 index 000000000000000..c1b076d12cddb7d --- /dev/null +++ b/iOS/testbed/Python.xcframework/ios-arm64/README @@ -0,0 +1,4 @@ +This directory is intentionally empty. + +It should be used as a target for `--enable-framework` when compiling an iOS on-device +build for testing purposes. diff --git a/iOS/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README b/iOS/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README new file mode 100644 index 000000000000000..ae334e5d769d9de --- /dev/null +++ b/iOS/testbed/Python.xcframework/ios-arm64_x86_64-simulator/README @@ -0,0 +1,4 @@ +This directory is intentionally empty. + +It should be used as a target for `--enable-framework` when compiling an iOS simulator +build for testing purposes (either x86_64 or ARM64). diff --git a/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj b/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj new file mode 100644 index 000000000000000..4f138a4e7ccefdf --- /dev/null +++ b/iOS/testbed/iOSTestbed.xcodeproj/project.pbxproj @@ -0,0 +1,570 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66162B0EFA380010BFC8 /* AppDelegate.m */; }; + 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607A66212B0EFA390010BFC8 /* Assets.xcassets */; }; + 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */; }; + 607A66282B0EFA390010BFC8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66272B0EFA390010BFC8 /* main.m */; }; + 607A66322B0EFA3A0010BFC8 /* iOSTestbedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.m */; }; + 607A664C2B0EFC080010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; }; + 607A664D2B0EFC080010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; }; + 607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */ = {isa = PBXBuildFile; fileRef = 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 607A660A2B0EFA380010BFC8 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 607A66112B0EFA380010BFC8; + remoteInfo = iOSTestbed; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 607A664E2B0EFC080010BFC8 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 607A664D2B0EFC080010BFC8 /* Python.xcframework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 607A66522B0EFFE00010BFC8 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 607A66122B0EFA380010BFC8 /* iOSTestbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iOSTestbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 607A66152B0EFA380010BFC8 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 607A66162B0EFA380010BFC8 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 607A66212B0EFA390010BFC8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 607A66242B0EFA390010BFC8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 607A66272B0EFA390010BFC8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSTestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iOSTestbedTests.m; sourceTree = ""; }; + 607A664A2B0EFB310010BFC8 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = ""; }; + 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "dylib-Info-template.plist"; sourceTree = ""; }; + 607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "iOSTestbed-Info.plist"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 607A660F2B0EFA380010BFC8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 607A664C2B0EFC080010BFC8 /* Python.xcframework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 607A662A2B0EFA3A0010BFC8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 607A66092B0EFA380010BFC8 = { + isa = PBXGroup; + children = ( + 607A664A2B0EFB310010BFC8 /* Python.xcframework */, + 607A66142B0EFA380010BFC8 /* iOSTestbed */, + 607A66302B0EFA3A0010BFC8 /* iOSTestbedTests */, + 607A66132B0EFA380010BFC8 /* Products */, + 607A664F2B0EFFE00010BFC8 /* Frameworks */, + ); + sourceTree = ""; + }; + 607A66132B0EFA380010BFC8 /* Products */ = { + isa = PBXGroup; + children = ( + 607A66122B0EFA380010BFC8 /* iOSTestbed.app */, + 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 607A66142B0EFA380010BFC8 /* iOSTestbed */ = { + isa = PBXGroup; + children = ( + 607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */, + 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */, + 607A66152B0EFA380010BFC8 /* AppDelegate.h */, + 607A66162B0EFA380010BFC8 /* AppDelegate.m */, + 607A66212B0EFA390010BFC8 /* Assets.xcassets */, + 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */, + 607A66272B0EFA390010BFC8 /* main.m */, + ); + path = iOSTestbed; + sourceTree = ""; + }; + 607A66302B0EFA3A0010BFC8 /* iOSTestbedTests */ = { + isa = PBXGroup; + children = ( + 607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.m */, + ); + path = iOSTestbedTests; + sourceTree = ""; + }; + 607A664F2B0EFFE00010BFC8 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 607A66112B0EFA380010BFC8 /* iOSTestbed */ = { + isa = PBXNativeTarget; + buildConfigurationList = 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbed" */; + buildPhases = ( + 607A660E2B0EFA380010BFC8 /* Sources */, + 607A660F2B0EFA380010BFC8 /* Frameworks */, + 607A66102B0EFA380010BFC8 /* Resources */, + 607A66552B0F061D0010BFC8 /* Install Target Specific Python Standard Library */, + 607A66562B0F06200010BFC8 /* Prepare Python Binary Modules */, + 607A664E2B0EFC080010BFC8 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = iOSTestbed; + productName = iOSTestbed; + productReference = 607A66122B0EFA380010BFC8 /* iOSTestbed.app */; + productType = "com.apple.product-type.application"; + }; + 607A662C2B0EFA3A0010BFC8 /* iOSTestbedTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbedTests" */; + buildPhases = ( + 607A66292B0EFA3A0010BFC8 /* Sources */, + 607A662A2B0EFA3A0010BFC8 /* Frameworks */, + 607A662B2B0EFA3A0010BFC8 /* Resources */, + 607A66522B0EFFE00010BFC8 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */, + ); + name = iOSTestbedTests; + productName = iOSTestbedTests; + productReference = 607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 607A660A2B0EFA380010BFC8 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastUpgradeCheck = 1500; + TargetAttributes = { + 607A66112B0EFA380010BFC8 = { + CreatedOnToolsVersion = 15.0.1; + }; + 607A662C2B0EFA3A0010BFC8 = { + CreatedOnToolsVersion = 15.0.1; + TestTargetID = 607A66112B0EFA380010BFC8; + }; + }; + }; + buildConfigurationList = 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "iOSTestbed" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 607A66092B0EFA380010BFC8; + productRefGroup = 607A66132B0EFA380010BFC8 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 607A66112B0EFA380010BFC8 /* iOSTestbed */, + 607A662C2B0EFA3A0010BFC8 /* iOSTestbedTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 607A66102B0EFA380010BFC8 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */, + 607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */, + 607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 607A662B2B0EFA3A0010BFC8 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 607A66552B0F061D0010BFC8 /* Install Target Specific Python Standard Library */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Install Target Specific Python Standard Library"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\n\nmkdir -p \"$CODESIGNING_FOLDER_PATH/python/lib\"\nif [ \"$EFFECTIVE_PLATFORM_NAME\" = \"-iphonesimulator\" ]; then\n echo \"Installing Python modules for iOS Simulator\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/ios-arm64_x86_64-simulator/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nelse\n echo \"Installing Python modules for iOS Device\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/ios-arm64/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nfi\n"; + }; + 607A66562B0F06200010BFC8 /* Prepare Python Binary Modules */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Prepare Python Binary Modules"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\n\ninstall_dylib () {\n INSTALL_BASE=$1\n FULL_DYLIB=$2\n\n # The name of the .dylib file\n DYLIB=$(basename \"$FULL_DYLIB\")\n # The name of the .dylib file, relative to the install base\n RELATIVE_DYLIB=${FULL_DYLIB#$CODESIGNING_FOLDER_PATH/$INSTALL_BASE/}\n # The full dotted name of the binary module, constructed from the file path.\n FULL_MODULE_NAME=$(echo $RELATIVE_DYLIB | cut -d \".\" -f 1 | tr \"/\" \".\"); \n # A bundle identifier; not actually used, but required by Xcode framework packaging\n FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr \"_\" \"-\")\n # The name of the framework folder.\n FRAMEWORK_FOLDER=\"Frameworks/$FULL_MODULE_NAME.framework\"\n\n # If the framework folder doesn't exist, create it.\n if [ ! -d \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\" ]; then\n echo \"Creating framework for $RELATIVE_DYLIB\" \n mkdir -p \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\"\n cp \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleExecutable -string \"$DYLIB\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleIdentifier -string \"$FRAMEWORK_BUNDLE_ID\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\" \n fi\n \n echo \"Installing binary for $RELATIVE_DYLIB\" \n mv \"$FULL_DYLIB\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\"\n}\n\nPYTHON_VER=$(ls -1 \"$CODESIGNING_FOLDER_PATH/python/lib\")\necho \"Install Python $PYTHON_VER standard library dylibs...\"\nfind \"$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload\" -name \"*.dylib\" | while read FULL_DYLIB; do\n install_dylib python/lib/$PYTHON_VER/lib-dynload \"$FULL_DYLIB\"\ndone\n\n# Clean up dylib template \nrm -f \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\"\necho \"Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)...\"\nfind \"$CODESIGNING_FOLDER_PATH/Frameworks\" -name \"*.framework\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der \"{}\" \\; \n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 607A660E2B0EFA380010BFC8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 607A66172B0EFA380010BFC8 /* AppDelegate.m in Sources */, + 607A66282B0EFA390010BFC8 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 607A66292B0EFA3A0010BFC8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 607A66322B0EFA3A0010BFC8 /* iOSTestbedTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 607A662F2B0EFA3A0010BFC8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 607A66112B0EFA380010BFC8 /* iOSTestbed */; + targetProxy = 607A662E2B0EFA3A0010BFC8 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 607A66242B0EFA390010BFC8 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 607A663F2B0EFA3A0010BFC8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 607A66402B0EFA3A0010BFC8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 607A66422B0EFA3A0010BFC8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 3HEZE76D99; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; + INFOPLIST_FILE = "iOSTestbed/iOSTestbed-Info.plist"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 3.13.0a1; + PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbed; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 607A66432B0EFA3A0010BFC8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 3HEZE76D99; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; + INFOPLIST_FILE = "iOSTestbed/iOSTestbed-Info.plist"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 3.13.0a1; + PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbed; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 607A66452B0EFA3A0010BFC8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 3HEZE76D99; + GENERATE_INFOPLIST_FILE = YES; + HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbedTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/iOSTestbed"; + }; + name = Debug; + }; + 607A66462B0EFA3A0010BFC8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 3HEZE76D99; + GENERATE_INFOPLIST_FILE = YES; + HEADER_SEARCH_PATHS = "\"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers\""; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.python.iOSTestbedTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOSTestbed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/iOSTestbed"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 607A660D2B0EFA380010BFC8 /* Build configuration list for PBXProject "iOSTestbed" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607A663F2B0EFA3A0010BFC8 /* Debug */, + 607A66402B0EFA3A0010BFC8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 607A66412B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbed" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607A66422B0EFA3A0010BFC8 /* Debug */, + 607A66432B0EFA3A0010BFC8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 607A66442B0EFA3A0010BFC8 /* Build configuration list for PBXNativeTarget "iOSTestbedTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607A66452B0EFA3A0010BFC8 /* Debug */, + 607A66462B0EFA3A0010BFC8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 607A660A2B0EFA380010BFC8 /* Project object */; +} diff --git a/iOS/testbed/iOSTestbed/AppDelegate.h b/iOS/testbed/iOSTestbed/AppDelegate.h new file mode 100644 index 000000000000000..f695b3b5efc08b7 --- /dev/null +++ b/iOS/testbed/iOSTestbed/AppDelegate.h @@ -0,0 +1,11 @@ +// +// AppDelegate.h +// iOSTestbed +// + +#import + +@interface AppDelegate : UIResponder + + +@end diff --git a/iOS/testbed/iOSTestbed/AppDelegate.m b/iOS/testbed/iOSTestbed/AppDelegate.m new file mode 100644 index 000000000000000..e5085399d0ca5fb --- /dev/null +++ b/iOS/testbed/iOSTestbed/AppDelegate.m @@ -0,0 +1,19 @@ +// +// AppDelegate.m +// iOSTestbed +// + +#import "AppDelegate.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + return YES; +} + +@end diff --git a/iOS/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json b/iOS/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000000000..eb8789700816459 --- /dev/null +++ b/iOS/testbed/iOSTestbed/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json b/iOS/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000000000..13613e3ee1a9348 --- /dev/null +++ b/iOS/testbed/iOSTestbed/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/testbed/iOSTestbed/Assets.xcassets/Contents.json b/iOS/testbed/iOSTestbed/Assets.xcassets/Contents.json new file mode 100644 index 000000000000000..73c00596a7fca3f --- /dev/null +++ b/iOS/testbed/iOSTestbed/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard b/iOS/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000000000..5daafe73a866b7a --- /dev/null +++ b/iOS/testbed/iOSTestbed/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/iOS/testbed/iOSTestbed/dylib-Info-template.plist b/iOS/testbed/iOSTestbed/dylib-Info-template.plist new file mode 100644 index 000000000000000..f652e272f71c88a --- /dev/null +++ b/iOS/testbed/iOSTestbed/dylib-Info-template.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + + CFBundleIdentifier + + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSupportedPlatforms + + iPhoneOS + + MinimumOSVersion + 12.0 + CFBundleVersion + 1 + + diff --git a/iOS/testbed/iOSTestbed/iOSTestbed-Info.plist b/iOS/testbed/iOSTestbed/iOSTestbed-Info.plist new file mode 100644 index 000000000000000..e2aa460b6fd5eeb --- /dev/null +++ b/iOS/testbed/iOSTestbed/iOSTestbed-Info.plist @@ -0,0 +1,54 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + org.python.iOSTestbed + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIRequiresFullScreen + + UILaunchStoryboardName + Launch Screen + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + MainModule + ios + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + + + diff --git a/iOS/testbed/iOSTestbed/main.m b/iOS/testbed/iOSTestbed/main.m new file mode 100644 index 000000000000000..e32bd78c9b42eee --- /dev/null +++ b/iOS/testbed/iOSTestbed/main.m @@ -0,0 +1,16 @@ +// +// main.m +// iOSTestbed +// + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + NSString * appDelegateClassName; + @autoreleasepool { + appDelegateClassName = NSStringFromClass([AppDelegate class]); + + return UIApplicationMain(argc, argv, nil, appDelegateClassName); + } +} diff --git a/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m b/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m new file mode 100644 index 000000000000000..53ea107db4a2de9 --- /dev/null +++ b/iOS/testbed/iOSTestbedTests/iOSTestbedTests.m @@ -0,0 +1,111 @@ +#import +#import + +@interface iOSTestbedTests : XCTestCase + +@end + +@implementation iOSTestbedTests + + +- (void)testPython { + // Arguments to pass into the test suite runner. + // argv[0] must identify the process; any subsequent arg + // will be handled as if it were an argument to `python -m test` + const char *argv[] = { + "iOSTestbed", // argv[0] is the process that is running. + "-uall", // Enable all resources + "-v", // run in verbose mode so we get test failure information + // To run a subset of tests, add the test names below; e.g., + // "test_os", + // "test_sys", + }; + + // Start a Python interpreter. + int exit_code; + PyStatus status; + PyPreConfig preconfig; + PyConfig config; + NSString *python_home; + wchar_t *wtmp_str; + + NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; + + // Generate an isolated Python configuration. + NSLog(@"Configuring isolated Python..."); + PyPreConfig_InitIsolatedConfig(&preconfig); + PyConfig_InitIsolatedConfig(&config); + + // Configure the Python interpreter: + // Enforce UTF-8 encoding for stderr, stdout, file-system encoding and locale. + // See https://docs.python.org/3/library/os.html#python-utf-8-mode. + preconfig.utf8_mode = 1; + // Don't buffer stdio. We want output to appears in the log immediately + config.buffered_stdio = 0; + // Don't write bytecode; we can't modify the app bundle + // after it has been signed. + config.write_bytecode = 0; + // Ensure that signal handlers are installed + config.install_signal_handlers = 1; + // Run the test module. + config.run_module = Py_DecodeLocale("test", NULL); + // For debugging - enable verbose mode. + // config.verbose = 1; + + NSLog(@"Pre-initializing Python runtime..."); + status = Py_PreInitialize(&preconfig); + if (PyStatus_Exception(status)) { + XCTFail(@"Unable to pre-initialize Python interpreter: %s", status.err_msg); + PyConfig_Clear(&config); + return; + } + + // Set the home for the Python interpreter + python_home = [NSString stringWithFormat:@"%@/python", resourcePath, nil]; + NSLog(@"PythonHome: %@", python_home); + wtmp_str = Py_DecodeLocale([python_home UTF8String], NULL); + status = PyConfig_SetString(&config, &config.home, wtmp_str); + if (PyStatus_Exception(status)) { + XCTFail(@"Unable to set PYTHONHOME: %s", status.err_msg); + PyConfig_Clear(&config); + return; + } + PyMem_RawFree(wtmp_str); + + // Read the site config + status = PyConfig_Read(&config); + if (PyStatus_Exception(status)) { + XCTFail(@"Unable to read site config: %s", status.err_msg); + PyConfig_Clear(&config); + return; + } + + NSLog(@"Configure argc/argv..."); + status = PyConfig_SetBytesArgv(&config, sizeof(argv) / sizeof(char *), (char**) argv); + if (PyStatus_Exception(status)) { + XCTFail(@"Unable to configure argc/argv: %s", status.err_msg); + PyConfig_Clear(&config); + return; + } + + NSLog(@"Initializing Python runtime..."); + status = Py_InitializeFromConfig(&config); + if (PyStatus_Exception(status)) { + XCTFail(@"Unable to initialize Python interpreter: %s", status.err_msg); + PyConfig_Clear(&config); + return; + } + + // Start the test suite. Print a separator to differentiate Python startup logs from app logs + NSLog(@"---------------------------------------------------------------------------"); + + exit_code = Py_RunMain(); + XCTAssertEqual(exit_code, 0, @"Python test suite did not pass"); + + NSLog(@"---------------------------------------------------------------------------"); + + Py_Finalize(); +} + + +@end diff --git a/pyconfig.h.in b/pyconfig.h.in index 63a3437ebb3eac1..e28baef51d57373 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -157,6 +157,9 @@ /* Define to 1 if you have the `clock_settime' function. */ #undef HAVE_CLOCK_SETTIME +/* Define to 1 if the system has the type `clock_t'. */ +#undef HAVE_CLOCK_T + /* Define to 1 if you have the `closefrom' function. */ #undef HAVE_CLOSEFROM @@ -933,6 +936,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_PROCESS_H +/* Define to 1 if you have the `process_vm_readv' function. */ +#undef HAVE_PROCESS_VM_READV + /* Define if your compiler supports function prototype */ #undef HAVE_PROTOTYPES @@ -1938,7 +1944,7 @@ /* Define on FreeBSD to activate all library features */ #undef __BSD_VISIBLE -/* Define to 'long' if doesn't define. */ +/* Define to 'long' if does not define clock_t. */ #undef clock_t /* Define to empty if `const' does not conform to ANSI C. */
- + - +