diff --git a/Doc/library/concurrent.futures.rst b/Doc/library/concurrent.futures.rst index 8b6d749f40cbf0..e4b505c3f9761e 100644 --- a/Doc/library/concurrent.futures.rst +++ b/Doc/library/concurrent.futures.rst @@ -21,6 +21,11 @@ or separate processes, using :class:`ProcessPoolExecutor`. Each implements the same interface, which is defined by the abstract :class:`Executor` class. +:class:`concurrent.futures.Future` must not be confused with +:class:`asyncio.Future`, which is designed for use with :mod:`asyncio` +tasks and coroutines. See the :doc:`asyncio's Future ` +documentation for a detailed comparison of the two. + .. include:: ../includes/wasm-notavail.rst Executor Objects diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index e7c757405371c3..6118d7c556a617 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -2369,7 +2369,9 @@ expression support in the :mod:`re` module). If the string starts with the *prefix* string, return ``string[len(prefix):]``. Otherwise, return a copy of the original - string:: + string: + + .. doctest:: >>> 'TestHook'.removeprefix('Test') 'Hook' @@ -2378,12 +2380,16 @@ expression support in the :mod:`re` module). .. versionadded:: 3.9 + See also :meth:`removesuffix` and :meth:`startswith`. + .. method:: str.removesuffix(suffix, /) If the string ends with the *suffix* string and that *suffix* is not empty, return ``string[:-len(suffix)]``. Otherwise, return a copy of the - original string:: + original string: + + .. doctest:: >>> 'MiscTests'.removesuffix('Tests') 'Misc' @@ -2392,6 +2398,8 @@ expression support in the :mod:`re` module). .. versionadded:: 3.9 + See also :meth:`removeprefix` and :meth:`endswith`. + .. method:: str.replace(old, new, /, count=-1) @@ -2416,6 +2424,16 @@ expression support in the :mod:`re` module). Return the highest index in the string where substring *sub* is found, such that *sub* is contained within ``s[start:end]``. Optional arguments *start* and *end* are interpreted as in slice notation. Return ``-1`` on failure. + For example: + + .. doctest:: + + >>> 'spam, spam, spam'.rfind('sp') + 12 + >>> 'spam, spam, spam'.rfind('sp', 0, 10) + 6 + + See also :meth:`find` and :meth:`rindex`. .. method:: str.rindex(sub[, start[, end]]) diff --git a/Include/internal/pycore_optimizer_types.h b/Include/internal/pycore_optimizer_types.h index de8e50921e3311..0a193268c4d618 100644 --- a/Include/internal/pycore_optimizer_types.h +++ b/Include/internal/pycore_optimizer_types.h @@ -10,8 +10,8 @@ extern "C" { #include "pycore_uop.h" // UOP_MAX_TRACE_LENGTH -// Holds locals, stack, locals, stack ... co_consts (in that order) -#define MAX_ABSTRACT_INTERP_SIZE 4096 +// Holds locals, stack, locals, stack ... (in that order) +#define MAX_ABSTRACT_INTERP_SIZE 512 #define TY_ARENA_SIZE (UOP_MAX_TRACE_LENGTH * 5) diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h index 81cabb4dca47e4..262051c015ab5e 100644 --- a/Include/internal/pycore_tstate.h +++ b/Include/internal/pycore_tstate.h @@ -56,8 +56,8 @@ typedef struct _PyJitTracerState { _PyJitTracerInitialState initial_state; _PyJitTracerPreviousState prev_state; _PyJitTracerTranslatorState translator_state; - JitOptContext opt_context; - _PyUOpInstruction code_buffer[UOP_MAX_TRACE_LENGTH]; + JitOptContext *opt_context; + _PyUOpInstruction *code_buffer; } _PyJitTracerState; #endif diff --git a/Lib/_pyio.py b/Lib/_pyio.py index 69a088df8fc987..77c44addabf225 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -949,12 +949,12 @@ def read1(self, size=-1): return self.read(size) def write(self, b): - if self.closed: - raise ValueError("write to closed file") if isinstance(b, str): raise TypeError("can't write str to binary stream") with memoryview(b) as view: n = view.nbytes # Size of any bytes-like object + if self.closed: + raise ValueError("write to closed file") if n == 0: return 0 diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py index 09bfb374732e86..2e362ac1b02ac9 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -2438,6 +2438,7 @@ def test_reduce_None(self): with self.assertRaises(TypeError): self.dumps(c) + @support.skip_if_unlimited_stack_size @no_tracing def test_bad_getattr(self): # Issue #3514: crash when there is an infinite loop in __getattr__ diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 847d9074eb82cd..7bc2e1f3150035 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -45,6 +45,7 @@ "check__all__", "skip_if_buggy_ucrt_strfptime", "check_disallow_instantiation", "check_sanitizer", "skip_if_sanitizer", "requires_limited_api", "requires_specialization", "thread_unsafe", + "skip_if_unlimited_stack_size", # sys "MS_WINDOWS", "is_jython", "is_android", "is_emscripten", "is_wasi", "is_apple_mobile", "check_impl_detail", "unix_shell", "setswitchinterval", @@ -1771,6 +1772,25 @@ def skip_if_pgo_task(test): return test if ok else unittest.skip(msg)(test) +def skip_if_unlimited_stack_size(test): + """Skip decorator for tests not run when an unlimited stack size is configured. + + Tests using support.infinite_recursion([...]) may otherwise run into + an infinite loop, running until the memory on the system is filled and + crashing due to OOM. + + See https://github.com/python/cpython/issues/143460. + """ + if is_wasi or os.name == "nt": + return test + + import resource + curlim, maxlim = resource.getrlimit(resource.RLIMIT_STACK) + unlimited_stack_size_cond = curlim == maxlim and curlim in (-1, 0xFFFF_FFFF_FFFF_FFFF) + reason = "Not run due to unlimited stack size" + return unittest.skipIf(unlimited_stack_size_cond, reason)(test) + + def detect_api_mismatch(ref_api, other_api, *, ignore=()): """Returns the set of items in ref_api not in other_api, except for a defined list of items to be ignored in this check. diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py index d2b76b46dbe2eb..3917407fb37d9e 100644 --- a/Lib/test/test_ast/test_ast.py +++ b/Lib/test/test_ast/test_ast.py @@ -25,7 +25,7 @@ from test import support from test.support import os_helper -from test.support import skip_emscripten_stack_overflow, skip_wasi_stack_overflow +from test.support import skip_emscripten_stack_overflow, skip_wasi_stack_overflow, skip_if_unlimited_stack_size from test.support.ast_helper import ASTTestMixin from test.support.import_helper import ensure_lazy_imports from test.test_ast.utils import to_tuple @@ -989,6 +989,7 @@ def next(self): enum._test_simple_enum(_Precedence, _ast_unparse._Precedence) @support.cpython_only + @skip_if_unlimited_stack_size @skip_wasi_stack_overflow() @skip_emscripten_stack_overflow() def test_ast_recursion_limit(self): @@ -1127,6 +1128,7 @@ def test_pickling(self): ast2 = pickle.loads(pickle.dumps(tree, protocol)) self.assertEqual(to_tuple(ast2), to_tuple(tree)) + @skip_if_unlimited_stack_size def test_copy_with_parents(self): # gh-120108 code = """ @@ -1974,6 +1976,7 @@ def test_level_as_none(self): exec(code, ns) self.assertIn('sleep', ns) + @skip_if_unlimited_stack_size @skip_emscripten_stack_overflow() def test_recursion_direct(self): e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) @@ -1982,6 +1985,7 @@ def test_recursion_direct(self): with support.infinite_recursion(): compile(ast.Expression(e), "", "eval") + @skip_if_unlimited_stack_size @skip_emscripten_stack_overflow() def test_recursion_indirect(self): e = ast.UnaryOp(op=ast.Not(), lineno=0, col_offset=0, operand=ast.Constant(1)) diff --git a/Lib/test/test_context.py b/Lib/test/test_context.py index a08038b5dbd407..ef20495dcc01ea 100644 --- a/Lib/test/test_context.py +++ b/Lib/test/test_context.py @@ -556,6 +556,36 @@ def fun(): ctx.run(fun) + def test_context_eq_reentrant_contextvar_set(self): + var = contextvars.ContextVar("v") + ctx1 = contextvars.Context() + ctx2 = contextvars.Context() + + class ReentrantEq: + def __eq__(self, other): + ctx1.run(lambda: var.set(object())) + return True + + ctx1.run(var.set, ReentrantEq()) + ctx2.run(var.set, object()) + ctx1 == ctx2 + + def test_context_eq_reentrant_contextvar_set_in_hash(self): + var = contextvars.ContextVar("v") + ctx1 = contextvars.Context() + ctx2 = contextvars.Context() + + class ReentrantHash: + def __hash__(self): + ctx1.run(lambda: var.set(object())) + return 0 + def __eq__(self, other): + return isinstance(other, ReentrantHash) + + ctx1.run(var.set, ReentrantHash()) + ctx2.run(var.set, ReentrantHash()) + ctx1 == ctx2 + # HAMT Tests diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py index 00518abcb11b46..c03b0a09f71889 100644 --- a/Lib/test/test_float.py +++ b/Lib/test/test_float.py @@ -651,6 +651,24 @@ class F(float, H): value = F('nan') self.assertEqual(hash(value), object.__hash__(value)) + def test_issue_gh143006(self): + # When comparing negative non-integer float and int with the + # same number of bits in the integer part, __neg__() in the + # int subclass returning not an int caused an assertion error. + class EvilInt(int): + def __neg__(self): + return "" + + i = -1 << 50 + f = float(i) - 0.5 + i = EvilInt(i) + self.assertFalse(f == i) + self.assertTrue(f != i) + self.assertTrue(f < i) + self.assertTrue(f <= i) + self.assertFalse(f > i) + self.assertFalse(f >= i) + @unittest.skipUnless(hasattr(float, "__getformat__"), "requires __getformat__") class FormatFunctionsTestCase(unittest.TestCase): diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 090926fd8d8b61..459d56f82d6820 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -438,6 +438,7 @@ def test_setstate_subclasses(self): self.assertIs(type(r[0]), tuple) @support.skip_if_sanitizer("thread sanitizer crashes in __tsan::FuncEntry", thread=True) + @support.skip_if_unlimited_stack_size @support.skip_emscripten_stack_overflow() def test_recursive_pickle(self): with replaced_module('functools', self.module): @@ -2139,6 +2140,7 @@ def orig(a: int) -> nonexistent: ... @support.skip_on_s390x @unittest.skipIf(support.is_wasi, "WASI has limited C stack") @support.skip_if_sanitizer("requires deep stack", ub=True, thread=True) + @support.skip_if_unlimited_stack_size @support.skip_emscripten_stack_overflow() def test_lru_recursion(self): diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index fd9e46bf335fad..13d23af5aceb47 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -886,7 +886,7 @@ def test_created_with_capi(self): with self.assertRaisesRegex(InterpreterError, 'unrecognized'): interp.prepare_main({'spam': True}) with self.assertRaisesRegex(ExecutionFailed, 'NameError'): - self.run_from_capi(interpid, 'assert spam is True') + self.run_from_capi(interpid, 'spam') class TestInterpreterExec(TestBase): diff --git a/Lib/test/test_io/test_memoryio.py b/Lib/test/test_io/test_memoryio.py index bb023735e21398..f730e38a5d6485 100644 --- a/Lib/test/test_io/test_memoryio.py +++ b/Lib/test/test_io/test_memoryio.py @@ -587,6 +587,48 @@ def test_issue5449(self): self.ioclass(initial_bytes=buf) self.assertRaises(TypeError, self.ioclass, buf, foo=None) + def test_write_concurrent_close(self): + class B: + def __buffer__(self, flags): + memio.close() + return memoryview(b"A") + + memio = self.ioclass() + self.assertRaises(ValueError, memio.write, B()) + + # Prevent crashes when memio.write() or memio.writelines() + # concurrently mutates (e.g., closes or exports) 'memio'. + # See: https://github.com/python/cpython/issues/143378. + + def test_writelines_concurrent_close(self): + class B: + def __buffer__(self, flags): + memio.close() + return memoryview(b"A") + + memio = self.ioclass() + self.assertRaises(ValueError, memio.writelines, [B()]) + + def test_write_concurrent_export(self): + class B: + buf = None + def __buffer__(self, flags): + self.buf = memio.getbuffer() + return memoryview(b"A") + + memio = self.ioclass() + self.assertRaises(BufferError, memio.write, B()) + + def test_writelines_concurrent_export(self): + class B: + buf = None + def __buffer__(self, flags): + self.buf = memio.getbuffer() + return memoryview(b"A") + + memio = self.ioclass() + self.assertRaises(BufferError, memio.writelines, [B()]) + class TextIOTestMixin: diff --git a/Lib/test/test_isinstance.py b/Lib/test/test_isinstance.py index f440fc28ee7b7d..d97535ba46e677 100644 --- a/Lib/test/test_isinstance.py +++ b/Lib/test/test_isinstance.py @@ -317,6 +317,7 @@ def __bases__(self): self.assertRaises(RecursionError, issubclass, int, X()) self.assertRaises(RecursionError, isinstance, 1, X()) + @support.skip_if_unlimited_stack_size @support.skip_emscripten_stack_overflow() @support.skip_wasi_stack_overflow() def test_infinite_recursion_via_bases_tuple(self): @@ -328,6 +329,7 @@ def __getattr__(self, attr): with self.assertRaises(RecursionError): issubclass(Failure(), int) + @support.skip_if_unlimited_stack_size @support.skip_emscripten_stack_overflow() @support.skip_wasi_stack_overflow() def test_infinite_cycle_in_bases(self): diff --git a/Lib/test/test_json/test_recursion.py b/Lib/test/test_json/test_recursion.py index 40a0baa53f0c3b..ffd3404e6f77a0 100644 --- a/Lib/test/test_json/test_recursion.py +++ b/Lib/test/test_json/test_recursion.py @@ -68,6 +68,7 @@ def default(self, o): self.fail("didn't raise ValueError on default recursion") + @support.skip_if_unlimited_stack_size @support.skip_emscripten_stack_overflow() @support.skip_wasi_stack_overflow() def test_highly_nested_objects_decoding(self): @@ -84,6 +85,7 @@ def test_highly_nested_objects_decoding(self): with support.infinite_recursion(): self.loads('[' * very_deep + '1' + ']' * very_deep) + @support.skip_if_unlimited_stack_size @support.skip_wasi_stack_overflow() @support.skip_emscripten_stack_overflow() @support.requires_resource('cpu') @@ -99,6 +101,7 @@ def test_highly_nested_objects_encoding(self): with support.infinite_recursion(5000): self.dumps(d) + @support.skip_if_unlimited_stack_size @support.skip_emscripten_stack_overflow() @support.skip_wasi_stack_overflow() def test_endless_recursion(self): diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index 667fcc81d8e378..be7e307b4f1111 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -672,6 +672,7 @@ def test_recursive(depth, limit): """) script_helper.assert_python_ok("-c", code) + @support.skip_if_unlimited_stack_size def test_recursion(self): # Test infinite_recursion() and get_recursion_available() functions. def recursive_function(depth): diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 04018e9603ff13..1d8e908efb0572 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1350,7 +1350,7 @@ def test_disable_gil_abi(self): @test.support.cpython_only -@force_not_colorized +@test.support.force_not_colorized_test_class class UnraisableHookTest(unittest.TestCase): def test_original_unraisablehook(self): _testcapi = import_helper.import_module('_testcapi') @@ -1492,6 +1492,7 @@ def hook_func(args): def test_custom_unraisablehook_fail(self): _testcapi = import_helper.import_module('_testcapi') from _testcapi import err_writeunraisable + def hook_func(*args): raise Exception("hook_func failed") diff --git a/Lib/test/test_thread.py b/Lib/test/test_thread.py index d94e04250c9307..ac924728febc99 100644 --- a/Lib/test/test_thread.py +++ b/Lib/test/test_thread.py @@ -76,6 +76,14 @@ def test_stack_size(self): thread.stack_size(0) self.assertEqual(thread.stack_size(), 0, "stack_size not reset to default") + with self.assertRaises(ValueError): + # 123 bytes is too small + thread.stack_size(123) + + with self.assertRaises(ValueError): + # size must be positive + thread.stack_size(-4096) + @unittest.skipIf(os.name not in ("nt", "posix"), 'test meant for nt and posix') def test_nt_and_posix_stack_size(self): try: diff --git a/Lib/test/test_tomllib/test_misc.py b/Lib/test/test_tomllib/test_misc.py index 59116afa1f36ad..118fde24d88521 100644 --- a/Lib/test/test_tomllib/test_misc.py +++ b/Lib/test/test_tomllib/test_misc.py @@ -93,6 +93,7 @@ def test_deepcopy(self): } self.assertEqual(obj_copy, expected_obj) + @support.skip_if_unlimited_stack_size def test_inline_array_recursion_limit(self): with support.infinite_recursion(max_depth=100): available = support.get_recursion_available() @@ -104,6 +105,7 @@ def test_inline_array_recursion_limit(self): recursive_array_toml = "arr = " + nest_count * "[" + nest_count * "]" tomllib.loads(recursive_array_toml) + @support.skip_if_unlimited_stack_size def test_inline_table_recursion_limit(self): with support.infinite_recursion(max_depth=100): available = support.get_recursion_available() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-17-19-45-10.gh-issue-142829.ICtLXy.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-17-19-45-10.gh-issue-142829.ICtLXy.rst new file mode 100644 index 00000000000000..b85003071ac188 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-17-19-45-10.gh-issue-142829.ICtLXy.rst @@ -0,0 +1,3 @@ +Fix a use-after-free crash in :class:`contextvars.Context` comparison when a +custom ``__eq__`` method modifies the context via +:meth:`~contextvars.ContextVar.set`. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-22-22-37-53.gh-issue-143006.ZBQwbN.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-22-22-37-53.gh-issue-143006.ZBQwbN.rst new file mode 100644 index 00000000000000..f25620389fd5cd --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-22-22-37-53.gh-issue-143006.ZBQwbN.rst @@ -0,0 +1,2 @@ +Fix a possible assertion error when comparing negative non-integer ``float`` +and ``int`` with the same number of bits in the integer part. diff --git a/Misc/NEWS.d/next/Library/2026-01-03-19-41-36.gh-issue-143378.29AvE7.rst b/Misc/NEWS.d/next/Library/2026-01-03-19-41-36.gh-issue-143378.29AvE7.rst new file mode 100644 index 00000000000000..57bbb4d0a1399c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-03-19-41-36.gh-issue-143378.29AvE7.rst @@ -0,0 +1 @@ +Fix use-after-free crashes when a :class:`~io.BytesIO` object is concurrently mutated during :meth:`~io.RawIOBase.write` or :meth:`~io.IOBase.writelines`. diff --git a/Misc/NEWS.d/next/Library/2026-01-08-14-53-46.gh-issue-143547.wHBVlr.rst b/Misc/NEWS.d/next/Library/2026-01-08-14-53-46.gh-issue-143547.wHBVlr.rst new file mode 100644 index 00000000000000..934570b30b971f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-08-14-53-46.gh-issue-143547.wHBVlr.rst @@ -0,0 +1,3 @@ +Fix :func:`sys.unraisablehook` when the hook raises an exception and changes +:func:`sys.unraisablehook`: hold a strong reference to the old hook. Patch +by Victor Stinner. diff --git a/Misc/NEWS.d/next/Library/2026-01-09-13-07-22.gh-issue-143191.PPR_vW.rst b/Misc/NEWS.d/next/Library/2026-01-09-13-07-22.gh-issue-143191.PPR_vW.rst new file mode 100644 index 00000000000000..507b58362bec97 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-09-13-07-22.gh-issue-143191.PPR_vW.rst @@ -0,0 +1,2 @@ +:func:`_thread.stack_size` now raises :exc:`ValueError` if the stack size is +too small. Patch by Victor Stinner. diff --git a/Misc/NEWS.d/next/Tests/2026-01-09-13-52-10.gh-issue-143460._nW2jt.rst b/Misc/NEWS.d/next/Tests/2026-01-09-13-52-10.gh-issue-143460._nW2jt.rst new file mode 100644 index 00000000000000..b0df9917d62655 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2026-01-09-13-52-10.gh-issue-143460._nW2jt.rst @@ -0,0 +1 @@ +Skip tests relying on infinite recusion if stack size is unlimited. diff --git a/Modules/_io/bytesio.c b/Modules/_io/bytesio.c index 96611823ab6b45..d088bb0efac797 100644 --- a/Modules/_io/bytesio.c +++ b/Modules/_io/bytesio.c @@ -194,18 +194,18 @@ write_bytes_lock_held(bytesio *self, PyObject *b) { _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); - if (check_closed(self)) { - return -1; - } - if (check_exports(self)) { - return -1; - } - Py_buffer buf; + Py_ssize_t len; if (PyObject_GetBuffer(b, &buf, PyBUF_CONTIG_RO) < 0) { return -1; } - Py_ssize_t len = buf.len; + + if (check_closed(self) || check_exports(self)) { + len = -1; + goto done; + } + + len = buf.len; if (len == 0) { goto done; } diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index cc8277c5783858..73eff27343618c 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2200,9 +2200,10 @@ thread_stack_size(PyObject *self, PyObject *args) if (!PyArg_ParseTuple(args, "|n:stack_size", &new_size)) return NULL; - if (new_size < 0) { - PyErr_SetString(PyExc_ValueError, - "size must be 0 or a positive value"); + Py_ssize_t min_size = _PyOS_MIN_STACK_SIZE + SYSTEM_PAGE_SIZE; + if (new_size != 0 && new_size < min_size) { + PyErr_Format(PyExc_ValueError, + "size must be at least %zi bytes", min_size); return NULL; } diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 2cb690748d9de4..579765281ca484 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -435,82 +435,67 @@ float_richcompare(PyObject *v, PyObject *w, int op) assert(vsign != 0); /* if vsign were 0, then since wsign is * not 0, we would have taken the * vsign != wsign branch at the start */ - /* We want to work with non-negative numbers. */ - if (vsign < 0) { - /* "Multiply both sides" by -1; this also swaps the - * comparator. - */ - i = -i; - op = _Py_SwappedOp[op]; - } - assert(i > 0.0); (void) frexp(i, &exponent); /* exponent is the # of bits in v before the radix point; * we know that nbits (the # of bits in w) > 48 at this point */ if (exponent < nbits) { - i = 1.0; - j = 2.0; + j = i; + i = 0.0; goto Compare; } if (exponent > nbits) { - i = 2.0; - j = 1.0; + j = 0.0; goto Compare; } /* v and w have the same number of bits before the radix - * point. Construct two ints that have the same comparison - * outcome. + * point. Construct an int from the integer part of v and + * update op if necessary, so comparing two ints has the same outcome. */ { double fracpart; double intpart; PyObject *result = NULL; PyObject *vv = NULL; - PyObject *ww = w; - if (wsign < 0) { - ww = PyNumber_Negative(w); - if (ww == NULL) - goto Error; + fracpart = modf(i, &intpart); + if (fracpart != 0.0) { + switch (op) { + /* Non-integer float never equals to an int. */ + case Py_EQ: + Py_RETURN_FALSE; + case Py_NE: + Py_RETURN_TRUE; + /* For non-integer float, v <= w <=> v < w. + * If v > 0: trunc(v) < v < trunc(v) + 1 + * v < w => trunc(v) < w + * trunc(v) < w => trunc(v) + 1 <= w => v < w + * If v < 0: trunc(v) - 1 < v < trunc(v) + * v < w => trunc(v) - 1 < w => trunc(v) <= w + * trunc(v) <= w => v < w + */ + case Py_LT: + case Py_LE: + op = vsign > 0 ? Py_LT : Py_LE; + break; + /* The same as above, but with opposite directions. */ + case Py_GT: + case Py_GE: + op = vsign > 0 ? Py_GE : Py_GT; + break; + } } - else - Py_INCREF(ww); - fracpart = modf(i, &intpart); vv = PyLong_FromDouble(intpart); if (vv == NULL) goto Error; - if (fracpart != 0.0) { - /* Shift left, and or a 1 bit into vv - * to represent the lost fraction. - */ - PyObject *temp; - - temp = _PyLong_Lshift(ww, 1); - if (temp == NULL) - goto Error; - Py_SETREF(ww, temp); - - temp = _PyLong_Lshift(vv, 1); - if (temp == NULL) - goto Error; - Py_SETREF(vv, temp); - - temp = PyNumber_Or(vv, _PyLong_GetOne()); - if (temp == NULL) - goto Error; - Py_SETREF(vv, temp); - } - - r = PyObject_RichCompareBool(vv, ww, op); + r = PyObject_RichCompareBool(vv, w, op); if (r < 0) goto Error; result = PyBool_FromLong(r); Error: Py_XDECREF(vv); - Py_XDECREF(ww); return result; } } /* else if (PyLong_Check(w)) */ diff --git a/Python/errors.c b/Python/errors.c index 5c6ac48371a0ff..229e3a565db5cf 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1656,6 +1656,7 @@ format_unraisable_v(const char *format, va_list va, PyObject *obj) _Py_EnsureTstateNotNULL(tstate); PyObject *err_msg = NULL; + PyObject *hook = NULL; PyObject *exc_type, *exc_value, *exc_tb; _PyErr_Fetch(tstate, &exc_type, &exc_value, &exc_tb); @@ -1700,7 +1701,6 @@ format_unraisable_v(const char *format, va_list va, PyObject *obj) goto error; } - PyObject *hook; if (PySys_GetOptionalAttr(&_Py_ID(unraisablehook), &hook) < 0) { Py_DECREF(hook_args); err_msg_str = NULL; @@ -1713,7 +1713,6 @@ format_unraisable_v(const char *format, va_list va, PyObject *obj) } if (_PySys_Audit(tstate, "sys.unraisablehook", "OO", hook, hook_args) < 0) { - Py_DECREF(hook); Py_DECREF(hook_args); err_msg_str = "Exception ignored in audit hook"; obj = NULL; @@ -1721,13 +1720,11 @@ format_unraisable_v(const char *format, va_list va, PyObject *obj) } if (hook == Py_None) { - Py_DECREF(hook); Py_DECREF(hook_args); goto default_hook; } PyObject *res = PyObject_CallOneArg(hook, hook_args); - Py_DECREF(hook); Py_DECREF(hook_args); if (res != NULL) { Py_DECREF(res); @@ -1757,6 +1754,7 @@ format_unraisable_v(const char *format, va_list va, PyObject *obj) Py_XDECREF(exc_value); Py_XDECREF(exc_tb); Py_XDECREF(err_msg); + Py_XDECREF(hook); _PyErr_Clear(tstate); /* Just in case */ } diff --git a/Python/hamt.c b/Python/hamt.c index e372b1a1b4c18b..881290a0e60db8 100644 --- a/Python/hamt.c +++ b/Python/hamt.c @@ -2328,6 +2328,10 @@ _PyHamt_Eq(PyHamtObject *v, PyHamtObject *w) return 0; } + Py_INCREF(v); + Py_INCREF(w); + + int res = 1; PyHamtIteratorState iter; hamt_iter_t iter_res; hamt_find_t find_res; @@ -2343,25 +2347,38 @@ _PyHamt_Eq(PyHamtObject *v, PyHamtObject *w) find_res = hamt_find(w, v_key, &w_val); switch (find_res) { case F_ERROR: - return -1; + res = -1; + goto done; case F_NOT_FOUND: - return 0; + res = 0; + goto done; case F_FOUND: { + Py_INCREF(v_key); + Py_INCREF(v_val); + Py_INCREF(w_val); int cmp = PyObject_RichCompareBool(v_val, w_val, Py_EQ); + Py_DECREF(v_key); + Py_DECREF(v_val); + Py_DECREF(w_val); if (cmp < 0) { - return -1; + res = -1; + goto done; } if (cmp == 0) { - return 0; + res = 0; + goto done; } } } } } while (iter_res != I_END); - return 1; +done: + Py_DECREF(v); + Py_DECREF(w); + return res; } Py_ssize_t diff --git a/Python/optimizer.c b/Python/optimizer.c index 73617f6ca26425..a0d72454aa3ea5 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1025,6 +1025,13 @@ _PyJit_TryInitializeTracing( if (oparg > 0xFFFF) { return 0; } + if (_tstate->jit_tracer_state.code_buffer == NULL) { + _tstate->jit_tracer_state.code_buffer = (_PyUOpInstruction *)_PyObject_VirtualAlloc(UOP_BUFFER_SIZE); + if (_tstate->jit_tracer_state.code_buffer == NULL) { + // Don't error, just go to next instruction. + return 0; + } + } PyObject *func = PyStackRef_AsPyObjectBorrow(frame->f_funcobj); if (func == NULL) { return 0; diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 56d4f9945d6908..d7b81f07d0b86f 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -345,7 +345,15 @@ optimize_uops( assert(!PyErr_Occurred()); PyFunctionObject *func = tstate->jit_tracer_state.initial_state.func; - JitOptContext *ctx = &tstate->jit_tracer_state.opt_context; + JitOptContext *ctx = tstate->jit_tracer_state.opt_context; + if (ctx == NULL) { + ctx = (JitOptContext *)_PyObject_VirtualAlloc(sizeof(JitOptContext)); + if (ctx == NULL) { + // Don't error, just bail. + return 0; + } + tstate->jit_tracer_state.opt_context = ctx; + } uint32_t opcode = UINT16_MAX; // Make sure that watchers are set up diff --git a/Python/pystate.c b/Python/pystate.c index 74507efa5b4cf3..a186ac58abadec 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1553,6 +1553,8 @@ init_threadstate(_PyThreadStateImpl *_tstate, init_policy(&_tstate->policy.jit.side_exit_initial_backoff, "PYTHON_JIT_SIDE_EXIT_INITIAL_BACKOFF", SIDE_EXIT_INITIAL_BACKOFF, 0, MAX_BACKOFF); + _tstate->jit_tracer_state.code_buffer = NULL; + _tstate->jit_tracer_state.opt_context = NULL; #endif tstate->delete_later = NULL; @@ -1867,6 +1869,18 @@ tstate_delete_common(PyThreadState *tstate, int release_gil) assert(tstate_impl->refcounts.values == NULL); #endif +#if _Py_TIER2 + _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; + if (_tstate->jit_tracer_state.code_buffer != NULL) { + _PyObject_VirtualFree(_tstate->jit_tracer_state.code_buffer, UOP_BUFFER_SIZE); + _tstate->jit_tracer_state.code_buffer = NULL; + } + if (_tstate->jit_tracer_state.opt_context != NULL) { + _PyObject_VirtualFree(_tstate->jit_tracer_state.opt_context, sizeof(JitOptContext)); + _tstate->jit_tracer_state.opt_context = NULL; + } +#endif + HEAD_UNLOCK(runtime); // XXX Unbind in PyThreadState_Clear(), or earlier