Fix disabled stream identity and annotation operand checks (#8960)

Return the context's canonical None singleton when standard streams are
disabled. Check the exact set type before accessing user-rebindable
conditional-annotation tracking objects.

Both fixes apply to the unchanged Python 3.14 target. Include the no-stdio
embedding regression and the unchanged canonical regression from CPython's
3.14 maintenance backport c21e7c45, after the vulnerable 3.14.7 tag.

Extracted from the larger 3.15 migration; no marshal or version changes.

Assisted-by: Codex:model-version-unavailable
This commit is contained in:
youknowdot authored and GitHub committed 2026-10-05 09:03:18 +09:00
1 parent 7536730ed6
commit 6903fed201
3 files changed
+41 -7

No files matched your search

+16
View File
@@ -1,5 +1,6 @@
import annotationlib
import inspect
import itertools
import textwrap
import types
import unittest
@@ -782,6 +783,21 @@ class ConditionalAnnotationTests(unittest.TestCase):
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
+4 -4
View File
@@ -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<PySet> = unsafe {
// SAFETY: trust compiler
obj.downcast_unchecked_ref()
};
// Annotation tracking can load a user-rebound global here.
let set = obj.downcast_ref_if_exact::<PySet>(vm).ok_or_else(|| {
vm.new_type_error(format!("'{}' object is not a set", obj.class().name()))
})?;
set.add(item, vm)?;
Ok(None)
}
+21 -3
View File
@@ -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;