diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index c98b99e98e9..2b95cabf2e4 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -1,5 +1,6 @@ import annotationlib import inspect +import itertools import textwrap import types import unittest @@ -782,6 +783,21 @@ def test_non_name_annotations(self): class RegressionTests(unittest.TestCase): + # gh-154902 + def test_conditional_annotations_rebound(self): + # user code can rebind __conditional_annotations__ to any object + lefts = ("__conditional_annotations__", + 'globals()["__conditional_annotations__"]') + values = ("0", "{}", "[]", "''", "object()", "frozenset()") + for left, value in itertools.product(lefts, values): + with self.subTest(left=left, value=value): + code = f""" + {left} = {value} + x: int + """ + with self.assertRaises(TypeError): + run_code(code) + # gh-132479 def test_complex_comprehension_inlining(self): # Test that the various repro cases from the issue don't crash diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index cc54ad6ee18..6080e4d851b 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -5587,10 +5587,10 @@ impl ExecutingFrame<'_> { Instruction::SetAdd { i } => { let item = self.pop_value(); let obj = self.nth_value(i.get(arg) - 1); - let set: &Py = unsafe { - // SAFETY: trust compiler - obj.downcast_unchecked_ref() - }; + // Annotation tracking can load a user-rebound global here. + let set = obj.downcast_ref_if_exact::(vm).ok_or_else(|| { + vm.new_type_error(format!("'{}' object is not a set", obj.class().name())) + })?; set.add(item, vm)?; Ok(None) } diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 7cc636b37e3..4a74479f43d 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -1514,9 +1514,7 @@ impl VirtualMachine { // No stdio: set to None (embedding use case) #[cfg(not(feature = "stdio"))] - let make_stdio = |_name: &str, _fd: i32, _write: bool| { - Ok(crate::builtins::PyNone.into_pyobject(self)) - }; + let make_stdio = |_name: &str, _fd: i32, _write: bool| Ok(self.ctx.none()); let set_stdio = |name, fd, write| { let stdio: PyObjectRef = make_stdio(name, fd, write)?; @@ -3925,6 +3923,26 @@ pub fn resolve_frozen_alias(name: &str) -> &str { mod tests { use super::*; + #[cfg(not(feature = "stdio"))] + #[test] + fn no_stdio_streams_are_none_singleton() { + Interpreter::builder(Default::default()) + .build() + .enter(|vm| { + for name in [ + "stdin", + "stdout", + "stderr", + "__stdin__", + "__stdout__", + "__stderr__", + ] { + let stream = vm.sys_module.get_attr(name, vm).unwrap(); + assert!(vm.is_none(&stream), "sys.{name} must be the None singleton"); + } + }); + } + #[test] fn nested_frozen() { use rustpython_vm as vm;