From ec206b1125d5ca6581ef7f84bc7718540ca65a0c Mon Sep 17 00:00:00 2001 From: dot Date: Tue, 13 Oct 2026 10:47:47 +0900 Subject: [PATCH] Align local monitoring events and final warning-format tests Allow final exception-event masks locally and remove only the local event registration on DISABLE. Match final bytes unsupported-letter diagnostics. Copy the three affected canonical test definitions through update_lib from pinned final CPython. Assisted-by: OpenAI Codex:model identifier unavailable --- Lib/test/test_format.py | 2 +- Lib/test/test_warnings/__init__.py | 31 +++++++++++++++++--------- crates/vm/src/cformat.rs | 14 +++++++++--- crates/vm/src/stdlib/sys/monitoring.rs | 24 ++++++++++++++------ 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py index 1f626d87f..a4bf797aa 100644 --- a/Lib/test/test_format.py +++ b/Lib/test/test_format.py @@ -626,7 +626,7 @@ class FormatTest(unittest.TestCase): with self.assertRaisesRegex(ValueError, error_msg): f"{'x':zs}" # can't apply to string - error_msg = re.escape("unsupported format character 'z'") + error_msg = re.escape("unsupported format %z at position 0") with self.assertRaisesRegex(ValueError, error_msg): "%z.1f" % 0 # not allowed in old style string interpolation with self.assertRaisesRegex(ValueError, error_msg): diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index bbb64faa4..e7ca3e141 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -250,10 +250,23 @@ class FilterTests(BaseTest): self.module.warn_explicit('msg', UserWarning, 'filename', 42, module='package.module') self.assertEqual(len(w), 1) + self.module.warn_explicit('msg', UserWarning, '/path/to/package/module', 42) + self.assertEqual(len(w), 2) + self.module.warn_explicit('msg', UserWarning, '/path/to/package/module.py', 42) + self.assertEqual(len(w), 3) + self.module.warn_explicit('msg', UserWarning, '/path/to/package/module/__init__.py', 42) + self.assertEqual(len(w), 4) with self.assertRaises(UserWarning): - self.module.warn_explicit('msg', UserWarning, '/path/to/package/module', 42) - with self.assertRaises(UserWarning): - self.module.warn_explicit('msg', UserWarning, '/path/to/package/module.py', 42) + self.module.warn_explicit('msg', UserWarning, '/path/to/package/module/__init__', 42) + if MS_WINDOWS: + self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.PY', 42) + self.assertEqual(len(w), 5) + self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module\__INIT__.PY', 42) + self.assertEqual(len(w), 6) + self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.PYW', 42) + self.assertEqual(len(w), 7) + self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module\__INIT__.PYW', 42) + self.assertEqual(len(w), 8) with self.module.catch_warnings(record=True) as w: self.module.simplefilter('error') @@ -277,9 +290,8 @@ class FilterTests(BaseTest): with self.assertRaises(UserWarning): self.module.warn_explicit('msg', UserWarning, '/PATH/TO/PACKAGE/MODULE', 42) if MS_WINDOWS: - if self.module is py_warnings: - self.module.warn_explicit('msg', UserWarning, r'/path/to/package/module.PY', 42) - self.assertEqual(len(w), 3) + self.module.warn_explicit('msg', UserWarning, r'/path/to/package/module.PY', 42) + self.assertEqual(len(w), 3) with self.assertRaises(UserWarning): self.module.warn_explicit('msg', UserWarning, r'/path/to/package/module/__init__.py', 42) with self.assertRaises(UserWarning): @@ -303,9 +315,8 @@ class FilterTests(BaseTest): self.assertEqual(len(w), 1) self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.py', 42) self.assertEqual(len(w), 2) - if self.module is py_warnings: - self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.PY', 42) - self.assertEqual(len(w), 3) + self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.PY', 42) + self.assertEqual(len(w), 3) with self.assertRaises(UserWarning): self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.pyw', 42) with self.assertRaises(UserWarning): @@ -400,7 +411,7 @@ class FilterTests(BaseTest): def test_mutate_filter_list(self): class X: - def match(self, a): + def match(self, a, start=0): L[:] = [] L = [("default",X(),UserWarning,X(),0) for i in range(2)] diff --git a/crates/vm/src/cformat.rs b/crates/vm/src/cformat.rs index 9a68e7788..ae359250b 100644 --- a/crates/vm/src/cformat.rs +++ b/crates/vm/src/cformat.rs @@ -73,8 +73,14 @@ fn spec_format_bytes( spec: &CFormatSpec, obj: PyObjectRef, argument: FormatArgument<'_>, + format_start: usize, ) -> PyResult> { match &spec.format_type { + CFormatType::Unsupported { ch, .. } if ch.to_char_lossy().is_ascii_alphabetic() => Err(vm + .new_value_error(format!( + "unsupported format %{} at position {format_start}", + ch.to_char_lossy(), + ))), CFormatType::Unsupported { ch, index } => Err(vm.new_value_error(format!( "unsupported format character '{}' ({:#x}) at index {index}", ch.to_char_lossy(), @@ -407,7 +413,7 @@ pub(crate) fn cformat_bytes( } // dict - for (_, part) in format { + for (format_start, part) in format { match part { CFormatPart::Literal(literal) => result.extend(literal), CFormatPart::Spec(CFormatSpecKeyed { mapping_key, spec }) => { @@ -419,6 +425,7 @@ pub(crate) fn cformat_bytes( &spec, value, FormatArgument::Mapping(key.as_object()), + format_start, )?; result.extend(part_result); } @@ -443,7 +450,7 @@ pub(crate) fn cformat_bytes( let mut value_iter = value_iter.inspect(|_| argument_index.set(argument_index.get() + 1)); let is_tuple = values_obj.downcast_ref::().is_some(); - for (_, part) in format { + for (format_start, part) in format { match part { CFormatPart::Literal(literal) => result.extend(literal), CFormatPart::Spec(CFormatSpecKeyed { mut spec, .. }) => { @@ -464,7 +471,8 @@ pub(crate) fn cformat_bytes( } else { FormatArgument::Scalar }; - let part_result = spec_format_bytes(vm, &spec, value.to_owned(), argument)?; + let part_result = + spec_format_bytes(vm, &spec, value.to_owned(), argument, format_start)?; result.extend(part_result); } } diff --git a/crates/vm/src/stdlib/sys/monitoring.rs b/crates/vm/src/stdlib/sys/monitoring.rs index 930bef9cd..5076e8a97 100644 --- a/crates/vm/src/stdlib/sys/monitoring.rs +++ b/crates/vm/src/stdlib/sys/monitoring.rs @@ -9,7 +9,8 @@ use std::collections::{HashMap, HashSet}; pub(crate) const TOOL_LIMIT: usize = 6; const EVENTS_COUNT: usize = 19; -const LOCAL_EVENTS_COUNT: usize = 11; +const INSTRUMENTED_EVENTS_COUNT: usize = 11; +const LOCAL_EVENTS_COUNT: usize = 16; const UNGROUPED_EVENTS_COUNT: usize = 18; /// Event identifier (`PY_MONITORING_EVENT_*`), stored in `tstate->what_event`. @@ -905,10 +906,21 @@ fn fire( for (tool, cb) in callbacks { let result = cb.call(args.clone(), vm)?; if disable_sentinel.as_ref().is_some_and(|d| result.is(d)) { - // Only local events (event_id < LOCAL_EVENTS_COUNT) can be disabled. - // Non-local events (RAISE, EXCEPTION_HANDLED, PY_UNWIND, etc.) - // cannot be disabled per code object. - if event_id >= LOCAL_EVENTS_COUNT { + if event_id < INSTRUMENTED_EVENTS_COUNT { + let mut state = vm.state.monitoring.lock(); + state.disabled.insert((code_id, offset as usize, tool)); + } else if event_id < LOCAL_EVENTS_COUNT { + // Non-instrumented events disable their local registration + // for this code object, leaving global events active. + let mut state = vm.state.monitoring.lock(); + if let Some(events) = state.local_events.get_mut(&(tool, code_id)) { + *events &= !event_mask; + if *events == 0 { + state.local_events.remove(&(tool, code_id)); + } + } + update_events_mask(vm, &state); + } else { // Remove the callback. let mut state = vm.state.monitoring.lock(); state.callbacks.remove(&(tool, event_id)); @@ -917,8 +929,6 @@ fn fire( EVENT_NAMES[event_id] ))); } - let mut state = vm.state.monitoring.lock(); - state.disabled.insert((code_id, offset as usize, tool)); } } Ok(())