diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index 48eb16bd90834ba..6b68c0d085589a1 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -639,7 +639,7 @@ but need extra remarks for use as slots: in the following situations: - The base is not variable-sized (its - :c:member:`~PyTypeObject.tp_itemsize`). + :c:member:`~PyTypeObject.tp_itemsize` is zero). - The requested :c:member:`PyType_Spec.basicsize` is positive, suggesting that the memory layout of the base class is known. - The requested :c:member:`PyType_Spec.basicsize` is zero, diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst index 7b61a0ed5fa6bac..60c60a9edeec49c 100644 --- a/Doc/library/curses.rst +++ b/Doc/library/curses.rst @@ -139,10 +139,13 @@ The module :mod:`!curses` defines the following functions: .. function:: color_pair(pair_number) Return the attribute value for displaying text in the specified color pair. - Only the first 256 color pairs are supported. This - attribute value can be combined with :const:`A_STANDOUT`, :const:`A_REVERSE`, - and the other :const:`!A_\*` attributes. :func:`pair_number` is the counterpart - to this function. + Only color pairs that fit in the color-pair field of the returned value can + be represented (usually the first 256); a larger *pair_number* raises + :exc:`OverflowError` rather than being silently masked to a different pair. + Use :meth:`~window.color_set` or :meth:`~window.attr_set` to display higher + pairs. This attribute value can be combined with :const:`A_STANDOUT`, + :const:`A_REVERSE`, and the other :const:`!A_\*` attributes. + :func:`pair_number` is the counterpart to this function. .. function:: curs_set(visibility) diff --git a/Doc/library/tkinter.rst b/Doc/library/tkinter.rst index 5c7227a21fc30ee..c368141126403ac 100644 --- a/Doc/library/tkinter.rst +++ b/Doc/library/tkinter.rst @@ -885,6 +885,20 @@ they are denoted in Tk, which can be useful when referring to the Tk man pages. | %d | detail | %D | delta | +----+---------------------+----+---------------------+ +The ``add`` parameter above only affects the bindings you make yourself. +Every widget also inherits *class bindings* +that implement its standard behavior -- +for example a :class:`Text` widget binds :kbd:`Control-t` +to transpose two characters. +These are described in the bindings section of the widget's Tk man page +(such as :manpage:`text(3tk)` or :manpage:`entry(3tk)`). + +Class bindings are processed separately from your own, +so binding an event yourself does not replace the default; both run. +To suppress an unwanted default binding, +bind the event on the widget +and return the string ``"break"`` from your callback. + The index parameter ^^^^^^^^^^^^^^^^^^^ diff --git a/Grammar/python.gram b/Grammar/python.gram index 9bf3a67939fcf37..a8adeb566aaf5d1 100644 --- a/Grammar/python.gram +++ b/Grammar/python.gram @@ -819,6 +819,7 @@ is_bitwise_or[CmpopExprPair*]: 'is' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, I bitwise_or[expr_ty]: | a=bitwise_or '|' b=bitwise_xor { _PyAST_BinOp(a, BitOr, b, EXTRA) } + | invalid_bitwise_or | bitwise_xor bitwise_xor[expr_ty]: @@ -827,6 +828,7 @@ bitwise_xor[expr_ty]: bitwise_and[expr_ty]: | a=bitwise_and '&' b=shift_expr { _PyAST_BinOp(a, BitAnd, b, EXTRA) } + | invalid_bitwise_and | shift_expr shift_expr[expr_ty]: @@ -1638,3 +1640,17 @@ invalid_type_params: RAISE_SYNTAX_ERROR_STARTING_FROM( token, "Type parameter list cannot be empty")} + +invalid_bitwise_and: + | a=bitwise_and b='&' c='&' { + _PyPegen_tokens_are_adjacent(b, c) + ? RAISE_SYNTAX_ERROR_KNOWN_RANGE(b, c, "invalid syntax. Maybe you meant 'and' or '&' instead of '&&'?") + : NULL + } + +invalid_bitwise_or: + | a=bitwise_or b='|' c='|' { + _PyPegen_tokens_are_adjacent(b, c) + ? RAISE_SYNTAX_ERROR_KNOWN_RANGE(b, c, "invalid syntax. Maybe you meant 'or' or '|' instead of '||'?") + : NULL + } diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index a9d97e47e005dff..f367146e262bfe8 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -143,6 +143,7 @@ struct _ts { struct _PyInterpreterFrame *base_frame; struct _PyInterpreterFrame *last_profiled_frame; + uintptr_t last_profiled_frame_seq; Py_tracefunc c_profilefunc; Py_tracefunc c_tracefunc; diff --git a/Include/internal/pycore_debug_offsets.h b/Include/internal/pycore_debug_offsets.h index 18490f98a918a73..6e1eb573c8c2c83 100644 --- a/Include/internal/pycore_debug_offsets.h +++ b/Include/internal/pycore_debug_offsets.h @@ -104,6 +104,7 @@ typedef struct _Py_DebugOffsets { uint64_t current_frame; uint64_t base_frame; uint64_t last_profiled_frame; + uint64_t last_profiled_frame_seq; uint64_t thread_id; uint64_t native_thread_id; uint64_t datastack_chunk; @@ -294,6 +295,7 @@ typedef struct _Py_DebugOffsets { .current_frame = offsetof(PyThreadState, current_frame), \ .base_frame = offsetof(PyThreadState, base_frame), \ .last_profiled_frame = offsetof(PyThreadState, last_profiled_frame), \ + .last_profiled_frame_seq = offsetof(PyThreadState, last_profiled_frame_seq), \ .thread_id = offsetof(PyThreadState, thread_id), \ .native_thread_id = offsetof(PyThreadState, native_thread_id), \ .datastack_chunk = offsetof(PyThreadState, datastack_chunk), \ diff --git a/Include/internal/pycore_interpframe.h b/Include/internal/pycore_interpframe.h index 3608a7f3bce894a..9809cd292995f0b 100644 --- a/Include/internal/pycore_interpframe.h +++ b/Include/internal/pycore_interpframe.h @@ -319,12 +319,15 @@ _PyThreadState_GetFrame(PyThreadState *tstate) // This avoids corrupting the cache when transient frames (called and returned // between profiler samples) update last_profiled_frame to addresses the // profiler never saw. +// The sequence distinguishes this anchor from a later frame that reuses the +// same _PyInterpreterFrame address. #define _PyThreadState_UpdateLastProfiledFrame(tstate, frame, previous) \ do { \ PyThreadState *tstate_ = (tstate); \ _PyInterpreterFrame *frame_ = (frame); \ if (tstate_->last_profiled_frame == frame_) { \ tstate_->last_profiled_frame = (previous); \ + tstate_->last_profiled_frame_seq++; \ } \ } while (0) diff --git a/InternalDocs/frames.md b/InternalDocs/frames.md index 60ab2055afa7b1c..475ca75e28e1a5d 100644 --- a/InternalDocs/frames.md +++ b/InternalDocs/frames.md @@ -142,22 +142,26 @@ since the frame chain may have been in an inconsistent state due to concurrent u ### Remote Profiling Frame Cache -The `last_profiled_frame` field in `PyThreadState` supports an optimization for -remote profilers that sample call stacks from external processes. When a remote -profiler reads the call stack, it writes the current frame address to this field. -The eval loop then keeps this pointer valid by updating it to the parent frame -whenever a frame returns (in `_PyEval_FrameClearAndPop`). +The `last_profiled_frame` and `last_profiled_frame_seq` fields in +`PyThreadState` support an optimization for remote profilers that sample call +stacks from external processes. When a remote profiler reads the call stack, it +writes the current frame address to `last_profiled_frame`. The eval loop then +keeps this pointer valid by updating it to the parent frame whenever a frame +returns (in `_PyEval_FrameClearAndPop`) and increments the sequence. This creates a "high-water mark" that always points to a frame still on the stack. On subsequent samples, the profiler can walk from `current_frame` until it reaches -`last_profiled_frame`, knowing that frames from that point downward are unchanged -and can be retrieved from a cache. This significantly reduces the amount of remote -memory reads needed when call stacks are deep and stable at their base. - -The update in `_PyEval_FrameClearAndPop` is guarded: it only writes when -`last_profiled_frame` is non-NULL AND matches the frame being popped. This -prevents transient frames (called and returned between profiler samples) from -corrupting the cache pointer, while avoiding any overhead when profiling is inactive. +`last_profiled_frame`, then validate the pointer and sequence before using cached +callers. This prevents a later frame that reuses the same `_PyInterpreterFrame` +address from being mistaken for the sampled frame. The cache significantly +reduces the amount of remote memory reads needed when call stacks are deep and +stable at their base. + +The update in `_PyEval_FrameClearAndPop` is guarded: it only advances the +pointer and sequence when `last_profiled_frame` is non-NULL AND matches the +frame being popped. This prevents transient frames (called and returned between +profiler samples) from corrupting the cache anchor, while avoiding any overhead +when profiling is inactive. ### The Instruction Pointer diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index db4ea8d30c7064f..c47f4e671b39def 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -272,12 +272,12 @@ def _wrap_strftime(object, format, timetuple): newformat.append(Zreplace) # Note that datetime(1000, 1, 1).strftime('%G') == '1000' so # year 1000 for %G can go on the fast path. - elif ((ch in 'YG' or ch in 'FC') and - object.year < 1000 and _need_normalize_century()): + elif (ch in 'YGFC' and timetuple[0] < 1000 and + _need_normalize_century()): if ch == 'G': year = int(_time.strftime("%G", timetuple)) else: - year = object.year + year = timetuple[0] if ch == 'C': push('{:02}'.format(year // 100)) else: diff --git a/Lib/profiling/sampling/binary_collector.py b/Lib/profiling/sampling/binary_collector.py index 64afe632fae175f..afbbc8292690678 100644 --- a/Lib/profiling/sampling/binary_collector.py +++ b/Lib/profiling/sampling/binary_collector.py @@ -94,6 +94,7 @@ def export(self, filename=None): filename: Ignored (binary files are written incrementally) """ self._writer.finalize() + return True @property def total_samples(self): diff --git a/Lib/profiling/sampling/cli.py b/Lib/profiling/sampling/cli.py index a5d9573ae6b6ddd..466b0aceae2dcc6 100644 --- a/Lib/profiling/sampling/cli.py +++ b/Lib/profiling/sampling/cli.py @@ -117,6 +117,9 @@ def __call__(self, parser, namespace, values, option_string=None): "binary": BinaryCollector, } +BROWSER_COMPATIBLE_FORMATS = ("flamegraph", "diff_flamegraph", "heatmap") + + def _setup_child_monitor(args, parent_pid): # Build CLI args for child profilers (excluding --subprocesses to avoid recursion) child_cli_args = _build_child_profiler_args(args) @@ -528,8 +531,12 @@ def _add_format_options(parser, include_compression=True, include_binary=True): output_group.add_argument( "--browser", action="store_true", - help="Automatically open HTML output (flamegraph, heatmap) in browser. " - "When using `--subprocesses`, only the main process opens the browser", + help=( + "Automatically open HTML output " + f"({', '.join('--' + f.replace('_', '-') for f in BROWSER_COMPATIBLE_FORMATS)}) " + "in browser. " + "When using `--subprocesses`, only the main process opens the browser" + ), ) @@ -789,13 +796,12 @@ def progress_callback(current, total): args.outfile or _generate_output_filename(args.format, os.getpid()) ) - collector.export(filename) + export_ok = collector.export(filename) # Auto-open browser for HTML output if --browser flag is set if ( - args.format in ( - 'flamegraph', 'diff_flamegraph', 'heatmap' - ) + export_ok + and args.format in BROWSER_COMPATIBLE_FORMATS and getattr(args, 'browser', False) ): _open_in_browser(filename) @@ -840,10 +846,14 @@ def _handle_output(collector, args, pid, mode): filename = os.path.join(args.outfile, _generate_output_filename(args.format, pid)) else: filename = args.outfile or _generate_output_filename(args.format, pid) - collector.export(filename) + export_ok = collector.export(filename) # Auto-open browser for HTML output if --browser flag is set - if args.format in ('flamegraph', 'diff_flamegraph', 'heatmap') and getattr(args, 'browser', False): + if ( + export_ok + and args.format in BROWSER_COMPATIBLE_FORMATS + and getattr(args, 'browser', False) + ): _open_in_browser(filename) @@ -875,13 +885,15 @@ def _validate_args(args, parser): if hasattr(args, 'live') and args.live: parser.error("--subprocesses is incompatible with --live mode.") - # Async-aware mode is incompatible with --native, --no-gc, --mode, and --all-threads + # Async-aware mode is incompatible with options that need thread data. if getattr(args, 'async_aware', False): issues = [] if getattr(args, 'native', False): issues.append("--native") if not getattr(args, 'gc', True): issues.append("--no-gc") + if getattr(args, 'format', None) == "binary": + issues.append("--binary") if hasattr(args, 'mode') and args.mode != "wall": issues.append(f"--mode={args.mode}") if hasattr(args, 'all_threads') and args.all_threads: diff --git a/Lib/profiling/sampling/collector.py b/Lib/profiling/sampling/collector.py index 8e0f0c44c4f8f36..1dc3656e0ebe976 100644 --- a/Lib/profiling/sampling/collector.py +++ b/Lib/profiling/sampling/collector.py @@ -163,7 +163,11 @@ def collect_failed_sample(self): @abstractmethod def export(self, filename): - """Export collected data to a file.""" + """Export collected data. + + Returns: + bool: True if output was generated, False if there was no data to export. + """ @staticmethod def _filter_internal_frames(frames): diff --git a/Lib/profiling/sampling/gecko_collector.py b/Lib/profiling/sampling/gecko_collector.py index 361f6037f216fdc..2bb5bd2f664d59f 100644 --- a/Lib/profiling/sampling/gecko_collector.py +++ b/Lib/profiling/sampling/gecko_collector.py @@ -756,6 +756,7 @@ def spin(): print( f"Open in Firefox Profiler: https://profiler.firefox.com/" ) + return True def _build_marker_schema(self): """Build marker schema definitions for Firefox Profiler.""" diff --git a/Lib/profiling/sampling/heatmap_collector.py b/Lib/profiling/sampling/heatmap_collector.py index 78f1e39f6a002d0..220b6b8150ac980 100644 --- a/Lib/profiling/sampling/heatmap_collector.py +++ b/Lib/profiling/sampling/heatmap_collector.py @@ -596,7 +596,7 @@ def export(self, output_path): """ if not self.file_samples: print("Warning: No heatmap data to export") - return + return False try: output_dir = self._prepare_output_directory(output_path) @@ -610,6 +610,7 @@ def export(self, output_path): self._generate_index_html(output_dir / 'index.html', file_stats) self._print_export_summary(output_dir, file_stats) + return True except Exception as e: print(f"Error: Failed to export heatmap: {e}") diff --git a/Lib/profiling/sampling/jsonl_collector.py b/Lib/profiling/sampling/jsonl_collector.py index 5aa42ef09024dc3..41b0456d6f56d3c 100644 --- a/Lib/profiling/sampling/jsonl_collector.py +++ b/Lib/profiling/sampling/jsonl_collector.py @@ -165,6 +165,7 @@ def export(self, filename): ) self._write_message(output, self._build_end_record()) print(f"JSONL profile written to {filename}") + return True def _build_meta_record(self): record = { diff --git a/Lib/profiling/sampling/pstats_collector.py b/Lib/profiling/sampling/pstats_collector.py index 43b1daf2a119d4e..7132cffd58f094a 100644 --- a/Lib/profiling/sampling/pstats_collector.py +++ b/Lib/profiling/sampling/pstats_collector.py @@ -63,6 +63,7 @@ def collect(self, stack_frames, timestamps_us=None): def export(self, filename): self.create_stats() self._dump_stats(filename) + return True def _dump_stats(self, file): stats_with_marker = dict(self.stats) diff --git a/Lib/profiling/sampling/stack_collector.py b/Lib/profiling/sampling/stack_collector.py index 42281dc6454c83c..eb1a3fba93cf33b 100644 --- a/Lib/profiling/sampling/stack_collector.py +++ b/Lib/profiling/sampling/stack_collector.py @@ -64,6 +64,7 @@ def export(self, filename): for stack, count in lines: f.write(f"{stack} {count}\n") print(f"Collapsed stack output written to {filename}") + return True class FlamegraphCollector(StackTraceCollector): @@ -161,7 +162,7 @@ def export(self, filename): print( "Warning: No functions found in profiling data. Check if sampling captured any data." ) - return + return False html_content = self._create_flamegraph_html(flamegraph_data) @@ -169,6 +170,7 @@ def export(self, filename): f.write(html_content) print(f"Flamegraph saved to: {filename}") + return True @staticmethod @functools.lru_cache(maxsize=None) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 28c3ab2605c45db..192b22ff7540034 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -4119,6 +4119,11 @@ def test_strftime_special(self): self.assertEqual(t.strftime('\0'*1000), '\0'*1000) self.assertEqual(t.strftime('\0%I%p%Z\0%X'), f'\0{s1}\0{s2}') self.assertEqual(t.strftime('%I%p%Z\0%X\0'), f'{s1}\0{s2}\0') + # gh-152305: the year directives must not raise on a time. + for directive, expected in (('%Y', '1900'), ('%G', '1900'), + ('%C', '19'), ('%F', '1900-01-01')): + with self.subTest(directive=directive): + self.assertEqual(t.strftime(directive), expected) def test_format(self): t = self.theclass(1, 2, 3, 4) diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 3df67c2bf0fafa0..c17f4c87705c46c 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -814,6 +814,10 @@ def test_argument_errors(self): # A character argument must be an int, a byte or a one-element string. self.assertRaises(TypeError, win.addch, []) self.assertRaises(OverflowError, win.addch, 2**64) + # The attribute argument is rejected, not truncated, when out of range. + self.assertRaises(OverflowError, win.addch, 'a', 2**64) + self.assertRaises(OverflowError, win.addstr, 'a', 2**64) + self.assertRaises(TypeError, win.addch, 'a', 'bold') # A string method rejects a non-string, non-bytes argument. self.assertRaises(TypeError, win.addstr, 5) self.assertRaises(TypeError, win.addstr) @@ -969,6 +973,11 @@ def test_attributes(self): self.assertRaises(OverflowError, win.attr_set, -1) self.assertRaises(OverflowError, win.attr_on, -1) self.assertRaises(OverflowError, win.attr_set, 1 << 64) + # attron()/attroff()/attrset() reject a bad attribute too. + self.assertRaises(OverflowError, win.attron, 1 << 64) + self.assertRaises(OverflowError, win.attroff, -1) + self.assertRaises(OverflowError, win.attrset, 1 << 64) + self.assertRaises(TypeError, win.attron, 'x') @requires_colors def test_attr_color_pair(self): @@ -1120,9 +1129,10 @@ def test_putwin(self): def test_scr_dump(self): # Test scr_dump(), scr_restore(), scr_init() and scr_set(). # scr_dump() writes the virtual screen to a named file; the other three - # functions load it back. The dumped image is internal curses state, - # not a window, so the round-trip is checked by comparing dump files - # rather than reading cells. + # load it back. The dump is opaque internal curses state -- on some + # platforms (such as macOS) it embeds raw pointers that change whenever + # the screen is reallocated -- so the round-trip is exercised + # functionally rather than by comparing dump bytes. stdscr = self.stdscr stdscr.erase() stdscr.addstr(0, 0, 'screen dump test') @@ -1131,27 +1141,14 @@ def test_scr_dump(self): dump = os.path.join(d, 'dump') self.assertIsNone(curses.scr_dump(dump)) with open(dump, 'rb') as f: - image = f.read() - self.assertTrue(image) - # The dump format embeds raw pointers on some platforms (such as - # macOS), so two dumps of the same screen are not always identical. - # Only compare dump files when the format proves deterministic. - dump2 = os.path.join(d, 'dump2') - curses.scr_dump(dump2) - with open(dump2, 'rb') as f: - deterministic = f.read() == image - # scr_restore() reloads that virtual screen, so dumping it again - # reproduces the original file even after the screen has changed. + self.assertTrue(f.read()) + # scr_restore() reloads the saved virtual screen, even after the + # screen has changed. stdscr.erase() stdscr.addstr(0, 0, 'something else') stdscr.refresh() self.assertIsNone(curses.scr_restore(dump)) - if deterministic: - restored = os.path.join(d, 'restored') - curses.scr_dump(restored) - with open(restored, 'rb') as f: - self.assertEqual(f.read(), image) - # scr_init() and scr_set() accept a dump file and return None. + # scr_init() and scr_set() also accept a dump file and return None. self.assertIsNone(curses.scr_init(dump)) self.assertIsNone(curses.scr_set(dump)) # A bytes (path-like) filename is accepted too. @@ -1729,6 +1726,11 @@ def test_color_attrs(self): self.assertEqual(curses.pair_number(attr | curses.A_BOLD), pair) self.assertEqual(curses.color_pair(0), 0) self.assertEqual(curses.pair_number(0), 0) + # A pair too large to fit is rejected, not silently masked (gh-119138). + max_pair = curses.pair_number(curses.A_COLOR) + self.assertEqual(curses.pair_number(curses.color_pair(max_pair)), max_pair) + self.assertRaises(OverflowError, curses.color_pair, max_pair + 1) + self.assertRaises(OverflowError, curses.color_pair, -1) @requires_curses_func('use_default_colors') @requires_colors diff --git a/Lib/test/test_interpreters/test_stress.py b/Lib/test/test_interpreters/test_stress.py index 50d87a6ccd3cadb..50d2444a4c72d31 100644 --- a/Lib/test/test_interpreters/test_stress.py +++ b/Lib/test/test_interpreters/test_stress.py @@ -25,6 +25,7 @@ def test_create_many_sequential(self): del alive support.gc_collect() + @threading_helper.requires_working_threading() @support.bigmemtest(size=200, memuse=32*2**20, dry_run=False) def test_create_many_threaded(self, size): alive = [] @@ -80,9 +81,12 @@ def test_create_interpreter_no_memory(self): import _testcapi assertion = self.assertRaises(InterpreterError) - _testcapi.set_nomemory(0, 1) - with assertion: - _interpreters.create() + try: + _testcapi.set_nomemory(0, 1) + with assertion: + _interpreters.create() + finally: + _testcapi.remove_mem_hooks() if __name__ == '__main__': diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_cli.py b/Lib/test/test_profiling/test_sampling_profiler/test_cli.py index 9c0734ac804e1bc..3448258eca5d6c2 100644 --- a/Lib/test/test_profiling/test_sampling_profiler/test_cli.py +++ b/Lib/test/test_profiling/test_sampling_profiler/test_cli.py @@ -7,6 +7,7 @@ import sys import tempfile import unittest +from types import SimpleNamespace from unittest import mock try: @@ -26,6 +27,7 @@ FORMAT_EXTENSIONS, _create_collector, _generate_output_filename, + _handle_output, main, ) from profiling.sampling.constants import ( @@ -727,6 +729,26 @@ def test_async_aware_flag_defaults_to_running(self): call_kwargs = mock_sample.call_args[1] self.assertEqual(call_kwargs.get("async_aware"), "running") + def test_handle_output_browser_not_opened_when_export_fails(self): + for format_type in ("flamegraph", "diff_flamegraph", "heatmap"): + with self.subTest(format=format_type): + collector = mock.MagicMock() + collector.export.return_value = False + args = SimpleNamespace( + format=format_type, + outfile="profile.html", + browser=True, + ) + + with ( + mock.patch("profiling.sampling.cli.os.path.isdir", return_value=False), + mock.patch("profiling.sampling.cli._open_in_browser") as mock_open, + ): + _handle_output(collector, args, pid=12345, mode=0) + + collector.export.assert_called_once_with("profile.html") + mock_open.assert_not_called() + def test_async_aware_with_async_mode_all(self): """Test --async-aware with --async-mode all.""" test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "--async-mode", "all"] @@ -866,6 +888,23 @@ def test_async_aware_incompatible_with_all_threads(self): self.assertIn("--all-threads", error_msg) self.assertIn("incompatible with --async-aware", error_msg) + def test_async_aware_incompatible_with_binary(self): + """Test --async-aware is incompatible with --binary.""" + test_args = ["profiling.sampling.cli", "attach", "12345", + "--async-aware", "--binary"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("sys.stderr", io.StringIO()) as mock_stderr, + self.assertRaises(SystemExit) as cm, + ): + main() + + self.assertEqual(cm.exception.code, 2) # argparse error + error_msg = mock_stderr.getvalue() + self.assertIn("--binary", error_msg) + self.assertIn("incompatible with --async-aware", error_msg) + @unittest.skipIf(is_emscripten, "subprocess not available") def test_run_nonexistent_script_exits_cleanly(self): """Test that running a non-existent script exits with a clean error.""" diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py index 1ab31af67fec522..56f3fe5e1c2605c 100644 --- a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py +++ b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py @@ -539,9 +539,10 @@ def test_flamegraph_collector_export(self): # Export flamegraph with captured_stdout(), captured_stderr(): - collector.export(flamegraph_out.name) + export_ok = collector.export(flamegraph_out.name) # Verify file was created and contains valid data + self.assertTrue(export_ok) self.assertTrue(os.path.exists(flamegraph_out.name)) self.assertGreater(os.path.getsize(flamegraph_out.name), 0) @@ -560,6 +561,21 @@ def test_flamegraph_collector_export(self): self.assertIn('"value":', content) self.assertIn('"children":', content) + def test_flamegraph_collector_empty_export_fails(self): + """Test empty flamegraph export reports no output.""" + flamegraph_out = tempfile.NamedTemporaryFile( + suffix=".html", delete=False + ) + self.addCleanup(close_and_unlink, flamegraph_out) + + collector = FlamegraphCollector(1000) + + with captured_stdout(), captured_stderr(): + export_ok = collector.export(flamegraph_out.name) + + self.assertFalse(export_ok) + self.assertEqual(os.path.getsize(flamegraph_out.name), 0) + def test_gecko_collector_basic(self): """Test basic GeckoCollector functionality.""" collector = GeckoCollector(1000) @@ -1666,8 +1682,9 @@ def test_diff_flamegraph_export(self): self.addCleanup(close_and_unlink, flamegraph_out) with captured_stdout(), captured_stderr(): - diff.export(flamegraph_out.name) + export_ok = diff.export(flamegraph_out.name) + self.assertTrue(export_ok) self.assertTrue(os.path.exists(flamegraph_out.name)) self.assertGreater(os.path.getsize(flamegraph_out.name), 0) diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index 42309dd8f6c1903..98be98a25278024 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -1057,8 +1057,7 @@ class ExternalEntityParserCreateErrorTest(unittest.TestCase): def setUpClass(cls): cls.testcapi = import_helper.import_module('_testcapi') - @unittest.skipIf(support.Py_TRACE_REFS, - 'Py_TRACE_REFS conflicts with testcapi.set_nomemory') + @support.nomemtest def test_error_path_no_crash(self): # When an allocation inside ExternalEntityParserCreate fails, # the partially-initialized subparser is deallocated. This diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index a3d485c998ac915..04e60c74ce1bea6 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -3505,6 +3505,50 @@ def test_ifexp_body_stmt_else_stmt(self): ]: self._check_error(f"x = {lhs_stmt} if 1 else {rhs_stmt}", msg) + def test_double_ampersand(self): + self._check_error( + "a && b", + r"Maybe you meant 'and' or '&' instead of '&&'\?", + lineno=1, + end_lineno=1, + offset=3, + end_offset=5, + ) + self._check_error( + "a & & b", + "invalid syntax", + lineno=1, + end_lineno=1, + offset=5, + end_offset=6, + ) + self._check_error( + "(a &\n & b)", + "invalid syntax", + lineno=2, + end_lineno=2, + offset=5, + end_offset=6, + ) + + def test_double_pipe(self): + self._check_error( + "a || b", + r"Maybe you meant 'or' or '|' instead of '||'\?", + lineno=1, + end_lineno=1, + offset=3, + end_offset=5, + ) + self._check_error( + "a | | b", + "invalid syntax", + lineno=1, + end_lineno=1, + offset=5, + end_offset=6, + ) + class LazyImportRestrictionTestCase(SyntaxErrorTestCase): """Test syntax restrictions for lazy imports.""" diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index f40da0b79aa4790..9f686e289c8f3be 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -6,6 +6,7 @@ import operator import os import random +import shutil import socket import struct import subprocess @@ -2003,7 +2004,8 @@ def tearDown(self): test.support.reap_children() def _run_remote_exec_test(self, script_code, python_args=None, env=None, - prologue='', + python_executable=None, prologue='', + after_ready=None, script_path=os_helper.TESTFN + '_remote.py'): # Create the script that will be remotely executed self.addCleanup(os_helper.unlink, script_path) @@ -2051,7 +2053,10 @@ def _run_remote_exec_test(self, script_code, python_args=None, env=None, ''') # Start the target process and capture its output - cmd = [sys.executable] + if python_executable is None: + python_executable = sys.executable + + cmd = [python_executable] if python_args: cmd.extend(python_args) cmd.append(target) @@ -2076,6 +2081,9 @@ def _run_remote_exec_test(self, script_code, python_args=None, env=None, response = client_socket.recv(1024) self.assertEqual(response, b"ready") + if after_ready is not None: + after_ready(proc) + # Try remote exec on the target process sys.remote_exec(proc.pid, script_path) @@ -2098,6 +2106,19 @@ def _run_remote_exec_test(self, script_code, python_args=None, env=None, proc.terminate() proc.wait(timeout=SHORT_TIMEOUT) + def _run_remote_exec_with_deleted_mapping(self, deleted_path, **kwargs): + def delete_loaded_mapping(proc): + os_helper.unlink(deleted_path) + with open(f'/proc/{proc.pid}/maps', encoding='utf-8') as maps: + self.assertIn(f'{deleted_path} (deleted)', maps.read()) + + script = 'print("Remote script executed successfully!")' + returncode, stdout, stderr = self._run_remote_exec_test( + script, after_ready=delete_loaded_mapping, **kwargs) + self.assertEqual(returncode, 0) + self.assertIn(b"Remote script executed successfully!", stdout) + self.assertEqual(stderr, b"") + def test_remote_exec(self): """Test basic remote exec functionality""" script = 'print("Remote script executed successfully!")' @@ -2224,6 +2245,75 @@ def test_remote_exec_invalid_script_path(self): with self.assertRaises(OSError): sys.remote_exec(os.getpid(), "invalid_script_path") + @unittest.skipUnless(sys.platform == 'linux', 'Linux-only regression test') + @unittest.skipUnless( + sysconfig.get_config_var('Py_ENABLE_SHARED') == 1, + 'requires a shared libpython build') + def test_remote_exec_deleted_libpython(self): + """Test remote exec when the target libpython was deleted.""" + build_dir = sysconfig.get_config_var('abs_builddir') + ldlibrary = sysconfig.get_config_var('LDLIBRARY') + instsoname = sysconfig.get_config_var('INSTSONAME') + if not build_dir or not ldlibrary or not instsoname: + self.skipTest('cannot determine shared libpython location') + + source_libpython = os.path.join(build_dir, instsoname) + if not os.path.exists(source_libpython): + self.skipTest(f'{source_libpython!r} does not exist') + + with os_helper.temp_dir() as lib_dir: + copied_libpython = os.path.join(lib_dir, instsoname) + shutil.copy2(source_libpython, copied_libpython) + if ldlibrary != instsoname: + os.symlink(instsoname, os.path.join(lib_dir, ldlibrary)) + + env = os.environ.copy() + ld_library_path = env.get('LD_LIBRARY_PATH') + env['LD_LIBRARY_PATH'] = lib_dir if not ld_library_path else ( + lib_dir + os.pathsep + ld_library_path) + + self._run_remote_exec_with_deleted_mapping(copied_libpython, + env=env) + + @unittest.skipUnless(sys.platform == 'linux', 'Linux-only regression test') + @unittest.skipUnless( + sysconfig.get_config_var('Py_ENABLE_SHARED') == 0, + 'requires a static Python build') + def test_remote_exec_deleted_static_executable(self): + """Test remote exec when the target static executable was deleted.""" + build_dir = sysconfig.get_config_var('abs_builddir') + srcdir = sysconfig.get_config_var('srcdir') + if not build_dir or not srcdir: + self.skipTest('cannot determine build-tree locations') + + pybuilddir_txt = os.path.join(build_dir, 'pybuilddir.txt') + if not os.path.exists(pybuilddir_txt): + self.skipTest(f'{pybuilddir_txt!r} does not exist') + + with open(pybuilddir_txt, encoding='utf-8') as pybuilddir_file: + pybuilddir = pybuilddir_file.read().strip() + source_ext_dir = os.path.join(build_dir, pybuilddir) + if not os.path.isdir(source_ext_dir): + self.skipTest(f'{source_ext_dir!r} does not exist') + + with os_helper.temp_dir() as copied_root: + copied_build_dir = os.path.join(copied_root, 'build') + copied_pybuilddir = os.path.join(copied_build_dir, pybuilddir) + os.makedirs(os.path.dirname(copied_pybuilddir)) + os.symlink(os.path.join(srcdir, 'Lib'), + os.path.join(copied_root, 'Lib')) + os.symlink(source_ext_dir, copied_pybuilddir) + shutil.copy2(pybuilddir_txt, + os.path.join(copied_build_dir, 'pybuilddir.txt')) + + copied_python = os.path.join(copied_build_dir, + os.path.basename(sys.executable)) + shutil.copy2(sys.executable, copied_python) + + self._run_remote_exec_with_deleted_mapping( + copied_python, python_args=['-S'], + python_executable=copied_python) + def test_remote_exec_in_process_without_debug_fails_envvar(self): """Test remote exec in a process without remote debugging enabled""" script = os_helper.TESTFN + '_remote.py' diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-04-15-02-35.gh-issue-146495.wWRRvx.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-04-15-02-35.gh-issue-146495.wWRRvx.rst new file mode 100644 index 000000000000000..1d86bf5307ba99d --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-04-15-02-35.gh-issue-146495.wWRRvx.rst @@ -0,0 +1 @@ +Improve :exc:`SyntaxError` message for ``&&`` and ``||`` operators, suggesting ``and``/``&`` and ``or``/``|`` respectively. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-06-20-15-08.gh-issue-151029.A33CKK.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-06-20-15-08.gh-issue-151029.A33CKK.rst new file mode 100644 index 000000000000000..cbfe5952627ad8a --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-06-20-15-08.gh-issue-151029.A33CKK.rst @@ -0,0 +1,2 @@ +On Linux, fix :func:`sys.remote_exec` unable to find remote writable memory +when ``libpython`` replaced on disk. diff --git a/Misc/NEWS.d/next/Library/2026-06-13-11-57-48.gh-issue-151436.UEDowO.rst b/Misc/NEWS.d/next/Library/2026-06-13-11-57-48.gh-issue-151436.UEDowO.rst index 1d1aadbf57be485..6b10b03ba02fdf3 100644 --- a/Misc/NEWS.d/next/Library/2026-06-13-11-57-48.gh-issue-151436.UEDowO.rst +++ b/Misc/NEWS.d/next/Library/2026-06-13-11-57-48.gh-issue-151436.UEDowO.rst @@ -1,4 +1,4 @@ -Fix skewed stack trackes in the Tachyon profiler when caching is enabled and +Fix skewed stack traces in the Tachyon profiler when caching is enabled and when generators and coroutines are profiled, by updating ``tstate->last_profiled_frame`` at every frame-removal site. The issue resulted in total erasure of some callers. Patch by Maurycy Pawłowski-Wieroński. diff --git a/Misc/NEWS.d/next/Library/2026-06-17-22-31-57.gh-issue-151613.n0nua1.rst b/Misc/NEWS.d/next/Library/2026-06-17-22-31-57.gh-issue-151613.n0nua1.rst new file mode 100644 index 000000000000000..fbf3ce47ff2739f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-17-22-31-57.gh-issue-151613.n0nua1.rst @@ -0,0 +1,3 @@ +Fix another way the Tachyon profiler frame cache could produce impossible +mixed stack traces when ``_PyInterpreterFrame`` addresses are reused, by +validating cached frame anchors with a sequence counter. diff --git a/Misc/NEWS.d/next/Library/2026-06-26-15-41-34.gh-issue-152305.WnbbBc.rst b/Misc/NEWS.d/next/Library/2026-06-26-15-41-34.gh-issue-152305.WnbbBc.rst new file mode 100644 index 000000000000000..4f27e2ed016d694 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-26-15-41-34.gh-issue-152305.WnbbBc.rst @@ -0,0 +1,2 @@ +Fix the pure-Python :meth:`datetime.time.strftime` implementation raising :exc:`AttributeError` for the +year directives. Patch by tonghuaroot. diff --git a/Misc/NEWS.d/next/Library/2026-06-26-17-18-01.gh-issue-152275.V64zKa.rst b/Misc/NEWS.d/next/Library/2026-06-26-17-18-01.gh-issue-152275.V64zKa.rst new file mode 100644 index 000000000000000..ec33586df1a7381 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-26-17-18-01.gh-issue-152275.V64zKa.rst @@ -0,0 +1,7 @@ +The :mod:`curses` module now raises :exc:`OverflowError` instead of silently +truncating an out-of-range value: :func:`curses.color_pair` rejects a color +pair number that does not fit in the ``chtype`` color field, and the +``attr`` argument of the character-cell and attribute methods +(:meth:`~curses.window.addch`, :meth:`~curses.window.addstr`, +:meth:`~curses.window.attron`, :meth:`~curses.window.attrset` and others) is +checked against the ``chtype`` range. diff --git a/Misc/NEWS.d/next/Library/2026-06-27-17-19-09.gh-issue-151126.huUyOM.rst b/Misc/NEWS.d/next/Library/2026-06-27-17-19-09.gh-issue-151126.huUyOM.rst new file mode 100644 index 000000000000000..2e51fd45b595486 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-27-17-19-09.gh-issue-151126.huUyOM.rst @@ -0,0 +1,2 @@ +Fix two crashes in :mod:`tkinter` and :mod:`socket` modules initialization +under a memory pressure. Sets missing :exc:`MemoryError`. diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index 0a77469581432fe..76e93784b94d466 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -692,7 +692,7 @@ typedef struct { obj may also be a complexchar, whose cell is used directly; it carries its own rendition, so supplying *attr* too (attr_given) is rejected. */ static int -PyCurses_ConvertToCell(PyCursesWindowObject *win, PyObject *obj, long attr, +PyCurses_ConvertToCell(PyCursesWindowObject *win, PyObject *obj, attr_t attr, int attr_given, const char *funcname, chtype *pch, cchar_t *pwc) { @@ -931,8 +931,9 @@ attr_converter(PyObject *arg, void *ptr) class attr_converter(CConverter): type = 'attr_t' converter = 'attr_converter' + c_ignored_default = '0' [python start generated code]*/ -/*[python end generated code: output=da39a3ee5e6b4b0d input=6132d3d99d3ec25a]*/ +/*[python end generated code: output=da39a3ee5e6b4b0d input=57b994c97cbd5e80]*/ #ifdef HAVE_NCURSESW /* -------------------------------------------------------*/ @@ -1835,7 +1836,7 @@ _curses.window.addch Character to add. [ - attr: long + attr: attr Attributes for the character. ] / @@ -1851,8 +1852,8 @@ current settings for the window object. static PyObject * _curses_window_addch_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *ch, int group_right_1, - long attr) -/*[clinic end generated code: output=00f4c37af3378f45 input=ab196a1dac3d354c]*/ + attr_t attr) +/*[clinic end generated code: output=3306e15a7059998f input=0a09ecdd04aa0a2d]*/ { int coordinates_group = group_left_1; int rtn; @@ -1908,7 +1909,7 @@ _curses_window_addch_impl(PyCursesWindowObject *self, int group_left_1, #endif static int -curses_wattrset(PyCursesWindowObject *self, long attr, const char *funcname) +curses_wattrset(PyCursesWindowObject *self, attr_t attr, const char *funcname) { if (wattrset(self->win, attr) == ERR) { curses_window_set_error(self, "wattrset", funcname); @@ -1931,7 +1932,7 @@ _curses.window.addstr String to add. [ - attr: long + attr: attr Attributes for characters. ] / @@ -1947,8 +1948,8 @@ current settings for the window object. static PyObject * _curses_window_addstr_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *str, int group_right_1, - long attr) -/*[clinic end generated code: output=65a928ea85ff3115 input=ff6cbb91448a22a3]*/ + attr_t attr) +/*[clinic end generated code: output=4942cdb202012076 input=0202b09895bcb472]*/ { int rtn; int strtype; @@ -2046,7 +2047,7 @@ _curses.window.addnstr Maximal number of characters. [ - attr: long + attr: attr Attributes for characters. ] / @@ -2062,8 +2063,8 @@ current settings for the window object. static PyObject * _curses_window_addnstr_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *str, int n, - int group_right_1, long attr) -/*[clinic end generated code: output=6d21cee2ce6876d9 input=72718415c2744a2a]*/ + int group_right_1, attr_t attr) +/*[clinic end generated code: output=356ce38504dabf1d input=147405505606cd08]*/ { int rtn; int strtype; @@ -2146,7 +2147,7 @@ _curses.window.bkgd ch: object Background character. [ - attr: long + attr: attr Background attributes. ] / @@ -2156,8 +2157,8 @@ Set the background property of the window. static PyObject * _curses_window_bkgd_impl(PyCursesWindowObject *self, PyObject *ch, - int group_right_1, long attr) -/*[clinic end generated code: output=73cb11ecca59612f input=a2129c1b709db432]*/ + int group_right_1, attr_t attr) +/*[clinic end generated code: output=4dc2599da3afa46a input=7aee8008ff8066a5]*/ { chtype bkgd; #ifdef HAVE_NCURSESW @@ -2183,15 +2184,15 @@ _curses_window_bkgd_impl(PyCursesWindowObject *self, PyObject *ch, /*[clinic input] _curses.window.attroff - attr: long + attr: attr / Remove attribute attr from the "background" set. [clinic start generated code]*/ static PyObject * -_curses_window_attroff_impl(PyCursesWindowObject *self, long attr) -/*[clinic end generated code: output=8a2fcd4df682fc64 input=786beedf06a7befe]*/ +_curses_window_attroff_impl(PyCursesWindowObject *self, attr_t attr) +/*[clinic end generated code: output=27c9e77df32fa5d3 input=a22d4035e962e9a7]*/ { int rtn = wattroff(self->win, (attr_t)attr); return curses_window_check_err(self, rtn, "wattroff", "attroff"); @@ -2200,15 +2201,15 @@ _curses_window_attroff_impl(PyCursesWindowObject *self, long attr) /*[clinic input] _curses.window.attron - attr: long + attr: attr / Add attribute attr to the "background" set. [clinic start generated code]*/ static PyObject * -_curses_window_attron_impl(PyCursesWindowObject *self, long attr) -/*[clinic end generated code: output=7afea43b237fa870 input=b57f824e1bf58326]*/ +_curses_window_attron_impl(PyCursesWindowObject *self, attr_t attr) +/*[clinic end generated code: output=150ff7c387068cc7 input=361b6389f4d08681]*/ { int rtn = wattron(self->win, (attr_t)attr); return curses_window_check_err(self, rtn, "wattron", "attron"); @@ -2217,15 +2218,15 @@ _curses_window_attron_impl(PyCursesWindowObject *self, long attr) /*[clinic input] _curses.window.attrset - attr: long + attr: attr / Set the "background" set of attributes. [clinic start generated code]*/ static PyObject * -_curses_window_attrset_impl(PyCursesWindowObject *self, long attr) -/*[clinic end generated code: output=84e379bff20c0433 input=42e400c0d0154ab5]*/ +_curses_window_attrset_impl(PyCursesWindowObject *self, attr_t attr) +/*[clinic end generated code: output=1b57b2a512603eb0 input=af748b1c18e35c34]*/ { int rtn = wattrset(self->win, (attr_t)attr); return curses_window_check_err(self, rtn, "wattrset", "attrset"); @@ -2356,7 +2357,7 @@ _curses.window.bkgdset ch: object Background character. [ - attr: long + attr: attr Background attributes. ] / @@ -2366,8 +2367,8 @@ Set the window's background. static PyObject * _curses_window_bkgdset_impl(PyCursesWindowObject *self, PyObject *ch, - int group_right_1, long attr) -/*[clinic end generated code: output=3c32f2de5685a482 input=1f0811b24af821ca]*/ + int group_right_1, attr_t attr) +/*[clinic end generated code: output=32f5117c9e45422a input=64cf7cd3562b379b]*/ { chtype bkgd; #ifdef HAVE_NCURSESW @@ -2756,7 +2757,7 @@ _curses.window.echochar Character to add. [ - attr: long + attr: attr Attributes for the character. ] / @@ -2766,8 +2767,8 @@ Add character ch with attribute attr, and refresh. static PyObject * _curses_window_echochar_impl(PyCursesWindowObject *self, PyObject *ch, - int group_right_1, long attr) -/*[clinic end generated code: output=f42da9e200c935e5 input=26e16855ec1b0e78]*/ + int group_right_1, attr_t attr) +/*[clinic end generated code: output=ab03afa580aa6a2a input=cd74c42aadcc7e30]*/ { chtype ch_; #ifdef HAVE_NCURSESW @@ -3194,7 +3195,7 @@ _curses.window.hline Line length. [ - attr: long + attr: attr Attributes for the characters. ] / @@ -3205,8 +3206,8 @@ Display a horizontal line. static PyObject * _curses_window_hline_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *ch, int n, - int group_right_1, long attr) -/*[clinic end generated code: output=c00d489d61fc9eef input=924f8c28521bc2ec]*/ + int group_right_1, attr_t attr) +/*[clinic end generated code: output=2c7489b8bd10c446 input=5d9f72ccba73975c]*/ { chtype ch_; #ifdef HAVE_NCURSESW @@ -3250,7 +3251,7 @@ _curses.window.insch Character to insert. [ - attr: long + attr: attr Attributes for the character. ] / @@ -3264,8 +3265,8 @@ right, with the rightmost characters on the line being lost. static PyObject * _curses_window_insch_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *ch, int group_right_1, - long attr) -/*[clinic end generated code: output=ade8cfe3a3bf3e34 input=47d2989159ae6ca7]*/ + attr_t attr) +/*[clinic end generated code: output=9d2576c0d8d982c4 input=f76641d529dbd8af]*/ { int rtn; chtype ch_ = 0; @@ -3598,7 +3599,7 @@ _curses.window.insstr String to insert. [ - attr: long + attr: attr Attributes for characters. ] / @@ -3615,8 +3616,8 @@ moving to y, x, if specified). static PyObject * _curses_window_insstr_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *str, int group_right_1, - long attr) -/*[clinic end generated code: output=c259a5265ad0b777 input=dbfbdd3892155ea6]*/ + attr_t attr) +/*[clinic end generated code: output=2c8ed843880619ab input=f4a9d26b270058c2]*/ { int rtn; int strtype; @@ -3710,7 +3711,7 @@ _curses.window.insnstr Maximal number of characters. [ - attr: long + attr: attr Attributes for characters. ] / @@ -3728,8 +3729,8 @@ does not change (after moving to y, x, if specified). static PyObject * _curses_window_insnstr_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *str, int n, - int group_right_1, long attr) -/*[clinic end generated code: output=971a32ea6328ec8b input=fd0a9b65b84b385f]*/ + int group_right_1, attr_t attr) +/*[clinic end generated code: output=4895829689f3bdd2 input=7412feb3910276bf]*/ { int rtn; int strtype; @@ -4290,7 +4291,7 @@ _curses.window.vline Line length. [ - attr: long + attr: attr Attributes for the character. ] / @@ -4301,8 +4302,8 @@ Display a vertical line. static PyObject * _curses_window_vline_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *ch, int n, - int group_right_1, long attr) -/*[clinic end generated code: output=287ad1cc8982217f input=1d4aa27ff0309bbc]*/ + int group_right_1, attr_t attr) +/*[clinic end generated code: output=18efd3ea37bb04f6 input=e8678752623197a1]*/ { chtype ch_; #ifdef HAVE_NCURSESW @@ -5272,7 +5273,20 @@ _curses_color_pair_impl(PyObject *module, int pair_number) PyCursesStatefulInitialised(module); PyCursesStatefulInitialisedColor(module); - return PyLong_FromLong(COLOR_PAIR(pair_number)); + /* COLOR_PAIR() packs the pair into a limited field; a pair too large to be + recovered by its inverse PAIR_NUMBER() would be masked to a different + one. Reject pairs that do not round-trip (this assumes only that the two + macros are inverses). color_set()/attr_set()/complexchar can still + display larger pairs. */ + chtype attr = COLOR_PAIR(pair_number); + if (pair_number < 0 || PAIR_NUMBER(attr) != pair_number) { + PyErr_Format(PyExc_OverflowError, + "color pair %d does not fit in a chtype " + "(color_pair() can encode only pairs 0 to %d)", + pair_number, (int)PAIR_NUMBER(A_COLOR)); + return NULL; + } + return PyLong_FromLong(attr); } /*[clinic input] diff --git a/Modules/_remote_debugging/_remote_debugging.h b/Modules/_remote_debugging/_remote_debugging.h index 635e6e208902af5..fa37fb7b2167ecf 100644 --- a/Modules/_remote_debugging/_remote_debugging.h +++ b/Modules/_remote_debugging/_remote_debugging.h @@ -225,9 +225,15 @@ typedef struct { #define FRAME_CACHE_MAX_THREADS 32 #define FRAME_CACHE_MAX_FRAMES 1024 +typedef struct { + uintptr_t frame; + uintptr_t seq; +} FrameCacheAnchor; + typedef struct { uint64_t thread_id; // 0 = empty slot uintptr_t thread_state_addr; + uintptr_t last_profiled_frame_seq; // sequence paired with addrs[0] uintptr_t addrs[FRAME_CACHE_MAX_FRAMES]; Py_ssize_t num_addrs; PyObject *thread_id_obj; // owned reference, NULL if empty @@ -434,7 +440,7 @@ typedef struct { uintptr_t thread_state_addr; // Owning thread state address uintptr_t base_frame_addr; // Sentinel at bottom (for validation) uintptr_t gc_frame; // GC frame address (0 if not tracking) - uintptr_t last_profiled_frame; // Last cached frame (0 if no cache) + FrameCacheAnchor last_profiled; // Last cached frame anchor StackChunkList *chunks; // Pre-copied stack chunks int skip_first_frame; // Skip frame_addr itself (continue from its caller) RemoteReadPrefetch prefetch; // Optional already-read thread/frame buffers @@ -622,15 +628,21 @@ extern void frame_cache_cleanup(RemoteUnwinderObject *unwinder); extern FrameCacheEntry *frame_cache_find(RemoteUnwinderObject *unwinder, uint64_t thread_id); extern FrameCacheEntry *frame_cache_find_by_tstate(RemoteUnwinderObject *unwinder, uintptr_t tstate_addr); extern int clear_last_profiled_frames(RemoteUnwinderObject *unwinder); +extern int set_last_profiled_frame(RemoteUnwinderObject *unwinder, uintptr_t tstate_addr, uintptr_t frame_addr); extern void frame_cache_invalidate_stale(RemoteUnwinderObject *unwinder, PyObject *result); extern int frame_cache_lookup_and_extend( RemoteUnwinderObject *unwinder, uint64_t thread_id, - uintptr_t last_profiled_frame, + uintptr_t thread_state_addr, + FrameCacheAnchor anchor, PyObject *frame_info, uintptr_t *frame_addrs, Py_ssize_t *num_addrs, Py_ssize_t max_addrs); +extern int frame_cache_anchor_matches( + RemoteUnwinderObject *unwinder, + uintptr_t thread_state_addr, + FrameCacheAnchor anchor); // Returns: 1 = stored, 0 = not stored (graceful), -1 = error // Only stores complete stacks that reach base_frame_addr extern int frame_cache_store( @@ -640,6 +652,7 @@ extern int frame_cache_store( const uintptr_t *addrs, Py_ssize_t num_addrs, uintptr_t thread_state_addr, + uintptr_t last_profiled_frame_seq, uintptr_t base_frame_addr, uintptr_t last_frame_visited); diff --git a/Modules/_remote_debugging/debug_offsets_validation.h b/Modules/_remote_debugging/debug_offsets_validation.h index f070f03ac459dcd..c0c01a0a639e196 100644 --- a/Modules/_remote_debugging/debug_offsets_validation.h +++ b/Modules/_remote_debugging/debug_offsets_validation.h @@ -31,7 +31,7 @@ #define FIELD_SIZE(type, member) sizeof(((type *)0)->member) enum { - PY_REMOTE_DEBUG_OFFSETS_TOTAL_SIZE = 880, + PY_REMOTE_DEBUG_OFFSETS_TOTAL_SIZE = 888, PY_REMOTE_ASYNC_DEBUG_OFFSETS_TOTAL_SIZE = 104, }; @@ -261,7 +261,8 @@ validate_fixed_field( APPLY(thread_state, next, sizeof(uintptr_t), _Alignof(uintptr_t), buffer_size); \ APPLY(thread_state, current_frame, sizeof(uintptr_t), _Alignof(uintptr_t), buffer_size); \ APPLY(thread_state, base_frame, sizeof(uintptr_t), _Alignof(uintptr_t), buffer_size); \ - APPLY(thread_state, last_profiled_frame, sizeof(uintptr_t), _Alignof(uintptr_t), buffer_size) + APPLY(thread_state, last_profiled_frame, sizeof(uintptr_t), _Alignof(uintptr_t), buffer_size); \ + APPLY(thread_state, last_profiled_frame_seq, sizeof(uintptr_t), _Alignof(uintptr_t), buffer_size) #define PY_REMOTE_DEBUG_INTERPRETER_STATE_FIELDS(APPLY, buffer_size) \ APPLY(interpreter_state, id, sizeof(int64_t), _Alignof(int64_t), buffer_size); \ diff --git a/Modules/_remote_debugging/frame_cache.c b/Modules/_remote_debugging/frame_cache.c index 19fc406bca9ac96..1b2d9a60f408b91 100644 --- a/Modules/_remote_debugging/frame_cache.c +++ b/Modules/_remote_debugging/frame_cache.c @@ -147,25 +147,87 @@ frame_cache_invalidate_stale(RemoteUnwinderObject *unwinder, PyObject *result) Py_CLEAR(unwinder->frame_cache[i].frame_list); unwinder->frame_cache[i].thread_id = 0; unwinder->frame_cache[i].thread_state_addr = 0; + unwinder->frame_cache[i].last_profiled_frame_seq = 0; unwinder->frame_cache[i].num_addrs = 0; STATS_INC(unwinder, stale_cache_invalidations); } } } +static int +read_last_profiled_anchor(RemoteUnwinderObject *unwinder, + uintptr_t thread_state_addr, + FrameCacheAnchor *anchor) +{ + uintptr_t frame_offset = (uintptr_t)unwinder->debug_offsets.thread_state.last_profiled_frame; + uintptr_t seq_offset = (uintptr_t)unwinder->debug_offsets.thread_state.last_profiled_frame_seq; + + // These fields are adjacent in PyThreadState. Read them together when the + // layout allows it so validation uses a pointer and sequence from the same + // remote-memory read. + if (seq_offset == frame_offset + sizeof(uintptr_t)) { + uintptr_t live_anchor[2]; + if (_Py_RemoteDebug_ReadRemoteMemory(&unwinder->handle, + thread_state_addr + frame_offset, + sizeof(live_anchor), + live_anchor) < 0) { + return -1; + } + anchor->frame = live_anchor[0]; + anchor->seq = live_anchor[1]; + return 0; + } + + if (read_ptr(unwinder, thread_state_addr + frame_offset, &anchor->frame) < 0) { + return -1; + } + return read_ptr(unwinder, thread_state_addr + seq_offset, &anchor->seq); +} + +static Py_ssize_t +find_cached_frame_addr(const FrameCacheEntry *entry, uintptr_t frame_addr, + uintptr_t *real_pops) +{ + *real_pops = 0; + for (Py_ssize_t i = 0; i < entry->num_addrs; i++) { + if (entry->addrs[i] == frame_addr) { + return i; + } + if (entry->addrs[i] != 0) { + (*real_pops)++; + } + } + return -1; +} + +int +frame_cache_anchor_matches( + RemoteUnwinderObject *unwinder, + uintptr_t thread_state_addr, + FrameCacheAnchor anchor) +{ + FrameCacheAnchor live_anchor = {0, 0}; + if (read_last_profiled_anchor(unwinder, thread_state_addr, &live_anchor) < 0) { + PyErr_Clear(); + return 0; + } + return live_anchor.frame == anchor.frame && live_anchor.seq == anchor.seq; +} + // Find last_profiled_frame in cache and extend frame_info with cached continuation // If frame_addrs is provided (not NULL), also extends it with cached addresses int frame_cache_lookup_and_extend( RemoteUnwinderObject *unwinder, uint64_t thread_id, - uintptr_t last_profiled_frame, + uintptr_t thread_state_addr, + FrameCacheAnchor anchor, PyObject *frame_info, uintptr_t *frame_addrs, Py_ssize_t *num_addrs, Py_ssize_t max_addrs) { - if (!unwinder->frame_cache || last_profiled_frame == 0) { + if (!unwinder->frame_cache || anchor.frame == 0) { return 0; } @@ -173,24 +235,31 @@ frame_cache_lookup_and_extend( if (!entry || !entry->frame_list) { return 0; } + if (entry->thread_state_addr != thread_state_addr) { + return 0; + } assert(entry->num_addrs >= 0 && entry->num_addrs <= FRAME_CACHE_MAX_FRAMES); - // Find the index where last_profiled_frame matches - Py_ssize_t start_idx = -1; - for (Py_ssize_t i = 0; i < entry->num_addrs; i++) { - if (entry->addrs[i] == last_profiled_frame) { - start_idx = i; - break; - } - } - + uintptr_t real_pops = 0; + Py_ssize_t start_idx = find_cached_frame_addr(entry, anchor.frame, &real_pops); if (start_idx < 0) { return 0; // Not found } assert(start_idx < entry->num_addrs); + // Synthetic marker frames (/) are stored as addr-0 entries but + // never increment last_profiled_frame_seq in the target (only real frame + // pops do). Count the real frames before start_idx so the sequence check is + // not thrown off by markers sitting between the leaf and the anchor. + if (entry->last_profiled_frame_seq + real_pops != anchor.seq) { + return 0; + } + Py_ssize_t num_frames = PyList_GET_SIZE(entry->frame_list); + if (start_idx >= num_frames) { + return 0; + } // Extend frame_info with frames ABOVE start_idx (not including it). // The frame at start_idx (last_profiled_frame) was the executing frame @@ -200,6 +269,9 @@ frame_cache_lookup_and_extend( if (cache_start >= num_frames) { return 0; // Nothing above last_profiled_frame to extend with } + if (!frame_cache_anchor_matches(unwinder, thread_state_addr, anchor)) { + return 0; + } PyObject *slice = PyList_GetSlice(entry->frame_list, cache_start, num_frames); if (!slice) { @@ -235,6 +307,7 @@ frame_cache_store( const uintptr_t *addrs, Py_ssize_t num_addrs, uintptr_t thread_state_addr, + uintptr_t last_profiled_frame_seq, uintptr_t base_frame_addr, uintptr_t last_frame_visited) { @@ -277,6 +350,7 @@ frame_cache_store( } entry->thread_id = thread_id; entry->thread_state_addr = thread_state_addr; + entry->last_profiled_frame_seq = last_profiled_frame_seq; if (entry->thread_id_obj == NULL) { entry->thread_id_obj = PyLong_FromUnsignedLongLong(thread_id); if (entry->thread_id_obj == NULL) { diff --git a/Modules/_remote_debugging/frames.c b/Modules/_remote_debugging/frames.c index e7d2a2764390261..46968acc6ff1feb 100644 --- a/Modules/_remote_debugging/frames.c +++ b/Modules/_remote_debugging/frames.c @@ -416,7 +416,7 @@ process_frame_chain( Py_DECREF(frame); } - if (ctx->last_profiled_frame != 0 && frame_addr == ctx->last_profiled_frame) { + if (ctx->last_profiled.frame != 0 && frame_addr == ctx->last_profiled.frame) { ctx->stopped_at_cached_frame = 1; break; } @@ -437,14 +437,23 @@ process_frame_chain( return 0; } -// Clear last_profiled_frame for all threads in the target process. -// This must be called at the start of profiling to avoid stale values -// from previous profilers causing us to stop frame walking early. +int +set_last_profiled_frame(RemoteUnwinderObject *unwinder, uintptr_t tstate_addr, + uintptr_t frame_addr) +{ + uintptr_t lpf_addr = tstate_addr + + (uintptr_t)unwinder->debug_offsets.thread_state.last_profiled_frame; + return _Py_RemoteDebug_WriteRemoteMemory(&unwinder->handle, lpf_addr, + sizeof(uintptr_t), &frame_addr); +} + +// Clear the profiler anchor frame for all threads in the target process. The +// sequence is intentionally preserved: a zero frame disables cache lookup, and +// the next profiler-owned anchor should use the target's current generation. int clear_last_profiled_frames(RemoteUnwinderObject *unwinder) { uintptr_t current_interp = unwinder->interpreter_addr; - uintptr_t zero = 0; const size_t MAX_INTERPRETERS = 256; size_t interp_count = 0; @@ -467,11 +476,8 @@ clear_last_profiled_frames(RemoteUnwinderObject *unwinder) size_t thread_count = 0; while (tstate_addr != 0 && thread_count < MAX_THREADS_PER_INTERP) { thread_count++; - // Clear last_profiled_frame - uintptr_t lpf_addr = tstate_addr + unwinder->debug_offsets.thread_state.last_profiled_frame; - if (_Py_RemoteDebug_WriteRemoteMemory(&unwinder->handle, lpf_addr, - sizeof(uintptr_t), &zero) < 0) { - // Non-fatal: just continue + uintptr_t no_frame = 0; + if (set_last_profiled_frame(unwinder, tstate_addr, no_frame) < 0) { PyErr_Clear(); } @@ -512,10 +518,10 @@ try_full_cache_hit( const FrameWalkContext *ctx, uint64_t thread_id) { - if (!unwinder->frame_cache || ctx->last_profiled_frame == 0) { + if (!unwinder->frame_cache || ctx->last_profiled.frame == 0) { return 0; } - if (ctx->frame_addr != ctx->last_profiled_frame) { + if (ctx->frame_addr != ctx->last_profiled.frame) { return 0; } @@ -523,10 +529,16 @@ try_full_cache_hit( if (!entry || !entry->frame_list) { return 0; } + if (entry->thread_state_addr != ctx->thread_state_addr) { + return 0; + } if (entry->num_addrs == 0 || entry->addrs[0] != ctx->frame_addr) { return 0; } + if (entry->last_profiled_frame_seq != ctx->last_profiled.seq) { + return 0; + } PyObject *current_frame = NULL; uintptr_t code_object_addr = 0; @@ -544,6 +556,11 @@ try_full_cache_hit( if (parse_result < 0) { return -1; } + if (!frame_cache_anchor_matches(unwinder, ctx->thread_state_addr, + ctx->last_profiled)) { + Py_XDECREF(current_frame); + return 0; + } if (current_frame != NULL) { if (PyList_Append(ctx->frame_info, current_frame) < 0) { @@ -582,9 +599,12 @@ collect_frames_with_cache( assert(ctx->chunks != NULL); + // Cache misses copy stack chunks before walking. Frames found there are + // parsed from a stable snapshot, which keeps moving stacks from seeding the + // cache with an impossible parent chain. if (ctx->chunks->count == 0) { if (copy_stack_chunks(unwinder, ctx->thread_state_addr, ctx->chunks) < 0) { - PyErr_Clear(); + return -1; } } @@ -598,7 +618,9 @@ collect_frames_with_cache( if (ctx->stopped_at_cached_frame) { Py_ssize_t frames_before_cache = PyList_GET_SIZE(ctx->frame_info); - int cache_result = frame_cache_lookup_and_extend(unwinder, thread_id, ctx->last_profiled_frame, + int cache_result = frame_cache_lookup_and_extend(unwinder, thread_id, + ctx->thread_state_addr, + ctx->last_profiled, ctx->frame_info, ctx->frame_addrs, &ctx->num_addrs, ctx->max_addrs); if (cache_result < 0) { @@ -610,7 +632,7 @@ collect_frames_with_cache( // Continue walking from last_profiled_frame, skipping it (already processed) Py_ssize_t frames_before_walk = PyList_GET_SIZE(ctx->frame_info); FrameWalkContext continue_ctx = { - .frame_addr = ctx->last_profiled_frame, + .frame_addr = ctx->last_profiled.frame, .base_frame_addr = ctx->base_frame_addr, .gc_frame = ctx->gc_frame, .chunks = ctx->chunks, @@ -634,13 +656,14 @@ collect_frames_with_cache( STATS_ADD(unwinder, frames_read_from_cache, PyList_GET_SIZE(ctx->frame_info) - frames_before_cache); } } else { - if (ctx->last_profiled_frame == 0) { + if (ctx->last_profiled.frame == 0) { STATS_INC(unwinder, frame_cache_misses); } } - if (frame_cache_store(unwinder, thread_id, ctx->frame_info, ctx->frame_addrs, ctx->num_addrs, - ctx->thread_state_addr, ctx->base_frame_addr, + if (frame_cache_store(unwinder, thread_id, ctx->frame_info, ctx->frame_addrs, + ctx->num_addrs, ctx->thread_state_addr, + ctx->last_profiled.seq, ctx->base_frame_addr, ctx->last_frame_visited) < 0) { return -1; } diff --git a/Modules/_remote_debugging/module.c b/Modules/_remote_debugging/module.c index 36115f20d9d4ccc..7979cd43c6a1274 100644 --- a/Modules/_remote_debugging/module.c +++ b/Modules/_remote_debugging/module.c @@ -475,8 +475,8 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self, return -1; } - // Clear stale last_profiled_frame values from previous profilers - // This prevents us from stopping frame walking early due to stale values + // Clear stale profiler anchors from previous profilers. This prevents us + // from stopping frame walking early due to stale frame pointers. if (cache_frames) { clear_last_profiled_frames(self); } diff --git a/Modules/_remote_debugging/threads.c b/Modules/_remote_debugging/threads.c index 81735e85395ac9e..29f22f14c9b29f8 100644 --- a/Modules/_remote_debugging/threads.c +++ b/Modules/_remote_debugging/threads.c @@ -503,7 +503,8 @@ unwind_stack_for_thread( goto error; } - // In cache mode, copying stack chunks is more expensive than direct memory reads + // Cache mode skips this for full hits, but cache misses copy chunks before + // walking so newly stored cache entries come from a stable stack snapshot. if (!unwinder->cache_frames) { if (copy_stack_chunks(unwinder, *current_tstate, &chunks) < 0) { set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to copy stack chunks"); @@ -528,18 +529,17 @@ unwind_stack_for_thread( if (unwinder->cache_frames) { // Use cache to avoid re-reading unchanged parent frames - ctx.last_profiled_frame = GET_MEMBER(uintptr_t, ts, + ctx.last_profiled.frame = GET_MEMBER(uintptr_t, ts, unwinder->debug_offsets.thread_state.last_profiled_frame); + ctx.last_profiled.seq = GET_MEMBER(uintptr_t, ts, + unwinder->debug_offsets.thread_state.last_profiled_frame_seq); if (collect_frames_with_cache(unwinder, &ctx, tid) < 0) { set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to collect frames"); goto error; } // Update last_profiled_frame for next sample if it changed - if (frame_addr != ctx.last_profiled_frame) { - uintptr_t lpf_addr = - *current_tstate + (uintptr_t)unwinder->debug_offsets.thread_state.last_profiled_frame; - if (_Py_RemoteDebug_WriteRemoteMemory(&unwinder->handle, lpf_addr, - sizeof(uintptr_t), &frame_addr) < 0) { + if (frame_addr != ctx.last_profiled.frame) { + if (set_last_profiled_frame(unwinder, *current_tstate, frame_addr) < 0) { PyErr_Clear(); // Non-fatal } } diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 137eba40a762c0a..8fa58d07096e30b 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -3574,7 +3574,7 @@ PyInit__tkinter(void) tcl_lock = PyThread_allocate_lock(); if (tcl_lock == NULL) - return NULL; + return PyErr_NoMemory(); m = PyModule_Create(&_tkintermodule); if (m == NULL) diff --git a/Modules/clinic/_cursesmodule.c.h b/Modules/clinic/_cursesmodule.c.h index a677abe6037edf1..a4a7791fa6adcd6 100644 --- a/Modules/clinic/_cursesmodule.c.h +++ b/Modules/clinic/_cursesmodule.c.h @@ -220,7 +220,7 @@ PyDoc_STRVAR(_curses_window_addch__doc__, static PyObject * _curses_window_addch_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *ch, int group_right_1, - long attr); + attr_t attr); static PyObject * _curses_window_addch(PyObject *self, PyObject *args) @@ -231,7 +231,7 @@ _curses_window_addch(PyObject *self, PyObject *args) int x = 0; PyObject *ch; int group_right_1 = 0; - long attr = 0; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -240,7 +240,7 @@ _curses_window_addch(PyObject *self, PyObject *args) } break; case 2: - if (!PyArg_ParseTuple(args, "Ol:addch", &ch, &attr)) { + if (!PyArg_ParseTuple(args, "OO&:addch", &ch, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -252,7 +252,7 @@ _curses_window_addch(PyObject *self, PyObject *args) group_left_1 = 1; break; case 4: - if (!PyArg_ParseTuple(args, "iiOl:addch", &y, &x, &ch, &attr)) { + if (!PyArg_ParseTuple(args, "iiOO&:addch", &y, &x, &ch, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -292,7 +292,7 @@ PyDoc_STRVAR(_curses_window_addstr__doc__, static PyObject * _curses_window_addstr_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *str, int group_right_1, - long attr); + attr_t attr); static PyObject * _curses_window_addstr(PyObject *self, PyObject *args) @@ -303,7 +303,7 @@ _curses_window_addstr(PyObject *self, PyObject *args) int x = 0; PyObject *str; int group_right_1 = 0; - long attr = 0; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -312,7 +312,7 @@ _curses_window_addstr(PyObject *self, PyObject *args) } break; case 2: - if (!PyArg_ParseTuple(args, "Ol:addstr", &str, &attr)) { + if (!PyArg_ParseTuple(args, "OO&:addstr", &str, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -324,7 +324,7 @@ _curses_window_addstr(PyObject *self, PyObject *args) group_left_1 = 1; break; case 4: - if (!PyArg_ParseTuple(args, "iiOl:addstr", &y, &x, &str, &attr)) { + if (!PyArg_ParseTuple(args, "iiOO&:addstr", &y, &x, &str, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -366,7 +366,7 @@ PyDoc_STRVAR(_curses_window_addnstr__doc__, static PyObject * _curses_window_addnstr_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *str, int n, - int group_right_1, long attr); + int group_right_1, attr_t attr); static PyObject * _curses_window_addnstr(PyObject *self, PyObject *args) @@ -378,7 +378,7 @@ _curses_window_addnstr(PyObject *self, PyObject *args) PyObject *str; int n; int group_right_1 = 0; - long attr = 0; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 2: @@ -387,7 +387,7 @@ _curses_window_addnstr(PyObject *self, PyObject *args) } break; case 3: - if (!PyArg_ParseTuple(args, "Oil:addnstr", &str, &n, &attr)) { + if (!PyArg_ParseTuple(args, "OiO&:addnstr", &str, &n, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -399,7 +399,7 @@ _curses_window_addnstr(PyObject *self, PyObject *args) group_left_1 = 1; break; case 5: - if (!PyArg_ParseTuple(args, "iiOil:addnstr", &y, &x, &str, &n, &attr)) { + if (!PyArg_ParseTuple(args, "iiOiO&:addnstr", &y, &x, &str, &n, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -429,7 +429,7 @@ PyDoc_STRVAR(_curses_window_bkgd__doc__, static PyObject * _curses_window_bkgd_impl(PyCursesWindowObject *self, PyObject *ch, - int group_right_1, long attr); + int group_right_1, attr_t attr); static PyObject * _curses_window_bkgd(PyObject *self, PyObject *args) @@ -437,7 +437,7 @@ _curses_window_bkgd(PyObject *self, PyObject *args) PyObject *return_value = NULL; PyObject *ch; int group_right_1 = 0; - long attr = 0; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -446,7 +446,7 @@ _curses_window_bkgd(PyObject *self, PyObject *args) } break; case 2: - if (!PyArg_ParseTuple(args, "Ol:bkgd", &ch, &attr)) { + if (!PyArg_ParseTuple(args, "OO&:bkgd", &ch, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -471,16 +471,15 @@ PyDoc_STRVAR(_curses_window_attroff__doc__, {"attroff", (PyCFunction)_curses_window_attroff, METH_O, _curses_window_attroff__doc__}, static PyObject * -_curses_window_attroff_impl(PyCursesWindowObject *self, long attr); +_curses_window_attroff_impl(PyCursesWindowObject *self, attr_t attr); static PyObject * _curses_window_attroff(PyObject *self, PyObject *arg) { PyObject *return_value = NULL; - long attr; + attr_t attr; - attr = PyLong_AsLong(arg); - if (attr == -1 && PyErr_Occurred()) { + if (!attr_converter(arg, &attr)) { goto exit; } return_value = _curses_window_attroff_impl((PyCursesWindowObject *)self, attr); @@ -499,16 +498,15 @@ PyDoc_STRVAR(_curses_window_attron__doc__, {"attron", (PyCFunction)_curses_window_attron, METH_O, _curses_window_attron__doc__}, static PyObject * -_curses_window_attron_impl(PyCursesWindowObject *self, long attr); +_curses_window_attron_impl(PyCursesWindowObject *self, attr_t attr); static PyObject * _curses_window_attron(PyObject *self, PyObject *arg) { PyObject *return_value = NULL; - long attr; + attr_t attr; - attr = PyLong_AsLong(arg); - if (attr == -1 && PyErr_Occurred()) { + if (!attr_converter(arg, &attr)) { goto exit; } return_value = _curses_window_attron_impl((PyCursesWindowObject *)self, attr); @@ -527,16 +525,15 @@ PyDoc_STRVAR(_curses_window_attrset__doc__, {"attrset", (PyCFunction)_curses_window_attrset, METH_O, _curses_window_attrset__doc__}, static PyObject * -_curses_window_attrset_impl(PyCursesWindowObject *self, long attr); +_curses_window_attrset_impl(PyCursesWindowObject *self, attr_t attr); static PyObject * _curses_window_attrset(PyObject *self, PyObject *arg) { PyObject *return_value = NULL; - long attr; + attr_t attr; - attr = PyLong_AsLong(arg); - if (attr == -1 && PyErr_Occurred()) { + if (!attr_converter(arg, &attr)) { goto exit; } return_value = _curses_window_attrset_impl((PyCursesWindowObject *)self, attr); @@ -715,7 +712,7 @@ PyDoc_STRVAR(_curses_window_bkgdset__doc__, static PyObject * _curses_window_bkgdset_impl(PyCursesWindowObject *self, PyObject *ch, - int group_right_1, long attr); + int group_right_1, attr_t attr); static PyObject * _curses_window_bkgdset(PyObject *self, PyObject *args) @@ -723,7 +720,7 @@ _curses_window_bkgdset(PyObject *self, PyObject *args) PyObject *return_value = NULL; PyObject *ch; int group_right_1 = 0; - long attr = 0; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -732,7 +729,7 @@ _curses_window_bkgdset(PyObject *self, PyObject *args) } break; case 2: - if (!PyArg_ParseTuple(args, "Ol:bkgdset", &ch, &attr)) { + if (!PyArg_ParseTuple(args, "OO&:bkgdset", &ch, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1027,7 +1024,7 @@ PyDoc_STRVAR(_curses_window_echochar__doc__, static PyObject * _curses_window_echochar_impl(PyCursesWindowObject *self, PyObject *ch, - int group_right_1, long attr); + int group_right_1, attr_t attr); static PyObject * _curses_window_echochar(PyObject *self, PyObject *args) @@ -1035,7 +1032,7 @@ _curses_window_echochar(PyObject *self, PyObject *args) PyObject *return_value = NULL; PyObject *ch; int group_right_1 = 0; - long attr = 0; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -1044,7 +1041,7 @@ _curses_window_echochar(PyObject *self, PyObject *args) } break; case 2: - if (!PyArg_ParseTuple(args, "Ol:echochar", &ch, &attr)) { + if (!PyArg_ParseTuple(args, "OO&:echochar", &ch, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1361,7 +1358,7 @@ PyDoc_STRVAR(_curses_window_hline__doc__, static PyObject * _curses_window_hline_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *ch, int n, - int group_right_1, long attr); + int group_right_1, attr_t attr); static PyObject * _curses_window_hline(PyObject *self, PyObject *args) @@ -1373,7 +1370,7 @@ _curses_window_hline(PyObject *self, PyObject *args) PyObject *ch; int n; int group_right_1 = 0; - long attr = 0; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 2: @@ -1382,7 +1379,7 @@ _curses_window_hline(PyObject *self, PyObject *args) } break; case 3: - if (!PyArg_ParseTuple(args, "Oil:hline", &ch, &n, &attr)) { + if (!PyArg_ParseTuple(args, "OiO&:hline", &ch, &n, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1394,7 +1391,7 @@ _curses_window_hline(PyObject *self, PyObject *args) group_left_1 = 1; break; case 5: - if (!PyArg_ParseTuple(args, "iiOil:hline", &y, &x, &ch, &n, &attr)) { + if (!PyArg_ParseTuple(args, "iiOiO&:hline", &y, &x, &ch, &n, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1432,7 +1429,7 @@ PyDoc_STRVAR(_curses_window_insch__doc__, static PyObject * _curses_window_insch_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *ch, int group_right_1, - long attr); + attr_t attr); static PyObject * _curses_window_insch(PyObject *self, PyObject *args) @@ -1443,7 +1440,7 @@ _curses_window_insch(PyObject *self, PyObject *args) int x = 0; PyObject *ch; int group_right_1 = 0; - long attr = 0; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -1452,7 +1449,7 @@ _curses_window_insch(PyObject *self, PyObject *args) } break; case 2: - if (!PyArg_ParseTuple(args, "Ol:insch", &ch, &attr)) { + if (!PyArg_ParseTuple(args, "OO&:insch", &ch, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1464,7 +1461,7 @@ _curses_window_insch(PyObject *self, PyObject *args) group_left_1 = 1; break; case 4: - if (!PyArg_ParseTuple(args, "iiOl:insch", &y, &x, &ch, &attr)) { + if (!PyArg_ParseTuple(args, "iiOO&:insch", &y, &x, &ch, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1551,7 +1548,7 @@ PyDoc_STRVAR(_curses_window_insstr__doc__, static PyObject * _curses_window_insstr_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *str, int group_right_1, - long attr); + attr_t attr); static PyObject * _curses_window_insstr(PyObject *self, PyObject *args) @@ -1562,7 +1559,7 @@ _curses_window_insstr(PyObject *self, PyObject *args) int x = 0; PyObject *str; int group_right_1 = 0; - long attr = 0; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -1571,7 +1568,7 @@ _curses_window_insstr(PyObject *self, PyObject *args) } break; case 2: - if (!PyArg_ParseTuple(args, "Ol:insstr", &str, &attr)) { + if (!PyArg_ParseTuple(args, "OO&:insstr", &str, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1583,7 +1580,7 @@ _curses_window_insstr(PyObject *self, PyObject *args) group_left_1 = 1; break; case 4: - if (!PyArg_ParseTuple(args, "iiOl:insstr", &y, &x, &str, &attr)) { + if (!PyArg_ParseTuple(args, "iiOO&:insstr", &y, &x, &str, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1627,7 +1624,7 @@ PyDoc_STRVAR(_curses_window_insnstr__doc__, static PyObject * _curses_window_insnstr_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *str, int n, - int group_right_1, long attr); + int group_right_1, attr_t attr); static PyObject * _curses_window_insnstr(PyObject *self, PyObject *args) @@ -1639,7 +1636,7 @@ _curses_window_insnstr(PyObject *self, PyObject *args) PyObject *str; int n; int group_right_1 = 0; - long attr = 0; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 2: @@ -1648,7 +1645,7 @@ _curses_window_insnstr(PyObject *self, PyObject *args) } break; case 3: - if (!PyArg_ParseTuple(args, "Oil:insnstr", &str, &n, &attr)) { + if (!PyArg_ParseTuple(args, "OiO&:insnstr", &str, &n, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1660,7 +1657,7 @@ _curses_window_insnstr(PyObject *self, PyObject *args) group_left_1 = 1; break; case 5: - if (!PyArg_ParseTuple(args, "iiOil:insnstr", &y, &x, &str, &n, &attr)) { + if (!PyArg_ParseTuple(args, "iiOiO&:insnstr", &y, &x, &str, &n, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -2238,7 +2235,7 @@ PyDoc_STRVAR(_curses_window_vline__doc__, static PyObject * _curses_window_vline_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *ch, int n, - int group_right_1, long attr); + int group_right_1, attr_t attr); static PyObject * _curses_window_vline(PyObject *self, PyObject *args) @@ -2250,7 +2247,7 @@ _curses_window_vline(PyObject *self, PyObject *args) PyObject *ch; int n; int group_right_1 = 0; - long attr = 0; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 2: @@ -2259,7 +2256,7 @@ _curses_window_vline(PyObject *self, PyObject *args) } break; case 3: - if (!PyArg_ParseTuple(args, "Oil:vline", &ch, &n, &attr)) { + if (!PyArg_ParseTuple(args, "OiO&:vline", &ch, &n, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -2271,7 +2268,7 @@ _curses_window_vline(PyObject *self, PyObject *args) group_left_1 = 1; break; case 5: - if (!PyArg_ParseTuple(args, "iiOil:vline", &y, &x, &ch, &n, &attr)) { + if (!PyArg_ParseTuple(args, "iiOiO&:vline", &y, &x, &ch, &n, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -5648,4 +5645,4 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored #ifndef _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF #define _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF #endif /* !defined(_CURSES_ASSUME_DEFAULT_COLORS_METHODDEF) */ -/*[clinic end generated code: output=db4cb7f72e1dc166 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=01cb1ecb396881c9 input=a9049054013a1b77]*/ diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 3e82af3194d053a..ada4fda6571049d 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -9296,6 +9296,7 @@ socket_exec(PyObject *m) #if defined(USE_GETHOSTBYNAME_LOCK) netdb_lock = PyThread_allocate_lock(); if (netdb_lock == NULL) { + PyErr_NoMemory(); goto error; } #endif diff --git a/Parser/parser.c b/Parser/parser.c index c55c081dfc3d8e2..58b6dd77a38b26d 100644 --- a/Parser/parser.c +++ b/Parser/parser.c @@ -362,186 +362,188 @@ static char *soft_keywords[] = { #define invalid_arithmetic_type 1273 #define invalid_factor_type 1274 #define invalid_type_params_type 1275 -#define _loop0_1_type 1276 -#define _loop1_2_type 1277 -#define _loop0_3_type 1278 -#define _gather_4_type 1279 -#define _tmp_5_type 1280 -#define _tmp_6_type 1281 -#define _tmp_7_type 1282 -#define _tmp_8_type 1283 -#define _tmp_9_type 1284 -#define _tmp_10_type 1285 -#define _tmp_11_type 1286 -#define _loop1_12_type 1287 -#define _loop0_13_type 1288 -#define _gather_14_type 1289 -#define _tmp_15_type 1290 -#define _tmp_16_type 1291 -#define _loop0_17_type 1292 -#define _loop1_18_type 1293 -#define _loop0_19_type 1294 -#define _gather_20_type 1295 -#define _tmp_21_type 1296 -#define _loop0_22_type 1297 -#define _gather_23_type 1298 -#define _loop1_24_type 1299 -#define _tmp_25_type 1300 -#define _tmp_26_type 1301 -#define _loop0_27_type 1302 -#define _loop0_28_type 1303 -#define _loop1_29_type 1304 -#define _loop1_30_type 1305 -#define _loop0_31_type 1306 -#define _loop1_32_type 1307 -#define _loop0_33_type 1308 -#define _gather_34_type 1309 -#define _tmp_35_type 1310 -#define _loop1_36_type 1311 -#define _loop1_37_type 1312 -#define _loop1_38_type 1313 -#define _loop0_39_type 1314 -#define _gather_40_type 1315 -#define _tmp_41_type 1316 -#define _tmp_42_type 1317 -#define _tmp_43_type 1318 -#define _loop0_44_type 1319 -#define _gather_45_type 1320 -#define _loop0_46_type 1321 -#define _gather_47_type 1322 -#define _tmp_48_type 1323 -#define _loop0_49_type 1324 -#define _gather_50_type 1325 -#define _loop0_51_type 1326 -#define _gather_52_type 1327 -#define _loop0_53_type 1328 -#define _gather_54_type 1329 -#define _loop1_55_type 1330 -#define _loop1_56_type 1331 -#define _loop0_57_type 1332 -#define _gather_58_type 1333 -#define _loop0_59_type 1334 -#define _gather_60_type 1335 -#define _loop1_61_type 1336 -#define _loop1_62_type 1337 -#define _loop1_63_type 1338 -#define _tmp_64_type 1339 -#define _loop0_65_type 1340 -#define _gather_66_type 1341 -#define _tmp_67_type 1342 -#define _tmp_68_type 1343 -#define _tmp_69_type 1344 -#define _tmp_70_type 1345 -#define _tmp_71_type 1346 -#define _loop0_72_type 1347 -#define _loop0_73_type 1348 -#define _loop1_74_type 1349 -#define _loop1_75_type 1350 -#define _loop0_76_type 1351 -#define _loop1_77_type 1352 -#define _loop0_78_type 1353 -#define _loop0_79_type 1354 -#define _loop0_80_type 1355 -#define _loop0_81_type 1356 -#define _loop1_82_type 1357 -#define _loop1_83_type 1358 -#define _tmp_84_type 1359 -#define _loop0_85_type 1360 -#define _gather_86_type 1361 -#define _loop1_87_type 1362 -#define _loop0_88_type 1363 -#define _tmp_89_type 1364 -#define _loop0_90_type 1365 -#define _gather_91_type 1366 -#define _tmp_92_type 1367 -#define _loop0_93_type 1368 -#define _gather_94_type 1369 -#define _loop0_95_type 1370 -#define _gather_96_type 1371 -#define _loop0_97_type 1372 -#define _loop0_98_type 1373 -#define _gather_99_type 1374 -#define _loop1_100_type 1375 -#define _tmp_101_type 1376 -#define _loop0_102_type 1377 -#define _gather_103_type 1378 -#define _loop0_104_type 1379 -#define _gather_105_type 1380 -#define _tmp_106_type 1381 -#define _tmp_107_type 1382 -#define _loop0_108_type 1383 -#define _gather_109_type 1384 -#define _tmp_110_type 1385 -#define _tmp_111_type 1386 -#define _tmp_112_type 1387 -#define _tmp_113_type 1388 -#define _tmp_114_type 1389 -#define _loop1_115_type 1390 -#define _tmp_116_type 1391 -#define _tmp_117_type 1392 -#define _tmp_118_type 1393 -#define _tmp_119_type 1394 -#define _tmp_120_type 1395 -#define _loop0_121_type 1396 -#define _loop0_122_type 1397 -#define _tmp_123_type 1398 -#define _tmp_124_type 1399 -#define _tmp_125_type 1400 -#define _tmp_126_type 1401 -#define _tmp_127_type 1402 -#define _tmp_128_type 1403 -#define _tmp_129_type 1404 -#define _tmp_130_type 1405 -#define _loop0_131_type 1406 -#define _gather_132_type 1407 -#define _tmp_133_type 1408 -#define _tmp_134_type 1409 -#define _tmp_135_type 1410 -#define _tmp_136_type 1411 -#define _loop0_137_type 1412 -#define _gather_138_type 1413 -#define _tmp_139_type 1414 -#define _loop0_140_type 1415 -#define _gather_141_type 1416 -#define _loop0_142_type 1417 -#define _gather_143_type 1418 -#define _tmp_144_type 1419 -#define _loop0_145_type 1420 -#define _tmp_146_type 1421 -#define _tmp_147_type 1422 -#define _tmp_148_type 1423 -#define _tmp_149_type 1424 -#define _tmp_150_type 1425 -#define _tmp_151_type 1426 -#define _tmp_152_type 1427 -#define _tmp_153_type 1428 -#define _tmp_154_type 1429 -#define _tmp_155_type 1430 -#define _tmp_156_type 1431 -#define _tmp_157_type 1432 -#define _tmp_158_type 1433 -#define _tmp_159_type 1434 -#define _tmp_160_type 1435 -#define _tmp_161_type 1436 -#define _tmp_162_type 1437 -#define _tmp_163_type 1438 -#define _tmp_164_type 1439 -#define _tmp_165_type 1440 -#define _tmp_166_type 1441 -#define _tmp_167_type 1442 -#define _tmp_168_type 1443 -#define _tmp_169_type 1444 -#define _tmp_170_type 1445 -#define _tmp_171_type 1446 -#define _tmp_172_type 1447 -#define _tmp_173_type 1448 -#define _loop0_174_type 1449 -#define _tmp_175_type 1450 -#define _tmp_176_type 1451 -#define _tmp_177_type 1452 -#define _tmp_178_type 1453 -#define _tmp_179_type 1454 -#define _tmp_180_type 1455 +#define invalid_bitwise_and_type 1276 // Left-recursive +#define invalid_bitwise_or_type 1277 // Left-recursive +#define _loop0_1_type 1278 +#define _loop1_2_type 1279 +#define _loop0_3_type 1280 +#define _gather_4_type 1281 +#define _tmp_5_type 1282 +#define _tmp_6_type 1283 +#define _tmp_7_type 1284 +#define _tmp_8_type 1285 +#define _tmp_9_type 1286 +#define _tmp_10_type 1287 +#define _tmp_11_type 1288 +#define _loop1_12_type 1289 +#define _loop0_13_type 1290 +#define _gather_14_type 1291 +#define _tmp_15_type 1292 +#define _tmp_16_type 1293 +#define _loop0_17_type 1294 +#define _loop1_18_type 1295 +#define _loop0_19_type 1296 +#define _gather_20_type 1297 +#define _tmp_21_type 1298 +#define _loop0_22_type 1299 +#define _gather_23_type 1300 +#define _loop1_24_type 1301 +#define _tmp_25_type 1302 +#define _tmp_26_type 1303 +#define _loop0_27_type 1304 +#define _loop0_28_type 1305 +#define _loop1_29_type 1306 +#define _loop1_30_type 1307 +#define _loop0_31_type 1308 +#define _loop1_32_type 1309 +#define _loop0_33_type 1310 +#define _gather_34_type 1311 +#define _tmp_35_type 1312 +#define _loop1_36_type 1313 +#define _loop1_37_type 1314 +#define _loop1_38_type 1315 +#define _loop0_39_type 1316 +#define _gather_40_type 1317 +#define _tmp_41_type 1318 +#define _tmp_42_type 1319 +#define _tmp_43_type 1320 +#define _loop0_44_type 1321 +#define _gather_45_type 1322 +#define _loop0_46_type 1323 +#define _gather_47_type 1324 +#define _tmp_48_type 1325 +#define _loop0_49_type 1326 +#define _gather_50_type 1327 +#define _loop0_51_type 1328 +#define _gather_52_type 1329 +#define _loop0_53_type 1330 +#define _gather_54_type 1331 +#define _loop1_55_type 1332 +#define _loop1_56_type 1333 +#define _loop0_57_type 1334 +#define _gather_58_type 1335 +#define _loop0_59_type 1336 +#define _gather_60_type 1337 +#define _loop1_61_type 1338 +#define _loop1_62_type 1339 +#define _loop1_63_type 1340 +#define _tmp_64_type 1341 +#define _loop0_65_type 1342 +#define _gather_66_type 1343 +#define _tmp_67_type 1344 +#define _tmp_68_type 1345 +#define _tmp_69_type 1346 +#define _tmp_70_type 1347 +#define _tmp_71_type 1348 +#define _loop0_72_type 1349 +#define _loop0_73_type 1350 +#define _loop1_74_type 1351 +#define _loop1_75_type 1352 +#define _loop0_76_type 1353 +#define _loop1_77_type 1354 +#define _loop0_78_type 1355 +#define _loop0_79_type 1356 +#define _loop0_80_type 1357 +#define _loop0_81_type 1358 +#define _loop1_82_type 1359 +#define _loop1_83_type 1360 +#define _tmp_84_type 1361 +#define _loop0_85_type 1362 +#define _gather_86_type 1363 +#define _loop1_87_type 1364 +#define _loop0_88_type 1365 +#define _tmp_89_type 1366 +#define _loop0_90_type 1367 +#define _gather_91_type 1368 +#define _tmp_92_type 1369 +#define _loop0_93_type 1370 +#define _gather_94_type 1371 +#define _loop0_95_type 1372 +#define _gather_96_type 1373 +#define _loop0_97_type 1374 +#define _loop0_98_type 1375 +#define _gather_99_type 1376 +#define _loop1_100_type 1377 +#define _tmp_101_type 1378 +#define _loop0_102_type 1379 +#define _gather_103_type 1380 +#define _loop0_104_type 1381 +#define _gather_105_type 1382 +#define _tmp_106_type 1383 +#define _tmp_107_type 1384 +#define _loop0_108_type 1385 +#define _gather_109_type 1386 +#define _tmp_110_type 1387 +#define _tmp_111_type 1388 +#define _tmp_112_type 1389 +#define _tmp_113_type 1390 +#define _tmp_114_type 1391 +#define _loop1_115_type 1392 +#define _tmp_116_type 1393 +#define _tmp_117_type 1394 +#define _tmp_118_type 1395 +#define _tmp_119_type 1396 +#define _tmp_120_type 1397 +#define _loop0_121_type 1398 +#define _loop0_122_type 1399 +#define _tmp_123_type 1400 +#define _tmp_124_type 1401 +#define _tmp_125_type 1402 +#define _tmp_126_type 1403 +#define _tmp_127_type 1404 +#define _tmp_128_type 1405 +#define _tmp_129_type 1406 +#define _tmp_130_type 1407 +#define _loop0_131_type 1408 +#define _gather_132_type 1409 +#define _tmp_133_type 1410 +#define _tmp_134_type 1411 +#define _tmp_135_type 1412 +#define _tmp_136_type 1413 +#define _loop0_137_type 1414 +#define _gather_138_type 1415 +#define _tmp_139_type 1416 +#define _loop0_140_type 1417 +#define _gather_141_type 1418 +#define _loop0_142_type 1419 +#define _gather_143_type 1420 +#define _tmp_144_type 1421 +#define _loop0_145_type 1422 +#define _tmp_146_type 1423 +#define _tmp_147_type 1424 +#define _tmp_148_type 1425 +#define _tmp_149_type 1426 +#define _tmp_150_type 1427 +#define _tmp_151_type 1428 +#define _tmp_152_type 1429 +#define _tmp_153_type 1430 +#define _tmp_154_type 1431 +#define _tmp_155_type 1432 +#define _tmp_156_type 1433 +#define _tmp_157_type 1434 +#define _tmp_158_type 1435 +#define _tmp_159_type 1436 +#define _tmp_160_type 1437 +#define _tmp_161_type 1438 +#define _tmp_162_type 1439 +#define _tmp_163_type 1440 +#define _tmp_164_type 1441 +#define _tmp_165_type 1442 +#define _tmp_166_type 1443 +#define _tmp_167_type 1444 +#define _tmp_168_type 1445 +#define _tmp_169_type 1446 +#define _tmp_170_type 1447 +#define _tmp_171_type 1448 +#define _tmp_172_type 1449 +#define _tmp_173_type 1450 +#define _loop0_174_type 1451 +#define _tmp_175_type 1452 +#define _tmp_176_type 1453 +#define _tmp_177_type 1454 +#define _tmp_178_type 1455 +#define _tmp_179_type 1456 +#define _tmp_180_type 1457 static mod_ty file_rule(Parser *p); static mod_ty interactive_rule(Parser *p); @@ -819,6 +821,8 @@ static void *invalid_string_tstring_concat_rule(Parser *p); static void *invalid_arithmetic_rule(Parser *p); static void *invalid_factor_rule(Parser *p); static void *invalid_type_params_rule(Parser *p); +static void *invalid_bitwise_and_rule(Parser *p); +static void *invalid_bitwise_or_rule(Parser *p); static asdl_seq *_loop0_1_rule(Parser *p); static asdl_seq *_loop1_2_rule(Parser *p); static asdl_seq *_loop0_3_rule(Parser *p); @@ -13503,7 +13507,7 @@ is_bitwise_or_rule(Parser *p) } // Left-recursive -// bitwise_or: bitwise_or '|' bitwise_xor | bitwise_xor +// bitwise_or: bitwise_or '|' bitwise_xor | invalid_bitwise_or | bitwise_xor static expr_ty bitwise_or_raw(Parser *); static expr_ty bitwise_or_rule(Parser *p) @@ -13599,6 +13603,25 @@ bitwise_or_raw(Parser *p) D(fprintf(stderr, "%*c%s bitwise_or[%d-%d]: %s failed!\n", p->level, ' ', p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "bitwise_or '|' bitwise_xor")); } + if (p->call_invalid_rules) { // invalid_bitwise_or + if (p->error_indicator) { + p->level--; + return NULL; + } + D(fprintf(stderr, "%*c> bitwise_or[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "invalid_bitwise_or")); + void *invalid_bitwise_or_var; + if ( + (invalid_bitwise_or_var = invalid_bitwise_or_rule(p)) // invalid_bitwise_or + ) + { + D(fprintf(stderr, "%*c+ bitwise_or[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "invalid_bitwise_or")); + _res = invalid_bitwise_or_var; + goto done; + } + p->mark = _mark; + D(fprintf(stderr, "%*c%s bitwise_or[%d-%d]: %s failed!\n", p->level, ' ', + p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "invalid_bitwise_or")); + } { // bitwise_xor if (p->error_indicator) { p->level--; @@ -13747,7 +13770,7 @@ bitwise_xor_raw(Parser *p) } // Left-recursive -// bitwise_and: bitwise_and '&' shift_expr | shift_expr +// bitwise_and: bitwise_and '&' shift_expr | invalid_bitwise_and | shift_expr static expr_ty bitwise_and_raw(Parser *); static expr_ty bitwise_and_rule(Parser *p) @@ -13843,6 +13866,25 @@ bitwise_and_raw(Parser *p) D(fprintf(stderr, "%*c%s bitwise_and[%d-%d]: %s failed!\n", p->level, ' ', p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "bitwise_and '&' shift_expr")); } + if (p->call_invalid_rules) { // invalid_bitwise_and + if (p->error_indicator) { + p->level--; + return NULL; + } + D(fprintf(stderr, "%*c> bitwise_and[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "invalid_bitwise_and")); + void *invalid_bitwise_and_var; + if ( + (invalid_bitwise_and_var = invalid_bitwise_and_rule(p)) // invalid_bitwise_and + ) + { + D(fprintf(stderr, "%*c+ bitwise_and[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "invalid_bitwise_and")); + _res = invalid_bitwise_and_var; + goto done; + } + p->mark = _mark; + D(fprintf(stderr, "%*c%s bitwise_and[%d-%d]: %s failed!\n", p->level, ' ', + p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "invalid_bitwise_and")); + } { // shift_expr if (p->error_indicator) { p->level--; @@ -28434,6 +28476,106 @@ invalid_type_params_rule(Parser *p) return _res; } +// Left-recursive +// invalid_bitwise_and: bitwise_and '&' '&' +static void * +invalid_bitwise_and_rule(Parser *p) +{ + if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) { + _Pypegen_stack_overflow(p); + } + if (p->error_indicator) { + p->level--; + return NULL; + } + void * _res = NULL; + int _mark = p->mark; + { // bitwise_and '&' '&' + if (p->error_indicator) { + p->level--; + return NULL; + } + D(fprintf(stderr, "%*c> invalid_bitwise_and[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "bitwise_and '&' '&'")); + expr_ty a; + Token * b; + Token * c; + if ( + (a = bitwise_and_rule(p)) // bitwise_and + && + (b = _PyPegen_expect_token(p, 19)) // token='&' + && + (c = _PyPegen_expect_token(p, 19)) // token='&' + ) + { + D(fprintf(stderr, "%*c+ invalid_bitwise_and[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "bitwise_and '&' '&'")); + _res = _PyPegen_tokens_are_adjacent ( b , c ) ? RAISE_SYNTAX_ERROR_KNOWN_RANGE ( b , c , "invalid syntax. Maybe you meant 'and' or '&' instead of '&&'?" ) : NULL; + if ((_res == NULL || p->error_indicator) && PyErr_Occurred()) { + p->error_indicator = 1; + p->level--; + return NULL; + } + goto done; + } + p->mark = _mark; + D(fprintf(stderr, "%*c%s invalid_bitwise_and[%d-%d]: %s failed!\n", p->level, ' ', + p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "bitwise_and '&' '&'")); + } + _res = NULL; + done: + p->level--; + return _res; +} + +// Left-recursive +// invalid_bitwise_or: bitwise_or '|' '|' +static void * +invalid_bitwise_or_rule(Parser *p) +{ + if (p->level++ == MAXSTACK || _Py_ReachedRecursionLimitWithMargin(PyThreadState_Get(), 1)) { + _Pypegen_stack_overflow(p); + } + if (p->error_indicator) { + p->level--; + return NULL; + } + void * _res = NULL; + int _mark = p->mark; + { // bitwise_or '|' '|' + if (p->error_indicator) { + p->level--; + return NULL; + } + D(fprintf(stderr, "%*c> invalid_bitwise_or[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "bitwise_or '|' '|'")); + expr_ty a; + Token * b; + Token * c; + if ( + (a = bitwise_or_rule(p)) // bitwise_or + && + (b = _PyPegen_expect_token(p, 18)) // token='|' + && + (c = _PyPegen_expect_token(p, 18)) // token='|' + ) + { + D(fprintf(stderr, "%*c+ invalid_bitwise_or[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "bitwise_or '|' '|'")); + _res = _PyPegen_tokens_are_adjacent ( b , c ) ? RAISE_SYNTAX_ERROR_KNOWN_RANGE ( b , c , "invalid syntax. Maybe you meant 'or' or '|' instead of '||'?" ) : NULL; + if ((_res == NULL || p->error_indicator) && PyErr_Occurred()) { + p->error_indicator = 1; + p->level--; + return NULL; + } + goto done; + } + p->mark = _mark; + D(fprintf(stderr, "%*c%s invalid_bitwise_or[%d-%d]: %s failed!\n", p->level, ' ', + p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "bitwise_or '|' '|'")); + } + _res = NULL; + done: + p->level--; + return _res; +} + // _loop0_1: NEWLINE static asdl_seq * _loop0_1_rule(Parser *p) diff --git a/Parser/pegen.h b/Parser/pegen.h index 5c461e82a7f0fa7..4a4f7536db2da79 100644 --- a/Parser/pegen.h +++ b/Parser/pegen.h @@ -208,6 +208,12 @@ RAISE_ERROR_KNOWN_LOCATION(Parser *p, PyObject *errtype, RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError, (a)->lineno, (a)->col_offset, CURRENT_POS, CURRENT_POS, msg, ##__VA_ARGS__) #define RAISE_SYNTAX_ERROR_INVALID_TARGET(type, e) _RAISE_SYNTAX_ERROR_INVALID_TARGET(p, type, e) +Py_LOCAL_INLINE(int) +_PyPegen_tokens_are_adjacent(Token *a, Token *b) +{ + return (a->end_lineno == b->lineno) && (a->end_col_offset == b->col_offset); +} + Py_LOCAL_INLINE(void *) CHECK_CALL(Parser *p, void *result) { diff --git a/Python/pystate.c b/Python/pystate.c index fed1df0173bacf1..29f13e92e0dd1fd 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1605,6 +1605,8 @@ init_threadstate(_PyThreadStateImpl *_tstate, tstate->current_frame = &_tstate->base_frame; // base_frame pointer for profilers to validate stack unwinding tstate->base_frame = &_tstate->base_frame; + tstate->last_profiled_frame = NULL; + tstate->last_profiled_frame_seq = 0; tstate->datastack_chunk = NULL; tstate->datastack_top = NULL; tstate->datastack_limit = NULL; diff --git a/Python/remote_debug.h b/Python/remote_debug.h index 6fecc23502b46ef..7b7380d25bf496f 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -781,6 +781,106 @@ search_elf_file_for_section( return result; } +static const char * +find_debug_cookie(const char *buffer, size_t len) +{ + const char *cookie = _Py_Debug_Cookie; + const size_t cookie_len = sizeof(_Py_Debug_Cookie) - 1; + if (len < cookie_len) { + return NULL; + } + + size_t pos = 0; + size_t last = len - cookie_len; + while (pos <= last) { + const char *candidate = memchr( + buffer + pos, cookie[0], last - pos + 1); + if (candidate == NULL) { + return NULL; + } + pos = (size_t)(candidate - buffer); + if (memcmp(candidate, cookie, cookie_len) == 0) { + return candidate; + } + pos++; + } + return NULL; +} + +static int +linux_map_path_is_deleted(const char *path) +{ + static const char deleted_suffix[] = " (deleted)"; + size_t path_len = strlen(path); + size_t suffix_len = sizeof(deleted_suffix) - 1; + return path_len >= suffix_len + && strcmp(path + path_len - suffix_len, deleted_suffix) == 0; +} + +static int +linux_map_perms_are_readwrite(const char *perms) +{ + return perms[0] == 'r' && perms[1] == 'w'; +} + +static uintptr_t +scan_linux_mapping_for_pyruntime_cookie( + proc_handle_t *handle, + uintptr_t start, + uintptr_t end) +{ + if (end <= start) { + return 0; + } + + const size_t cookie_len = sizeof(_Py_Debug_Cookie) - 1; + const size_t overlap = cookie_len - 1; + const size_t chunk_size = 1024 * 1024; + char *buffer = PyMem_Malloc(chunk_size); + if (buffer == NULL) { + PyErr_NoMemory(); + _set_debug_exception_cause(PyExc_MemoryError, + "Cannot allocate memory while scanning PID %d for PyRuntime cookie", + handle->pid); + return 0; + } + + uintptr_t retval = 0; + uintptr_t mapping_size = end - start; + uintptr_t offset = 0; + while (offset < mapping_size) { + uintptr_t remaining = mapping_size - offset; + size_t wanted = remaining > chunk_size + ? chunk_size : (size_t)remaining; + if (_Py_RemoteDebug_ReadRemoteMemory( + handle, start + offset, wanted, buffer) < 0) { + if (_Py_RemoteDebug_HasPermissionError()) { + goto exit; + } + // A candidate mapping can disappear or contain unreadable holes while + // the target process keeps running. Treat those as non-matches and + // keep scanning other candidate mappings. + PyErr_Clear(); + } + else { + const char *hit = find_debug_cookie(buffer, wanted); + if (hit != NULL) { + retval = start + offset + (uintptr_t)(hit - buffer); + goto exit; + } + } + + if (wanted <= overlap) { + break; + } + offset += wanted - overlap; + } + +exit: + PyMem_Free(buffer); + return retval; +} + static uintptr_t search_linux_map_for_section(proc_handle_t *handle, const char* secname, const char* substr, section_validator_t validator) @@ -835,16 +935,22 @@ search_linux_map_for_section(proc_handle_t *handle, const char* secname, const c linelen = 0; unsigned long start = 0; - unsigned long path_pos = 0; - sscanf(line, "%lx-%*x %*s %*s %*s %*s %ln", &start, &path_pos); + unsigned long end = 0; + int path_pos = 0; + char perms[5] = ""; + int fields = sscanf(line, "%lx-%lx %4s %*s %*s %*s %n", + &start, &end, perms, &path_pos); - if (!path_pos) { + if (fields < 3 || !path_pos) { // Line didn't match our format string. This shouldn't be // possible, but let's be defensive and skip the line. continue; } const char *path = line + path_pos; + if (path[0] == '\0') { + continue; + } if (path[0] == '[' && path[strlen(path)-1] == ']') { // Skip [heap], [stack], [anon:cpython:pymalloc], etc. continue; @@ -858,8 +964,21 @@ search_linux_map_for_section(proc_handle_t *handle, const char* secname, const c } if (strstr(filename, substr)) { - PyErr_Clear(); - retval = search_elf_file_for_section(handle, secname, start, path); + int deleted_pyruntime_mapping = + strcmp(secname, "PyRuntime") == 0 + && linux_map_path_is_deleted(path); + if (deleted_pyruntime_mapping + && linux_map_perms_are_readwrite(perms)) { + PyErr_Clear(); + retval = scan_linux_mapping_for_pyruntime_cookie( + handle, (uintptr_t)start, (uintptr_t)end); + } + if (!deleted_pyruntime_mapping + && retval == 0 && !PyErr_Occurred()) { + PyErr_Clear(); + retval = search_elf_file_for_section( + handle, secname, start, path); + } if (retval) { if (validator == NULL || validator(handle, retval)) { break;