diff --git a/.github/workflows/reusable-san.yml b/.github/workflows/reusable-san.yml index 405fa36454a62b..cae51fad2c586c 100644 --- a/.github/workflows/reusable-san.yml +++ b/.github/workflows/reusable-san.yml @@ -17,6 +17,7 @@ permissions: env: FORCE_COLOR: 1 + OPENSSL_VER: 3.5.7 jobs: build-san-reusable: @@ -38,28 +39,55 @@ jobs: run: | sudo ./.github/workflows/posix-deps-apt.sh # On ubuntu-26.04 image, clang is clang-21 by default - - if [ "${SANITIZER}" = "TSan" ]; then - # Reduce ASLR to avoid TSan crashing - sudo sysctl -w vm.mmap_rnd_bits=28 - fi - - - name: Sanitizer option setup - run: | - if [ "${SANITIZER}" = "TSan" ]; then - echo "TSAN_OPTIONS=${SAN_LOG_OPTION} suppressions=${GITHUB_WORKSPACE}/Tools/tsan/suppressions${{ - fromJSON(inputs.free-threading) - && '_free_threading' - || '' - }}.txt handle_segv=0" >> "$GITHUB_ENV" - else - echo "UBSAN_OPTIONS=${SAN_LOG_OPTION} halt_on_error=1 suppressions=${GITHUB_WORKSPACE}/Tools/ubsan/suppressions.txt" >> "$GITHUB_ENV" - fi echo "CC=clang" >> "$GITHUB_ENV" echo "CXX=clang++" >> "$GITHUB_ENV" + - name: TSan option setup + if: inputs.sanitizer == 'TSan' + run: | + sudo sysctl -w vm.mmap_rnd_bits=28 # Reduce ASLR to avoid TSan crashing + + echo "MULTISSL_DIR=${GITHUB_WORKSPACE}/multissl" >> "$GITHUB_ENV" + echo "OPENSSL_DIR=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}" >> "$GITHUB_ENV" + echo "TSAN_OPTIONS=${SAN_LOG_OPTION} suppressions=${GITHUB_WORKSPACE}/Tools/tsan/suppressions${SUPPRESSIONS_SUFFIX}.txt handle_segv=0" >> "$GITHUB_ENV" env: - SANITIZER: ${{ inputs.sanitizer }} SAN_LOG_OPTION: log_path=${{ github.workspace }}/san_log + SUPPRESSIONS_SUFFIX: >- + ${{ + fromJSON(inputs.free-threading) + && '_free_threading' + || '' + }} + - name: UBSan option setup + if: inputs.sanitizer != 'TSan' + run: >- + echo + "UBSAN_OPTIONS=${SAN_LOG_OPTION} + halt_on_error=1 + suppressions=${GITHUB_WORKSPACE}/Tools/ubsan/suppressions.txt" + >> "$GITHUB_ENV" + env: + SAN_LOG_OPTION: log_path=${{ github.workspace }}/san_log + - name: Add ccache to PATH + run: | + echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV" + - name: 'Restore OpenSSL build (TSan)' + id: cache-openssl + if: inputs.sanitizer == 'TSan' + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + with: + path: ./multissl/openssl/${{ env.OPENSSL_VER }} + key: ${{ env.IMAGE_OS_VERSION }}-multissl-openssl-tsan-${{ env.OPENSSL_VER }} + - name: Install OpenSSL (TSan) + if: >- + inputs.sanitizer == 'TSan' + && steps.cache-openssl.outputs.cache-hit != 'true' + run: >- + python3 Tools/ssl/multissltests.py + --steps=library + --base-directory="${MULTISSL_DIR}" + --openssl="${OPENSSL_VER}" + --system=Linux + --tsan - name: Configure CPython run: >- ./configure @@ -70,6 +98,7 @@ jobs: || '--with-undefined-behavior-sanitizer --with-strict-overflow' }} --with-pydebug + ${{ inputs.sanitizer == 'TSan' && '--with-openssl="$OPENSSL_DIR" --with-openssl-rpath=auto' || '' }} ${{ fromJSON(inputs.free-threading) && '--disable-gil' || '' }} - name: Build CPython run: make -j4 diff --git a/Doc/library/codecs.rst b/Doc/library/codecs.rst index 99fcf35aa893e4..b871942837d7c9 100644 --- a/Doc/library/codecs.rst +++ b/Doc/library/codecs.rst @@ -1438,6 +1438,14 @@ encodings. | | | code actually uses UTF-8 | | | | by default. | +--------------------+---------+---------------------------+ +| utf-7-imap | mUTF-7 | Modified UTF-7 encoding | +| | | of :rfc:`3501` for IMAP4 | +| | | mailbox names. Only | +| | | ``errors='strict'`` is | +| | | supported. | +| | | | +| | | .. versionadded:: next | ++--------------------+---------+---------------------------+ .. versionchanged:: 3.8 "unicode_internal" codec is removed. diff --git a/Doc/library/ipaddress.rst b/Doc/library/ipaddress.rst index 9ccd8602bcb2c3..387d8034bb8c84 100644 --- a/Doc/library/ipaddress.rst +++ b/Doc/library/ipaddress.rst @@ -718,6 +718,35 @@ dictionaries. .. deprecated:: 3.7 It uses the same ordering and comparison algorithm as "<", "==", and ">" + .. method:: next_network(next_prefix=None) + + Finds the next closest network of prefix size *next_prefix*. If + *next_prefix=None*, then the current network prefix will be used. + Returns a single network object. + + Raises :exc:`ValueError` if *next_prefix* is out of range or no further + network of the requested size exists. + + >>> IPv4Network('192.0.2.0/24').next_network() + IPv4Network('192.0.3.0/24') + >>> IPv4Network('192.0.2.0/24').next_network(next_prefix=25) + IPv4Network('192.0.3.0/25') + >>> IPv4Network('192.0.2.0/24').next_network(next_prefix=23) + IPv4Network('192.0.4.0/23') + >>> IPv4Network('192.0.80.0/22').next_network(next_prefix=18) + IPv4Network('192.0.128.0/18') + >>> IPv4Network('192.0.80.0/22').next_network(next_prefix=50) + Traceback (most recent call last): + File "", line 1, in + raise ValueError( + ValueError: next prefix must be between 1 and 32 + >>> IPv4Network('255.255.255.0/24').next_network() + Traceback (most recent call last): + File "", line 1, in + raise ValueError( + ValueError: out of address space, cannot make another /24 network + + .. versionadded:: 3.16 .. class:: IPv6Network(address, strict=True) @@ -790,6 +819,7 @@ dictionaries. .. method:: supernet(prefixlen_diff=1, new_prefix=None) .. method:: subnet_of(other) .. method:: supernet_of(other) + .. method:: next_network(next_prefix=None) .. method:: compare_networks(other) Refer to the corresponding attribute documentation in diff --git a/Doc/library/tkinter.font.rst b/Doc/library/tkinter.font.rst index 1ac7ac8e98d166..c1cab7802da388 100644 --- a/Doc/library/tkinter.font.rst +++ b/Doc/library/tkinter.font.rst @@ -74,6 +74,8 @@ The different font weights and slants are: requested ones because of platform limitations. With no *option*, return a dictionary of all the attributes; if *option* is given, return the value of that single attribute. + The attributes are resolved on the display of the *displayof* widget, + or the main application window if it is not specified. .. method:: cget(option) @@ -97,7 +99,11 @@ The different font weights and slants are: .. method:: copy() - Return new instance of the current font. + Return a distinct copy of the current font: + a new named font with the same attributes but a different name, + which can be reconfigured independently of the original. + If the current font wraps a font description, + the copy is instead a named font with its resolved attributes. .. method:: measure(text, displayof=None) diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 43ecdb41afc3a9..e871ad822bbced 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -214,6 +214,14 @@ curses wide-character support. (Contributed by Serhiy Storchaka in :gh:`133031`.) +encodings +--------- + +* Add the ``utf-7-imap`` codec, implementing the modified UTF-7 encoding + used for international IMAP4 mailbox names (:rfc:`3501`). + (Contributed by Serhiy Storchaka in :gh:`66788`.) + + gzip ---- @@ -245,6 +253,14 @@ imaplib (Contributed by Przemysław Buczkowski and Serhiy Storchaka in :gh:`89869`.) +ipaddress +--------- + +Add :meth:`~ipaddress.IPv4Network.next_network` and +:meth:`~ipaddress.IPv6Network.next_network` methods to find the next nearest +network with a specific prefix size. + + logging ------- diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 98880cd83780dc..307ecdbaaefcea 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -872,7 +872,8 @@ static inline int _PyType_SUPPORTS_WEAKREFS(PyTypeObject *type) { return (type->tp_weaklistoffset != 0); } -extern PyObject* _PyType_AllocNoTrack(PyTypeObject *type, Py_ssize_t nitems); +// Export for 'array' shared extension. +PyAPI_FUNC(PyObject*) _PyType_AllocNoTrack(PyTypeObject *type, Py_ssize_t nitems); PyAPI_FUNC(PyObject *) _PyType_NewManagedObject(PyTypeObject *type); extern PyTypeObject* _PyType_CalculateMetaclass(PyTypeObject *, PyObject *); diff --git a/Lib/encodings/aliases.py b/Lib/encodings/aliases.py index ef51168d755ba9..de26353aecaaac 100644 --- a/Lib/encodings/aliases.py +++ b/Lib/encodings/aliases.py @@ -600,6 +600,10 @@ 'utf7' : 'utf_7', 'unicode_1_1_utf_7' : 'utf_7', + # utf_7_imap codec + 'csutf7imap' : 'utf_7_imap', + 'mutf_7' : 'utf_7_imap', + # utf_8 codec 'csutf8' : 'utf_8', 'u8' : 'utf_8', diff --git a/Lib/encodings/utf_7_imap.py b/Lib/encodings/utf_7_imap.py new file mode 100644 index 00000000000000..159c8591cc1ff2 --- /dev/null +++ b/Lib/encodings/utf_7_imap.py @@ -0,0 +1,130 @@ +""" Codec for the modified UTF-7 encoding used for IMAP4 mailbox names, +as specified in RFC 3501, section 5.1.3. + +It differs from UTF-7 (RFC 2152) as follows: + +* "&" (not "+") is the shift character introducing a Base64 sequence, + and "&-" encodes a literal "&"; +* "," is used instead of "/" in the modified Base64 alphabet; +* only printable US-ASCII characters (except "&") may be represented + directly, so all other characters, including other controls, are + Base64-encoded. +""" + +import binascii +import codecs + +# The modified Base64 alphabet of RFC 3501: standard Base64 but with "," in +# place of "/". +_alphabet = binascii.BASE64_ALPHABET[:-1] + b',' + +### Codec APIs + +def utf_7_imap_encode(input, errors='strict'): + if errors != 'strict': + raise UnicodeError(f"Unsupported error handling: {errors}") + res = bytearray() + start = 0 # start of the current run + b64run = False # is input[start:i] a Base64 run? + def flush(end): + if start < end: + if b64run: + b64 = binascii.b2a_base64(input[start:end].encode('utf-16-be'), + alphabet=_alphabet, padded=False, + newline=False) + res.extend(b'&' + b64 + b'-') + else: + res.extend(input[start:end].encode('ascii')) + for i, ch in enumerate(input): + if ch == '&': + flush(i) + res.extend(b'&-') + start = i + 1 + b64run = False + elif ' ' <= ch <= '~': # printable ASCII, represented directly + if b64run: + flush(i) + start = i + b64run = False + else: # everything else is Base64-encoded + if not b64run: + flush(i) + start = i + b64run = True + flush(len(input)) + return res.take_bytes(), len(input) + +def utf_7_imap_decode(input, errors='strict'): + if errors != 'strict': + raise UnicodeError(f"Unsupported error handling: {errors}") + input = bytes(input) + res = [] + start = 0 # start of the current direct ASCII run + i = 0 + n = len(input) + def flush(end): + if start < end: + res.append(input[start:end].decode('ascii')) + while i < n: + c = input[i] + if c == b'&'[0]: + flush(i) + j = input.find(b'-', i + 1) + if j < 0: + raise UnicodeDecodeError('utf-7-imap', input, i, n, + 'unterminated shift sequence') + if j == i + 1: # '&-' + res.append('&') + else: + b64 = input[i + 1:j] + try: + data = binascii.a2b_base64(b64, alphabet=_alphabet, + strict_mode=True, padded=False) + except binascii.Error: + data = b'' + if not data or len(data) % 2: + raise UnicodeDecodeError('utf-7-imap', input, i, j + 1, + 'invalid shift sequence') + res.append(data.decode('utf-16-be')) + i = j + 1 + start = i + elif b' '[0] <= c <= b'~'[0]: + i += 1 + else: + raise UnicodeDecodeError('utf-7-imap', input, i, i + 1, + 'unexpected byte') + flush(n) + return ''.join(res), len(input) + +class Codec(codecs.Codec): + def encode(self, input, errors='strict'): + return utf_7_imap_encode(input, errors) + def decode(self, input, errors='strict'): + return utf_7_imap_decode(input, errors) + +class IncrementalEncoder(codecs.IncrementalEncoder): + def encode(self, input, final=False): + return utf_7_imap_encode(input, self.errors)[0] + +class IncrementalDecoder(codecs.IncrementalDecoder): + def decode(self, input, final=False): + return utf_7_imap_decode(input, self.errors)[0] + +class StreamWriter(Codec, codecs.StreamWriter): + pass + +class StreamReader(Codec, codecs.StreamReader): + pass + +### encodings module API + +def getregentry(): + return codecs.CodecInfo( + name='utf-7-imap', + encode=Codec().encode, + decode=Codec().decode, + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamwriter=StreamWriter, + streamreader=StreamReader, + ) diff --git a/Lib/imaplib.py b/Lib/imaplib.py index b1cf8872314d97..7b472c302fb061 100644 --- a/Lib/imaplib.py +++ b/Lib/imaplib.py @@ -138,6 +138,12 @@ _quoted = re.compile(br'"(?:[^"\\]|\\.)*+"') +def _paren_depth(data, depth=0): + # Net parenthesis nesting of data, ignoring parentheses in quoted strings. + data = _quoted.sub(b'', data) + return depth + data.count(b'(') - data.count(b')') + + class IMAP4: r"""IMAP4 client class. @@ -1345,6 +1351,11 @@ def _get_response(self, start_timeout=False): else: resp = self._get_line() + # Skip spurious blank lines between responses (some servers send one + # after a literal that ends a response). + while resp == b'': + resp = self._get_line() + # Command completion response? if self._match(self.tagre, resp): @@ -1382,6 +1393,7 @@ def _get_response(self, start_timeout=False): # Is there a literal to come? + depth = 0 # open parenthesis nesting so far while self._match(self.Literal, dat): # Read literal direct from connection. @@ -1395,13 +1407,15 @@ def _get_response(self, start_timeout=False): # Store response with literal as tuple self._append_untagged(typ, (dat, data)) + depth = _paren_depth(dat, depth) # Read trailer - possibly containing another literal dat = self._get_line() - # Skip a blank line that some servers send after a literal. - if dat == b'': + # Skip spurious blank lines after a literal, but only inside an + # unclosed parenthesis (at top level they end the response). + while dat == b'' and depth > 0: dat = self._get_line() self._append_untagged(typ, dat) diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index f1062a8cd052a5..9c7a0a46f4f0e1 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1119,6 +1119,47 @@ def is_loopback(self): return (self.network_address.is_loopback and self.broadcast_address.is_loopback) + def next_network(self, next_prefix=None): + """Get the next closest network with a specific prefix. + + Args: + next_prefix: The desired next prefix length, if not specified the + same self.prefixlen will be used + + Returns: + An IPv(4|6) Network object of the next closest network. + + """ + if next_prefix is None: + next_prefix = self.prefixlen + new_netmask = self.netmask + else: + if next_prefix < 1 or next_prefix > self.max_prefixlen: + raise ValueError( + f"next prefix must be between 1 and {self.max_prefixlen}" + ) + new_netmask, _ = self._make_netmask(next_prefix) + + bit_shift = ( + self.max_prefixlen - next_prefix + if next_prefix <= self.prefixlen + else self.max_prefixlen - self.prefixlen + ) + + next_ip = ( + ((new_netmask._ip & self.network_address._ip) >> bit_shift) + 1 + ) << bit_shift + + try: + return self.__class__( + f"{self._string_from_ip_int(next_ip)}/{next_prefix}" + ) + except OverflowError: + raise ValueError( + f"out of address space, cannot make another /{next_prefix} " + "network" + ) from None + class _BaseConstants: diff --git a/Lib/test/test_capi/test_config.py b/Lib/test/test_capi/test_config.py index c750a6c2a477ab..29012634361838 100644 --- a/Lib/test/test_capi/test_config.py +++ b/Lib/test/test_capi/test_config.py @@ -9,6 +9,7 @@ from test.support import import_helper _testcapi = import_helper.import_module('_testcapi') +_testinternalcapi = import_helper.import_module('_testinternalcapi') # Is the Py_STATS macro defined? @@ -378,6 +379,46 @@ def expect_bool_not(value): finally: config_set(name, old_value) + def test_config_set_global_vars(self): + # Test PyConfig_Set() with global configuration variables + config_get = _testcapi.config_get + config_set = _testcapi.config_set + get_configs = _testinternalcapi.get_configs + new_values = (0, 1, 5) + + for name, global_name, not_value in ( + ('bytes_warning', 'Py_BytesWarningFlag', False), + ('inspect', 'Py_InspectFlag', False), + ('interactive', 'Py_InteractiveFlag', False), + ('optimization_level', 'Py_OptimizeFlag', False), + ('parser_debug', 'Py_DebugFlag', False), + ('quiet', 'Py_QuietFlag', False), + ('use_environment', 'Py_IgnoreEnvironmentFlag', True), + ('verbose', 'Py_VerboseFlag', False), + ('write_bytecode', 'Py_DontWriteBytecodeFlag', True), + + # Read-only variables + #('buffered_stdio', 'Py_UnbufferedStdioFlag', True), + #('isolated', 'Py_IsolatedFlag', False), + #('pathconfig_warnings', 'Py_FrozenFlag', True), + #('site_import', 'Py_NoSiteFlag', True), + #('user_site_directory', 'Py_NoUserSiteDirectory', True), + # Windows only + #('legacy_windows_stdio', 'Py_LegacyWindowsStdioFlag', False) + ): + with self.subTest(name=name): + old_value = config_get(name) + try: + for value in new_values: + config_set(name, value) + global_config = get_configs()['global_config'] + expected = value + if not_value: + expected = int(not value) + self.assertEqual(global_config[global_name], expected) + finally: + config_set(name, old_value) + def test_config_set_cpu_count(self): config_get = _testcapi.config_get config_set = _testcapi.config_set diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py index 8fdd08df9e4f46..6f47db25271ffe 100644 --- a/Lib/test/test_codecs.py +++ b/Lib/test/test_codecs.py @@ -1401,6 +1401,73 @@ def test_decode_invalid(self): self.assertEqual(puny.decode("punycode", errors), expected) +utf_7_imap_testcases = [ + # (unicode, modified UTF-7) + ('', b''), + ('INBOX', b'INBOX'), + # Printable US-ASCII represents itself. + ('abcABC123 !#$%()*+,-./:;<=>?@[]^_`{|}~', b'abcABC123 !#$%()*+,-./:;<=>?@[]^_`{|}~'), + # "&" is escaped as "&-". + ('&', b'&-'), + ('&&', b'&-&-'), + ('A&B', b'A&-B'), + # "+" and "+-" are literal, unlike in UTF-7 where "+" is the shift character. + ('+', b'+'), + ('+-', b'+-'), + # RFC 3501 section 5.1.3 example. + ('~peter/mail/台北/日本語', + b'~peter/mail/&U,BTFw-/&ZeVnLIqe-'), + # Non-printable ASCII (including TAB) is Base64-encoded, not direct. + ('a\tb', b'a&AAk-b'), + ('\x00', b'&AAA-'), + ('Entw\xfcrfe', b'Entw&APw-rfe'), + ('ϰ', b'&A,A-'), # "," in the Base64 alphabet ("&A/A-" is invalid) + ('☃', b'&JgM-'), # snowman + ('\U0001f600', b'&2D3eAA-'), # non-BMP (surrogate pair) + ('Sent &\N{DELETE}', b'Sent &-&AH8-'), +] + +class Utf7ImapTest(unittest.TestCase): + @support.subTests('uni,encoded', utf_7_imap_testcases) + def test_encode(self, uni, encoded): + self.assertEqual(uni.encode('utf-7-imap'), encoded) + + @support.subTests('uni,encoded', utf_7_imap_testcases) + def test_decode(self, uni, encoded): + self.assertEqual(encoded.decode('utf-7-imap'), uni) + + # 'start' is the position of the first offending byte in each case. + @support.subTests('encoded,start', [ + (b'x&', 1), # "&" just before the end, unterminated + (b'&AAAA', 0), # unterminated, though the Base64 is valid + (b'&AB-', 0), # Base64 length not a multiple of a code unit + (b'&A/A-', 0), # "/" not in the alphabet ("&A,A-" is valid) + (b'&@@@-', 0), # invalid Base64 + (b'a\x80b', 1), # 8-bit byte outside a shift sequence + (b'a\x1fb', 1), # control byte outside a shift sequence + ]) + def test_decode_invalid(self, encoded, start): + with self.assertRaises(UnicodeDecodeError) as cm: + encoded.decode('utf-7-imap') + self.assertEqual(cm.exception.encoding, 'utf-7-imap') + self.assertEqual(cm.exception.start, start) + + def test_encode_lone_surrogate(self): + with self.assertRaises(UnicodeEncodeError): + '\ud800'.encode('utf-7-imap') + + def test_only_strict_errors(self): + with self.assertRaises(UnicodeError): + 'x'.encode('utf-7-imap', 'replace') + with self.assertRaises(UnicodeError): + b'x'.decode('utf-7-imap', 'ignore') + + def test_stateless(self): + # The codec is registered and exposes the standard interface. + info = codecs.lookup('utf-7-imap') + self.assertEqual(info.name, 'utf-7-imap') + + # From http://www.gnu.org/software/libidn/draft-josefsson-idn-test-vectors.html nameprep_tests = [ # 3.1 Map to nothing. diff --git a/Lib/test/test_free_threading/test_generators.py b/Lib/test/test_free_threading/test_generators.py index 2b41e28896f5a8..382503eebd123f 100644 --- a/Lib/test/test_free_threading/test_generators.py +++ b/Lib/test/test_free_threading/test_generators.py @@ -3,11 +3,10 @@ import threading import unittest from threading import Barrier -from unittest import TestCase import random import time -from test.support import threading_helper, Py_GIL_DISABLED +from test.support import threading_helper threading_helper.requires_working_threading(module=True) @@ -32,8 +31,7 @@ def set_gen_qualname(g, b): return g.__qualname__ -@unittest.skipUnless(Py_GIL_DISABLED, "Enable only in FT build") -class TestFTGenerators(TestCase): +class TestFTGenerators(unittest.TestCase): NUM_THREADS = 4 def concurrent_write_with_func(self, func): diff --git a/Lib/test/test_free_threading/test_types.py b/Lib/test/test_free_threading/test_types.py new file mode 100644 index 00000000000000..76fcf1590122f5 --- /dev/null +++ b/Lib/test/test_free_threading/test_types.py @@ -0,0 +1,33 @@ +import unittest +from typing import TypeVar +from test.support import threading_helper + +threading_helper.requires_working_threading(module=True) + + +class TestGenericAlias(unittest.TestCase): + def test_parameters_race(self): + # gh-153298 + + T = TypeVar('T') + slot = [list[T]] + + def access(): + for _ in range(2000): + try: + _ = slot[0].__parameters__ + except Exception: + pass + + def refresh(): + for _ in range(2000): + slot[0] = list[T] + + threading_helper.run_concurrently([ + *[access for _ in range(6)], + *[refresh for _ in range(2)], + ]) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py index a2ddbbf9121808..30297fb5afcd19 100644 --- a/Lib/test/test_imaplib.py +++ b/Lib/test/test_imaplib.py @@ -1041,6 +1041,51 @@ def cmd_FETCH(self, tag, args): self.assertEqual(data, [(b'1 (BODY[HEADER] {13}', b'Subject: test'), b')']) + def test_literal_terminating_response(self): + # A literal ending a response (a LIST mailbox name sent as a literal) + # has an empty trailer that must not be swallowed. Conforming case: + # no spurious blank lines. + names = [b'My (box)"', b'Another', b'Third'] + class Handler(SimpleIMAPHandler): + def cmd_LIST(self, tag, args): + for name in names: + self._send(b'* LIST (\\HasNoChildren) "/" {%d}\r\n' + % len(name)) + self._send(name) + self._send(b'\r\n') # ends the response, no blank + self._send_tagged(tag, 'OK', 'LIST completed') + client, _ = self._setup(Handler) + client.login('user', 'pass') + typ, data = client.list() + self.assertEqual(typ, 'OK') + self.assertEqual(data, [ + (b'(\\HasNoChildren) "/" {9}', b'My (box)"'), b'', + (b'(\\HasNoChildren) "/" {7}', b'Another'), b'', + (b'(\\HasNoChildren) "/" {5}', b'Third'), b'', + ]) + + def test_spurious_blank_lines_between_responses(self): + # A spurious blank line after each terminating literal falls between the + # untagged responses and must be skipped, even several in a row. + names = [b'My (box)"', b'Another', b'Third'] + class Handler(SimpleIMAPHandler): + def cmd_LIST(self, tag, args): + for name in names: + self._send(b'* LIST (\\HasNoChildren) "/" {%d}\r\n' + % len(name)) + self._send(name) + self._send(b'\r\n\r\n') # ends the response, then a blank + self._send_tagged(tag, 'OK', 'LIST completed') + client, _ = self._setup(Handler) + client.login('user', 'pass') + typ, data = client.list() + self.assertEqual(typ, 'OK') + self.assertEqual(data, [ + (b'(\\HasNoChildren) "/" {9}', b'My (box)"'), b'', + (b'(\\HasNoChildren) "/" {7}', b'Another'), b'', + (b'(\\HasNoChildren) "/" {5}', b'Third'), b'', + ]) + def test_unselect(self): client, server = self._setup(SimpleIMAPHandler) client.login('user', 'pass') diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index 3f017b97dc28a3..a74b692784eb59 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -1559,6 +1559,47 @@ def testHosts(self): self.assertEqual(list(ipaddress.ip_network(str_args).hosts()), list(ipaddress.ip_network(tpl_args).hosts())) + def testNextNetwork(self): + ipv4 = ipaddress.IPv4Network('1.2.3.0/24') + self.assertEqual( + ipv4.next_network(), + ipaddress.IPv4Network('1.2.4.0/24'), + ) + self.assertEqual( + ipv4.next_network(next_prefix=16), + ipaddress.IPv4Network('1.3.0.0/16'), + ) + self.assertEqual( + ipv4.next_network(next_prefix=25), + ipaddress.IPv4Network('1.2.4.0/25'), + ) + + ipv6 = ipaddress.IPv6Network('2001:dbb8:aaaa:aaaa::/64') + self.assertEqual( + ipv6.next_network(), + ipaddress.IPv6Network('2001:dbb8:aaaa:aaab::/64'), + ) + self.assertEqual( + ipv6.next_network(next_prefix=48), + ipaddress.IPv6Network('2001:dbb8:aaab::/48'), + ) + self.assertEqual( + ipv6.next_network(next_prefix=88), + ipaddress.IPv6Network('2001:dbb8:aaaa:aaab::/88'), + ) + + def testNextNetworkWithBadPrefix(self): + self.assertRaises(ValueError, self.ipv4_network.next_network, 0) + self.assertRaises(ValueError, self.ipv4_network.next_network, 35) + self.assertRaises(ValueError, self.ipv6_network.next_network, 0) + self.assertRaises(ValueError, self.ipv6_network.next_network, 150) + + def testNextNetworkOutOfAddressSpace(self): + ipv4 = ipaddress.IPv4Network('255.255.255.0/24') + self.assertRaises(ValueError, ipv4.next_network) + ipv6 = ipaddress.IPv6Network('ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/112') + self.assertRaises(ValueError, ipv6.next_network) + def testFancySubnetting(self): self.assertEqual(sorted(self.ipv4_network.subnets(prefixlen_diff=3)), sorted(self.ipv4_network.subnets(new_prefix=27))) diff --git a/Lib/test/test_readline.py b/Lib/test/test_readline.py index 3982686dd10aec..b0b9d64cfe6a5f 100644 --- a/Lib/test/test_readline.py +++ b/Lib/test/test_readline.py @@ -413,6 +413,16 @@ def test_write_read_limited_history(self): # So, we've only tested that the read did not fail. # See TestHistoryManipulation for the full test. + def test_environment_is_not_modified(self): + # os.environ contains environment at the time "os" module was loaded, so + # before the "readline" module is loaded. + original_env = dict(os.environ) + + # Force refresh of os.environ and make sure it is the same as before the + # refresh. + os.reload_environ() + self.assertEqual(dict(os.environ), original_env) + @unittest.skipUnless(hasattr(readline, "get_pre_input_hook"), "get_pre_input_hook not available") def test_get_pre_input_hook(self): diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 575a322aa45923..352a9c2a0c6106 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -1604,6 +1604,9 @@ def dummycallback(sock, servername, ctx, cycle=ctx): gc.collect() self.assertIs(wr(), None) + @support.skip_if_sanitizer("gh-150191: OpenSSL has an internal data race " + "when the SNI callback is replaced during a " + "handshake", thread=True) @threading_helper.requires_working_threading() def test_sni_callback_race(self): # Replacing sni_callback while a handshake is in-flight must not @@ -5045,6 +5048,9 @@ def server_callback(identity): with client_context.wrap_socket(socket.socket()) as s: s.connect((HOST, server.port)) + @support.skip_if_sanitizer("gh-150191: OpenSSL races on SSL->rwstate and " + "the socket BIO flags with concurrent read " + "and write", thread=True) def test_thread_recv_while_main_thread_sends(self): # GH-137583: Locking was added to calls to send() and recv() on SSL # socket objects. This seemed fine at the surface level because those diff --git a/Lib/test/test_tkinter/test_font.py b/Lib/test/test_tkinter/test_font.py index 8e278456f383a1..3d76ae630d97e3 100644 --- a/Lib/test/test_tkinter/test_font.py +++ b/Lib/test/test_tkinter/test_font.py @@ -20,6 +20,10 @@ def setUpClass(cls): except tkinter.TclError: cls.font = font.Font(root=cls.root, name=fontname, exists=False) + def actual_size(self, desc): + # The requested size is not always available (e.g. bitmap fonts). + return self.root.tk.call('font', 'actual', desc, '-size') + def test_configure(self): self.assertEqual(self.font.config, self.font.configure) options = self.font.configure() @@ -51,7 +55,7 @@ def test_create(self): f = font.Font(root=self.root, font=('Times', 20, 'bold')) self.assertIn(f.name, font.names(self.root)) self.assertEqual(f.actual('weight'), 'bold') - self.assertEqual(f.cget('size'), sizetype(20)) + self.assertEqual(f.cget('size'), self.actual_size(('Times', 20, 'bold'))) # ... or from the keyword options. f = font.Font(root=self.root, family='Times', size=20, weight='bold') @@ -62,13 +66,13 @@ def test_create(self): # Explicit options override the corresponding settings of *font*. f = font.Font(root=self.root, font=('Times', 20, 'bold'), weight='normal') self.assertEqual(f.actual('weight'), 'normal') - self.assertEqual(f.cget('size'), sizetype(20)) + self.assertEqual(f.cget('size'), self.actual_size(('Times', 20, 'bold'))) # The new font can be given an explicit name. f = font.Font(root=self.root, name='testfont', font=('Times', 20)) self.assertEqual(f.name, 'testfont') self.assertIn('testfont', font.names(self.root)) - self.assertEqual(f.cget('size'), sizetype(20)) + self.assertEqual(f.cget('size'), self.actual_size(('Times', 20))) # Reusing the name of an existing font fails. self.assertRaises(tkinter.TclError, font.Font, root=self.root, name='testfont', font=('Times', 10)) @@ -134,7 +138,7 @@ def test_existing(self): self.assertEqual(str(f), 'Times 20 bold') self.assertNotIn(f.name, font.names(self.root)) self.assertEqual(f.actual('weight'), 'bold') - self.assertEqual(f.actual('size'), sizetype(20)) + self.assertEqual(f.actual('size'), self.actual_size(('Times', 20, 'bold'))) # It can be used as a widget option, with the same effect as the # description itself (gh-143990). self.assertEqual(tkinter.Label(self.root, font=f).cget('font'), diff --git a/Misc/NEWS.d/next/C_API/2026-07-08-00-38-32.gh-issue-153300.lVoWyR.rst b/Misc/NEWS.d/next/C_API/2026-07-08-00-38-32.gh-issue-153300.lVoWyR.rst new file mode 100644 index 00000000000000..3c19c0e15d2bc6 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-07-08-00-38-32.gh-issue-153300.lVoWyR.rst @@ -0,0 +1,3 @@ +:c:func:`PyConfig_Set()` now also set global configuration variables. For +example, ``PyConfig_Set("inspect", value)`` now also sets +:c:var:`Py_InspectFlag`. Patch by Victor Stinner. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-08-13-23-11.gh-issue-153298.wvcXxN.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-08-13-23-11.gh-issue-153298.wvcXxN.rst new file mode 100644 index 00000000000000..38d548834d5e02 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-08-13-23-11.gh-issue-153298.wvcXxN.rst @@ -0,0 +1,2 @@ +Fixes a data race in :class:`types.GenericAlias` ``__parameters__`` +initialization on free-threading builds. diff --git a/Misc/NEWS.d/next/Library/2021-01-09-18-40-15.bpo-42861.T7Ge9O.rst b/Misc/NEWS.d/next/Library/2021-01-09-18-40-15.bpo-42861.T7Ge9O.rst new file mode 100644 index 00000000000000..46ed99d1e1b499 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-01-09-18-40-15.bpo-42861.T7Ge9O.rst @@ -0,0 +1,2 @@ +Add :meth:`~ipaddress.IPv4Network.next_network` and +:meth:`~ipaddress.IPv6Network.next_network`. Patch by Faisal Mahmood. diff --git a/Misc/NEWS.d/next/Library/2025-05-07-18-31-31.gh-issue-46927.sF02gj.rst b/Misc/NEWS.d/next/Library/2025-05-07-18-31-31.gh-issue-46927.sF02gj.rst new file mode 100644 index 00000000000000..3ba9757c8acb5d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-07-18-31-31.gh-issue-46927.sF02gj.rst @@ -0,0 +1,2 @@ +Prevent :mod:`readline` from overriding the ``COLUMNS`` and ``LINES`` +environment variables, as values are not updated on terminal resize. diff --git a/Misc/NEWS.d/next/Library/2026-07-01-10-00-00.gh-issue-88574.Kz3wQm.rst b/Misc/NEWS.d/next/Library/2026-07-01-10-00-00.gh-issue-88574.Kz3wQm.rst index 8d95bbbb5a15e4..371f4d08bccd2e 100644 --- a/Misc/NEWS.d/next/Library/2026-07-01-10-00-00.gh-issue-88574.Kz3wQm.rst +++ b/Misc/NEWS.d/next/Library/2026-07-01-10-00-00.gh-issue-88574.Kz3wQm.rst @@ -1,2 +1,4 @@ :mod:`imaplib` no longer fails when a server sends a spurious blank line -after the counted data of a literal. Such a blank line is now skipped. +after the counted data of a literal, including after a literal that +terminates a response (such as a mailbox name returned by ``LIST``). +Such blank lines are now skipped without swallowing the following line. diff --git a/Misc/NEWS.d/next/Library/2026-07-05-22-30-00.gh-issue-66788.tM7pQ2.rst b/Misc/NEWS.d/next/Library/2026-07-05-22-30-00.gh-issue-66788.tM7pQ2.rst new file mode 100644 index 00000000000000..29bd36ebd6295f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-05-22-30-00.gh-issue-66788.tM7pQ2.rst @@ -0,0 +1,2 @@ +Add the ``utf-7-imap`` codec, implementing the modified UTF-7 encoding +used for international IMAP4 mailbox names (:rfc:`3501`, section 5.1.3). diff --git a/Misc/NEWS.d/next/Library/2026-07-08-01-47-15.gh-issue-153083.XbKZCp.rst b/Misc/NEWS.d/next/Library/2026-07-08-01-47-15.gh-issue-153083.XbKZCp.rst new file mode 100644 index 00000000000000..b6d96851e17342 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-08-01-47-15.gh-issue-153083.XbKZCp.rst @@ -0,0 +1,2 @@ +Defer GC tracking of an :class:`array.array` to the end of its construction. +Patch by Donghee Na. diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c index 2b1373dbe6eb46..68486c66575933 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -14,6 +14,7 @@ #include "pycore_floatobject.h" // _PY_FLOAT_BIG_ENDIAN #include "pycore_modsupport.h" // _PyArg_NoKeywords() #include "pycore_moduleobject.h" // _PyModule_GetState() +#include "pycore_object.h" // _PyObject_GC_TRACK() #include "pycore_tuple.h" // _PyTuple_FromPairSteal #include "pycore_weakref.h" // FT_CLEAR_WEAKREFS() @@ -752,7 +753,8 @@ class array.array "arrayobject *" "ArrayType" /*[clinic end generated code: output=da39a3ee5e6b4b0d input=a5c29edf59f176a3]*/ static PyObject * -newarrayobject(PyTypeObject *type, Py_ssize_t size, const struct arraydescr *descr) +newarrayobject_untracked(PyTypeObject *type, Py_ssize_t size, + const struct arraydescr *descr) { arrayobject *op; size_t nbytes; @@ -767,7 +769,7 @@ newarrayobject(PyTypeObject *type, Py_ssize_t size, const struct arraydescr *des return PyErr_NoMemory(); } nbytes = size * descr->itemsize; - op = (arrayobject *) type->tp_alloc(type, 0); + op = (arrayobject *) _PyType_AllocNoTrack(type, 0); if (op == NULL) { return NULL; } @@ -789,6 +791,16 @@ newarrayobject(PyTypeObject *type, Py_ssize_t size, const struct arraydescr *des return (PyObject *) op; } +static PyObject * +newarrayobject(PyTypeObject *type, Py_ssize_t size, const struct arraydescr *descr) +{ + PyObject *op = newarrayobject_untracked(type, size, descr); + if (op != NULL) { + _PyObject_GC_TRACK(op); + } + return op; +} + static PyObject * getarrayitem(PyObject *op, Py_ssize_t i) { @@ -990,13 +1002,14 @@ array_slice(arrayobject *a, Py_ssize_t ilow, Py_ssize_t ihigh) ihigh = ilow; else if (ihigh > Py_SIZE(a)) ihigh = Py_SIZE(a); - np = (arrayobject *) newarrayobject(state->ArrayType, ihigh - ilow, a->ob_descr); + np = (arrayobject *) newarrayobject_untracked(state->ArrayType, ihigh - ilow, a->ob_descr); if (np == NULL) return NULL; if (ihigh > ilow) { memcpy(np->ob_item, a->ob_item + ilow * a->ob_descr->itemsize, (ihigh-ilow) * a->ob_descr->itemsize); } + _PyObject_GC_TRACK(np); return (PyObject *)np; } @@ -1067,7 +1080,7 @@ array_concat(PyObject *op, PyObject *bb) return PyErr_NoMemory(); } size = Py_SIZE(a) + Py_SIZE(b); - np = (arrayobject *) newarrayobject(state->ArrayType, size, a->ob_descr); + np = (arrayobject *) newarrayobject_untracked(state->ArrayType, size, a->ob_descr); if (np == NULL) { return NULL; } @@ -1078,6 +1091,7 @@ array_concat(PyObject *op, PyObject *bb) memcpy(np->ob_item + Py_SIZE(a)*a->ob_descr->itemsize, b->ob_item, Py_SIZE(b)*b->ob_descr->itemsize); } + _PyObject_GC_TRACK(np); return (PyObject *)np; #undef b } @@ -1095,16 +1109,19 @@ array_repeat(PyObject *op, Py_ssize_t n) return PyErr_NoMemory(); } Py_ssize_t size = array_length * n; - arrayobject* np = (arrayobject *) newarrayobject(state->ArrayType, size, a->ob_descr); + arrayobject* np = (arrayobject *) newarrayobject_untracked(state->ArrayType, size, a->ob_descr); if (np == NULL) return NULL; - if (size == 0) + if (size == 0) { + _PyObject_GC_TRACK(np); return (PyObject *)np; + } const Py_ssize_t oldbytes = array_length * a->ob_descr->itemsize; const Py_ssize_t newbytes = oldbytes * n; _PyBytes_RepeatBuffer(np->ob_item, newbytes, a->ob_item, oldbytes); + _PyObject_GC_TRACK(np); return (PyObject *)np; } @@ -2666,17 +2683,17 @@ array_subscr(PyObject *op, PyObject *item) return newarrayobject(state->ArrayType, 0, self->ob_descr); } else if (step == 1) { - PyObject *result = newarrayobject(state->ArrayType, - slicelength, self->ob_descr); + PyObject *result = newarrayobject_untracked(state->ArrayType, slicelength, self->ob_descr); if (result == NULL) return NULL; memcpy(((arrayobject *)result)->ob_item, self->ob_item + start * itemsize, slicelength * itemsize); + _PyObject_GC_TRACK(result); return result; } else { - result = newarrayobject(state->ArrayType, slicelength, self->ob_descr); + result = newarrayobject_untracked(state->ArrayType, slicelength, self->ob_descr); if (!result) return NULL; ar = (arrayobject*)result; @@ -2688,6 +2705,7 @@ array_subscr(PyObject *op, PyObject *item) itemsize); } + _PyObject_GC_TRACK(result); return result; } } @@ -2973,7 +2991,7 @@ array_new(PyTypeObject *type, PyObject *args, PyObject *kwds) else len = 0; - a = newarrayobject(type, len, descr); + a = newarrayobject_untracked(type, len, descr); if (a == NULL) { Py_XDECREF(it); return NULL; @@ -3039,6 +3057,8 @@ array_new(PyTypeObject *type, PyObject *args, PyObject *kwds) } Py_DECREF(it); } + // Track only once fully built. + _PyObject_GC_TRACK(a); return a; } } @@ -3140,7 +3160,7 @@ static PyType_Slot array_slots[] = { {Py_tp_methods, array_methods}, {Py_tp_members, array_members}, {Py_tp_getset, array_getsets}, - {Py_tp_alloc, PyType_GenericAlloc}, + {Py_tp_alloc, _PyType_AllocNoTrack}, {Py_tp_new, array_new}, {Py_tp_traverse, _PyObject_VisitType}, diff --git a/Modules/main.c b/Modules/main.c index a4dfddd98e257e..2f23513cc2403d 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -529,11 +529,15 @@ pymain_run_interactive_hook(int *exitcode) static void pymain_set_inspect(PyConfig *config, int inspect) { - config->inspect = inspect; -_Py_COMP_DIAG_PUSH -_Py_COMP_DIAG_IGNORE_DEPR_DECLS - Py_InspectFlag = inspect; -_Py_COMP_DIAG_POP + PyObject *value = PyLong_FromLong(inspect); + if (value == NULL || PyConfig_Set("inspect", value) < 0) { + fprintf(stderr, "Could not set the inspect flag\n"); + PyErr_Print(); + } + else { + assert(config->inspect == inspect); + } + Py_XDECREF(value); } @@ -635,7 +639,7 @@ pymain_run_python(int *exitcode) { PyObject *main_importer_path = NULL; PyInterpreterState *interp = _PyInterpreterState_GET(); - /* pymain_run_stdin() modify the config */ + /* pymain_repl() and pymain_run_stdin() modify the config */ PyConfig *config = (PyConfig*)_PyInterpreterState_GetConfig(interp); /* ensure path config is written into global variables */ diff --git a/Modules/readline.c b/Modules/readline.c index c580d2022fccf3..4c965e081c9d1a 100644 --- a/Modules/readline.c +++ b/Modules/readline.c @@ -1351,6 +1351,13 @@ setup_readline(readlinestate *mod_state) /* The name must be defined before initialization */ rl_readline_name = "python"; +#ifdef HAVE_RL_CHANGE_ENVIRONMENT + /* Prevent readline from setting the LINES and COLUMNS environment + * variables: ncurses prefers them over an ioctl() query, so a stale value + * left after a resize breaks SIGWINCH / KEY_RESIZE handling (gh-46927). */ + rl_change_environment = 0; +#endif + /* the libedit readline emulation resets key bindings etc * when calling rl_initialize. So call it upfront */ diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index c2083e6fcb77f4..4e85927def7ea7 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -841,7 +841,7 @@ static PyMemberDef ga_members[] = { }; static PyObject * -ga_parameters(PyObject *self, void *unused) +ga_parameters_lock_held(PyObject *self) { gaobject *alias = (gaobject *)self; if (alias->parameters == NULL) { @@ -853,6 +853,16 @@ ga_parameters(PyObject *self, void *unused) return Py_NewRef(alias->parameters); } +static PyObject * +ga_parameters(PyObject *self, void *unused) +{ + PyObject *result; + Py_BEGIN_CRITICAL_SECTION(self); + result = ga_parameters_lock_held(self); + Py_END_CRITICAL_SECTION(); + return result; +} + static PyObject * ga_unpacked_tuple_args(PyObject *self, void *unused) { diff --git a/Python/initconfig.c b/Python/initconfig.c index f653c00d1f0bed..cd5f3c086a2f4b 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -84,120 +84,139 @@ typedef struct { config_sys_flag_setter flag_setter; } PyConfigSysSpec; +typedef struct { + int *ptr; + int not; +} PyConfigGlobalVar; + typedef struct { const char *name; size_t offset; PyConfigMemberType type; PyConfigMemberVisibility visibility; PyConfigSysSpec sys; + PyConfigGlobalVar global_var; } PyConfigSpec; -#define SPEC(MEMBER, TYPE, VISIBILITY, sys) \ +#define SPEC(MEMBER, TYPE, VISIBILITY, sys, global_var) \ {#MEMBER, offsetof(PyConfig, MEMBER), \ - PyConfig_MEMBER_##TYPE, PyConfig_MEMBER_##VISIBILITY, sys} + PyConfig_MEMBER_##TYPE, PyConfig_MEMBER_##VISIBILITY, sys, global_var} #define SYS_ATTR(name) {name, -1, NULL} #define SYS_FLAG_SETTER(index, setter) {NULL, index, setter} #define SYS_FLAG(index) SYS_FLAG_SETTER(index, NULL) #define NO_SYS SYS_ATTR(NULL) +#define GLOBAL(ptr, not) {ptr, not} +#define NO_GLOBAL GLOBAL(NULL, 0) + +// Ignore deprecations on global variables such as Py_IsolatedFlag +_Py_COMP_DIAG_PUSH +_Py_COMP_DIAG_IGNORE_DEPR_DECLS + // Update _test_embed_set_config when adding new members static const PyConfigSpec PYCONFIG_SPEC[] = { // --- Public options ----------- - SPEC(argv, WSTR_LIST, PUBLIC, SYS_ATTR("argv")), - SPEC(base_exec_prefix, WSTR_OPT, PUBLIC, SYS_ATTR("base_exec_prefix")), - SPEC(base_executable, WSTR_OPT, PUBLIC, SYS_ATTR("_base_executable")), - SPEC(base_prefix, WSTR_OPT, PUBLIC, SYS_ATTR("base_prefix")), - SPEC(bytes_warning, UINT, PUBLIC, SYS_FLAG(9)), - SPEC(cpu_count, INT, PUBLIC, NO_SYS), - SPEC(lazy_imports, INT, PUBLIC, NO_SYS), - SPEC(exec_prefix, WSTR_OPT, PUBLIC, SYS_ATTR("exec_prefix")), - SPEC(executable, WSTR_OPT, PUBLIC, SYS_ATTR("executable")), - SPEC(inspect, BOOL, PUBLIC, SYS_FLAG(1)), - SPEC(int_max_str_digits, UINT, PUBLIC, NO_SYS), - SPEC(interactive, BOOL, PUBLIC, SYS_FLAG(2)), - SPEC(module_search_paths, WSTR_LIST, PUBLIC, SYS_ATTR("path")), - SPEC(optimization_level, UINT, PUBLIC, SYS_FLAG(3)), - SPEC(parser_debug, BOOL, PUBLIC, SYS_FLAG(0)), - SPEC(platlibdir, WSTR, PUBLIC, SYS_ATTR("platlibdir")), - SPEC(prefix, WSTR_OPT, PUBLIC, SYS_ATTR("prefix")), - SPEC(pycache_prefix, WSTR_OPT, PUBLIC, SYS_ATTR("pycache_prefix")), - SPEC(quiet, BOOL, PUBLIC, SYS_FLAG(10)), - SPEC(stdlib_dir, WSTR_OPT, PUBLIC, SYS_ATTR("_stdlib_dir")), - SPEC(use_environment, BOOL, PUBLIC, SYS_FLAG_SETTER(7, config_sys_flag_not)), - SPEC(verbose, UINT, PUBLIC, SYS_FLAG(8)), - SPEC(warnoptions, WSTR_LIST, PUBLIC, SYS_ATTR("warnoptions")), - SPEC(write_bytecode, BOOL, PUBLIC, SYS_FLAG_SETTER(4, config_sys_flag_not)), - SPEC(xoptions, WSTR_LIST, PUBLIC, SYS_ATTR("_xoptions")), + SPEC(argv, WSTR_LIST, PUBLIC, SYS_ATTR("argv"), NO_GLOBAL), + SPEC(base_exec_prefix, WSTR_OPT, PUBLIC, SYS_ATTR("base_exec_prefix"), NO_GLOBAL), + SPEC(base_executable, WSTR_OPT, PUBLIC, SYS_ATTR("_base_executable"), NO_GLOBAL), + SPEC(base_prefix, WSTR_OPT, PUBLIC, SYS_ATTR("base_prefix"), NO_GLOBAL), + SPEC(bytes_warning, UINT, PUBLIC, SYS_FLAG(9), GLOBAL(&Py_BytesWarningFlag, 0)), + SPEC(cpu_count, INT, PUBLIC, NO_SYS, NO_GLOBAL), + SPEC(lazy_imports, INT, PUBLIC, NO_SYS, NO_GLOBAL), + SPEC(exec_prefix, WSTR_OPT, PUBLIC, SYS_ATTR("exec_prefix"), NO_GLOBAL), + SPEC(executable, WSTR_OPT, PUBLIC, SYS_ATTR("executable"), NO_GLOBAL), + SPEC(inspect, BOOL, PUBLIC, SYS_FLAG(1), GLOBAL(&Py_InspectFlag, 0)), + SPEC(int_max_str_digits, UINT, PUBLIC, NO_SYS, NO_GLOBAL), + SPEC(interactive, BOOL, PUBLIC, SYS_FLAG(2), GLOBAL(&Py_InteractiveFlag, 0)), + SPEC(module_search_paths, WSTR_LIST, PUBLIC, SYS_ATTR("path"), NO_GLOBAL), + SPEC(optimization_level, UINT, PUBLIC, SYS_FLAG(3), GLOBAL(&Py_OptimizeFlag, 0)), + SPEC(parser_debug, BOOL, PUBLIC, SYS_FLAG(0), GLOBAL(&Py_DebugFlag, 0)), + SPEC(platlibdir, WSTR, PUBLIC, SYS_ATTR("platlibdir"), NO_GLOBAL), + SPEC(prefix, WSTR_OPT, PUBLIC, SYS_ATTR("prefix"), NO_GLOBAL), + SPEC(pycache_prefix, WSTR_OPT, PUBLIC, SYS_ATTR("pycache_prefix"), NO_GLOBAL), + SPEC(quiet, BOOL, PUBLIC, SYS_FLAG(10), GLOBAL(&Py_QuietFlag, 0)), + SPEC(stdlib_dir, WSTR_OPT, PUBLIC, SYS_ATTR("_stdlib_dir"), NO_GLOBAL), + SPEC(use_environment, BOOL, PUBLIC, + SYS_FLAG_SETTER(7, config_sys_flag_not), GLOBAL(&Py_IgnoreEnvironmentFlag, 1)), + SPEC(verbose, UINT, PUBLIC, SYS_FLAG(8), GLOBAL(&Py_VerboseFlag, 0)), + SPEC(warnoptions, WSTR_LIST, PUBLIC, SYS_ATTR("warnoptions"), NO_GLOBAL), + SPEC(write_bytecode, BOOL, PUBLIC, SYS_FLAG_SETTER(4, config_sys_flag_not), + GLOBAL(&Py_DontWriteBytecodeFlag, 1)), + SPEC(xoptions, WSTR_LIST, PUBLIC, SYS_ATTR("_xoptions"), NO_GLOBAL), // --- Read-only options ----------- #ifdef Py_STATS - SPEC(_pystats, BOOL, READ_ONLY, NO_SYS), + SPEC(_pystats, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), #endif - SPEC(buffered_stdio, BOOL, READ_ONLY, NO_SYS), - SPEC(check_hash_pycs_mode, WSTR, READ_ONLY, NO_SYS), - SPEC(code_debug_ranges, BOOL, READ_ONLY, NO_SYS), - SPEC(configure_c_stdio, BOOL, READ_ONLY, NO_SYS), - SPEC(dev_mode, BOOL, READ_ONLY, NO_SYS), // sys.flags.dev_mode - SPEC(dump_refs, BOOL, READ_ONLY, NO_SYS), - SPEC(dump_refs_file, WSTR_OPT, READ_ONLY, NO_SYS), + SPEC(buffered_stdio, BOOL, READ_ONLY, NO_SYS, + GLOBAL(&Py_UnbufferedStdioFlag, 1)), + SPEC(check_hash_pycs_mode, WSTR, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(code_debug_ranges, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(configure_c_stdio, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(dev_mode, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), // sys.flags.dev_mode + SPEC(dump_refs, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(dump_refs_file, WSTR_OPT, READ_ONLY, NO_SYS, NO_GLOBAL), #ifdef Py_GIL_DISABLED - SPEC(enable_gil, INT, READ_ONLY, NO_SYS), - SPEC(tlbc_enabled, INT, READ_ONLY, NO_SYS), + SPEC(enable_gil, INT, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(tlbc_enabled, INT, READ_ONLY, NO_SYS, NO_GLOBAL), #endif - SPEC(faulthandler, BOOL, READ_ONLY, NO_SYS), - SPEC(filesystem_encoding, WSTR, READ_ONLY, NO_SYS), - SPEC(filesystem_errors, WSTR, READ_ONLY, NO_SYS), - SPEC(hash_seed, ULONG, READ_ONLY, NO_SYS), - SPEC(home, WSTR_OPT, READ_ONLY, NO_SYS), - SPEC(thread_inherit_context, INT, READ_ONLY, NO_SYS), - SPEC(context_aware_warnings, INT, READ_ONLY, NO_SYS), - SPEC(import_time, UINT, READ_ONLY, NO_SYS), - SPEC(install_signal_handlers, BOOL, READ_ONLY, NO_SYS), - SPEC(isolated, BOOL, READ_ONLY, NO_SYS), // sys.flags.isolated + SPEC(faulthandler, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(filesystem_encoding, WSTR, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(filesystem_errors, WSTR, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(hash_seed, ULONG, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(home, WSTR_OPT, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(thread_inherit_context, INT, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(context_aware_warnings, INT, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(import_time, UINT, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(install_signal_handlers, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(isolated, BOOL, READ_ONLY, NO_SYS, GLOBAL(&Py_IsolatedFlag, 0)), // sys.flags.isolated #ifdef MS_WINDOWS - SPEC(legacy_windows_stdio, BOOL, READ_ONLY, NO_SYS), + SPEC(legacy_windows_stdio, BOOL, READ_ONLY, NO_SYS, + GLOBAL(&Py_LegacyWindowsStdioFlag, 0)), #endif - SPEC(malloc_stats, BOOL, READ_ONLY, NO_SYS), - SPEC(pymalloc_hugepages, BOOL, READ_ONLY, NO_SYS), - SPEC(orig_argv, WSTR_LIST, READ_ONLY, SYS_ATTR("orig_argv")), - SPEC(parse_argv, BOOL, READ_ONLY, NO_SYS), - SPEC(pathconfig_warnings, BOOL, READ_ONLY, NO_SYS), - SPEC(perf_profiling, UINT, READ_ONLY, NO_SYS), - SPEC(remote_debug, BOOL, READ_ONLY, NO_SYS), - SPEC(program_name, WSTR, READ_ONLY, NO_SYS), - SPEC(run_command, WSTR_OPT, READ_ONLY, NO_SYS), - SPEC(run_filename, WSTR_OPT, READ_ONLY, NO_SYS), - SPEC(run_module, WSTR_OPT, READ_ONLY, NO_SYS), + SPEC(malloc_stats, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(pymalloc_hugepages, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(orig_argv, WSTR_LIST, READ_ONLY, SYS_ATTR("orig_argv"), NO_GLOBAL), + SPEC(parse_argv, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(pathconfig_warnings, BOOL, READ_ONLY, NO_SYS, + GLOBAL(&Py_FrozenFlag, 1)), + SPEC(perf_profiling, UINT, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(remote_debug, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(program_name, WSTR, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(run_command, WSTR_OPT, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(run_filename, WSTR_OPT, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(run_module, WSTR_OPT, READ_ONLY, NO_SYS, NO_GLOBAL), #ifdef Py_DEBUG - SPEC(run_presite, WSTR_OPT, READ_ONLY, NO_SYS), + SPEC(run_presite, WSTR_OPT, READ_ONLY, NO_SYS, NO_GLOBAL), #endif - SPEC(safe_path, BOOL, READ_ONLY, NO_SYS), - SPEC(show_ref_count, BOOL, READ_ONLY, NO_SYS), - SPEC(site_import, BOOL, READ_ONLY, NO_SYS), // sys.flags.no_site - SPEC(skip_source_first_line, BOOL, READ_ONLY, NO_SYS), - SPEC(stdio_encoding, WSTR, READ_ONLY, NO_SYS), - SPEC(stdio_errors, WSTR, READ_ONLY, NO_SYS), - SPEC(tracemalloc, UINT, READ_ONLY, NO_SYS), - SPEC(use_frozen_modules, BOOL, READ_ONLY, NO_SYS), - SPEC(use_hash_seed, BOOL, READ_ONLY, NO_SYS), + SPEC(safe_path, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(show_ref_count, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(site_import, BOOL, READ_ONLY, NO_SYS, GLOBAL(&Py_NoSiteFlag, 1)), // sys.flags.no_site + SPEC(skip_source_first_line, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(stdio_encoding, WSTR, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(stdio_errors, WSTR, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(tracemalloc, UINT, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(use_frozen_modules, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), + SPEC(use_hash_seed, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), #ifdef __APPLE__ - SPEC(use_system_logger, BOOL, READ_ONLY, NO_SYS), + SPEC(use_system_logger, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), #endif - SPEC(user_site_directory, BOOL, READ_ONLY, NO_SYS), // sys.flags.no_user_site - SPEC(warn_default_encoding, BOOL, READ_ONLY, NO_SYS), + SPEC(user_site_directory, BOOL, READ_ONLY, NO_SYS, + GLOBAL(&Py_NoUserSiteDirectory, 1)), // sys.flags.no_user_site + SPEC(warn_default_encoding, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL), // --- Init-only options ----------- - SPEC(_config_init, UINT, INIT_ONLY, NO_SYS), - SPEC(_init_main, BOOL, INIT_ONLY, NO_SYS), - SPEC(_install_importlib, BOOL, INIT_ONLY, NO_SYS), - SPEC(_is_python_build, BOOL, INIT_ONLY, NO_SYS), - SPEC(module_search_paths_set, BOOL, INIT_ONLY, NO_SYS), - SPEC(pythonpath_env, WSTR_OPT, INIT_ONLY, NO_SYS), - SPEC(sys_path_0, WSTR_OPT, INIT_ONLY, NO_SYS), + SPEC(_config_init, UINT, INIT_ONLY, NO_SYS, NO_GLOBAL), + SPEC(_init_main, BOOL, INIT_ONLY, NO_SYS, NO_GLOBAL), + SPEC(_install_importlib, BOOL, INIT_ONLY, NO_SYS, NO_GLOBAL), + SPEC(_is_python_build, BOOL, INIT_ONLY, NO_SYS, NO_GLOBAL), + SPEC(module_search_paths_set, BOOL, INIT_ONLY, NO_SYS, NO_GLOBAL), + SPEC(pythonpath_env, WSTR_OPT, INIT_ONLY, NO_SYS, NO_GLOBAL), + SPEC(sys_path_0, WSTR_OPT, INIT_ONLY, NO_SYS, NO_GLOBAL), // Array terminator {NULL, 0, 0, 0, NO_SYS}, @@ -233,11 +252,16 @@ static const PyConfigSpec PYPRECONFIG_SPEC[] = { {NULL, 0, 0, 0, NO_SYS}, }; +// End of ignoring deprecations on global variables +_Py_COMP_DIAG_POP + #undef SPEC #undef SYS_ATTR #undef SYS_FLAG_SETTER #undef SYS_FLAG #undef NO_SYS +#undef GLOBAL +#undef NO_GLOBAL // Forward declarations @@ -4996,6 +5020,16 @@ PyConfig_Set(const char *name, PyObject *value) Py_UNREACHABLE(); } + // Set the global variable + if (spec->global_var.ptr != NULL) { + assert(has_int_value); + int value = int_value; + if (spec->global_var.not) { + value = !value; + } + *spec->global_var.ptr = value; + } + if (spec->sys.attr != NULL) { // Set the sys attribute, but don't set PyInterpreterState.config // to keep the code simple. diff --git a/Tools/ssl/multissltests.py b/Tools/ssl/multissltests.py index 1a213187b897d1..d5e38993d971df 100755 --- a/Tools/ssl/multissltests.py +++ b/Tools/ssl/multissltests.py @@ -163,6 +163,12 @@ dest='keep_sources', help="Keep original sources for debugging." ) +parser.add_argument( + '--tsan', + action='store_true', + dest='tsan', + help="Build with thread sanitizer. (Disables fips in OpenSSL 3.x)." +) class AbstractBuilder(object): @@ -317,6 +323,8 @@ def _build_src(self, config_args=()): """Now build openssl""" log.info("Running build in {}".format(self.build_dir)) cwd = self.build_dir + if self.args.tsan: + config_args += ("-fsanitize=thread",) cmd = [ "./config", *config_args, "shared", "--debug", diff --git a/configure b/configure index 76c58f7c3a463d..363058cf3e8acf 100755 --- a/configure +++ b/configure @@ -28636,6 +28636,57 @@ then : printf "%s\n" "#define HAVE_RL_RESIZE_TERMINAL 1" >>confdefs.h +fi + + # rl_change_environment is in readline 6.3, but not in editline + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for rl_change_environment in -l$LIBREADLINE" >&5 +printf %s "checking for rl_change_environment in -l$LIBREADLINE... " >&6; } +if test ${ac_cv_readline_rl_change_environment+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include /* Must be first for Gnu Readline */ + #ifdef WITH_EDITLINE + # include + #else + # include + # include + #endif + +int +main (void) +{ +int x = rl_change_environment + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_readline_rl_change_environment=yes +else case e in #( + e) ac_cv_readline_rl_change_environment=no + ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_readline_rl_change_environment" >&5 +printf "%s\n" "$ac_cv_readline_rl_change_environment" >&6; } + if test "x$ac_cv_readline_rl_change_environment" = xyes +then : + + +printf "%s\n" "#define HAVE_RL_CHANGE_ENVIRONMENT 1" >>confdefs.h + + fi # check for readline 4.2 diff --git a/configure.ac b/configure.ac index de7a3abb379e65..0a734f9d82a1f4 100644 --- a/configure.ac +++ b/configure.ac @@ -6826,6 +6826,17 @@ AS_VAR_IF([with_readline], [no], [ AC_DEFINE([HAVE_RL_RESIZE_TERMINAL], [1], [Define if you have readline 4.0]) ]) + # rl_change_environment is in readline 6.3, but not in editline + AC_CACHE_CHECK([for rl_change_environment in -l$LIBREADLINE], [ac_cv_readline_rl_change_environment], [ + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([readline_includes], [int x = rl_change_environment])], + [ac_cv_readline_rl_change_environment=yes], [ac_cv_readline_rl_change_environment=no] + ) + ]) + AS_VAR_IF([ac_cv_readline_rl_change_environment], [yes], [ + AC_DEFINE([HAVE_RL_CHANGE_ENVIRONMENT], [1], [Define if you have readline 6.3]) + ]) + # check for readline 4.2 AC_CACHE_CHECK([for rl_completion_matches in -l$LIBREADLINE], [ac_cv_readline_rl_completion_matches], [ AC_LINK_IFELSE( diff --git a/pyconfig.h.in b/pyconfig.h.in index ce97099315bfe4..a619672c47e5b5 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -1153,6 +1153,9 @@ /* Define if you can turn off readline's signal handling. */ #undef HAVE_RL_CATCH_SIGNAL +/* Define if you have readline 6.3 */ +#undef HAVE_RL_CHANGE_ENVIRONMENT + /* Define to 1 if the system has the type 'rl_compdisp_func_t'. */ #undef HAVE_RL_COMPDISP_FUNC_T