mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Fix 3.15 script validation and exception cleanup contracts
Recognize common None loads when checking script returns. Keep the public cache tag version-derived while retaining the private DA16 format boundary. Release the thread-slot borrow before exception decrefs can finalize an interpreter. Assisted-by: OpenAI Codex:model identifier unavailable
This commit is contained in:
1 parent
487ae47f3c
commit
2191a9d0c8
4 files changed
+31
-49
No files matched your search
@@ -69,9 +69,10 @@ pub const RUSTPYTHON_VERSION: &str = const {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Private, non-distributable reconstruction ABI. This is not CPython's magic.
|
// Private, non-distributable reconstruction ABI. This is not CPython's magic.
|
||||||
// Change token and tag together for every incompatible development format.
|
// Change the token for every incompatible development format. The public
|
||||||
|
// cache tag identifies the implementation/version; magic rejects stale ABIs.
|
||||||
pub const PYC_MAGIC_NUMBER: u16 = 0xDA16;
|
pub const PYC_MAGIC_NUMBER: u16 = 0xDA16;
|
||||||
pub const PYC_CACHE_TAG: &str = "rustpython-315-devabi2";
|
pub const PYC_CACHE_TAG: &str = concat!("rustpython-", env!("MAJOR_CPY"), env!("MINOR_CPY"));
|
||||||
|
|
||||||
// CPython format: magic_number | ('\r' << 16) | ('\n' << 24)
|
// CPython format: magic_number | ('\r' << 16) | ('\n' << 24)
|
||||||
// This protects against text-mode file reads
|
// This protects against text-mode file reads
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
bytecode::{
|
bytecode::{
|
||||||
BorrowedConstant, CodeFlags, Constant, Instruction,
|
BorrowedConstant, CodeFlags, Constant, Instruction,
|
||||||
oparg::{OpArg, OpArgState},
|
oparg::{CommonConstant, OpArg, OpArgState},
|
||||||
},
|
},
|
||||||
protocol::PyBuffer,
|
protocol::PyBuffer,
|
||||||
};
|
};
|
||||||
@@ -828,47 +828,25 @@ pub(crate) fn code_returns_only_none(code: &Py<PyCode>) -> bool {
|
|||||||
if !is_pure_function(code) {
|
if !is_pure_function(code) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let units = &code.instructions;
|
// Final 3.15 bytecode loads None through LOAD_COMMON_CONSTANT, so it
|
||||||
let Some(last) = units.len().checked_sub(1) else {
|
// need not appear in co_consts. Walk decoded operands rather than looking
|
||||||
return true;
|
// one raw code unit back, which can lose EXTENDED_ARG bits.
|
||||||
};
|
let mut previous_loads_none = false;
|
||||||
// The last instruction either returns or raises. We can take advantage
|
for (_, op, arg) in walk_instructions(code) {
|
||||||
// of that for a quick exit.
|
if matches!(op, Instruction::ExtendedArg) {
|
||||||
let final_op = units.read_op(last).deoptimize();
|
continue;
|
||||||
|
}
|
||||||
// Look up None in co_consts.
|
if matches!(op, Instruction::ReturnValue) && !previous_loads_none {
|
||||||
let Some(none_index) = code
|
|
||||||
.constants
|
|
||||||
.iter()
|
|
||||||
.position(|c| matches!(c.borrow_constant(), BorrowedConstant::None))
|
|
||||||
else {
|
|
||||||
// None wasn't there, which means there was no implicit return,
|
|
||||||
// "return", or "return None".
|
|
||||||
|
|
||||||
// That means there must be an explicit return (non-None), or it only
|
|
||||||
// raises.
|
|
||||||
if matches!(final_op, Instruction::ReturnValue) {
|
|
||||||
// It was an explicit return (non-None).
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// It must end with a raise then. We still have to walk the bytecode
|
previous_loads_none = match op {
|
||||||
// to see if there's any explicit return (non-None).
|
Instruction::LoadCommonConstant { idx } => idx.get(arg) == CommonConstant::None,
|
||||||
return !walk_instructions(code).any(|(_, op, _)| matches!(op, Instruction::ReturnValue));
|
Instruction::LoadConst { consti } => code
|
||||||
};
|
.constants
|
||||||
// Walk the bytecode, looking for RETURN_VALUE.
|
.get(consti.get(arg) as usize)
|
||||||
for (at, op, _) in walk_instructions(code) {
|
.is_some_and(|value| matches!(value.borrow_constant(), BorrowedConstant::None)),
|
||||||
if !matches!(op, Instruction::ReturnValue) {
|
_ => false,
|
||||||
continue;
|
};
|
||||||
}
|
|
||||||
// Ignore it if it returns None.
|
|
||||||
if let Some(prev) = at.checked_sub(1)
|
|
||||||
&& matches!(units.read_op(prev).deoptimize(), Instruction::LoadConst { .. })
|
|
||||||
// We don't worry about EXTENDED_ARG for now.
|
|
||||||
&& usize::from(u8::from(units.read_arg(prev))) == none_index
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1094,13 +1094,14 @@ pub fn get_current_frame() -> *const InterpreterFrame {
|
|||||||
/// Called from push_exception/pop_exception/set_exception.
|
/// Called from push_exception/pop_exception/set_exception.
|
||||||
#[cfg(feature = "threading")]
|
#[cfg(feature = "threading")]
|
||||||
pub fn update_thread_exception(exc: Option<PyBaseExceptionRef>) {
|
pub fn update_thread_exception(exc: Option<PyBaseExceptionRef>) {
|
||||||
CURRENT_THREAD_SLOT.with(|slot| {
|
// Release the TLS borrow before dropping either exception: traceback
|
||||||
if let Some(s) = slot.borrow().as_ref() {
|
// locals can finalize another interpreter and re-enter this thread slot.
|
||||||
// SAFETY: Called only from the owning thread. The old ref is dropped
|
let slot = current_thread_slot();
|
||||||
// here on the owning thread, which is safe.
|
let old = slot.and_then(|s| {
|
||||||
let _old = unsafe { s.exception.swap(exc) };
|
// SAFETY: Called only from the owning thread.
|
||||||
}
|
unsafe { s.exception.swap(exc) }
|
||||||
});
|
});
|
||||||
|
drop(old);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collect all threads' current exceptions for sys._current_exceptions().
|
/// Collect all threads' current exceptions for sys._current_exceptions().
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ from pathlib import Path
|
|||||||
|
|
||||||
if sys.implementation.name == "rustpython":
|
if sys.implementation.name == "rustpython":
|
||||||
assert external.MAGIC_NUMBER == bytes((0x16, 0xDA, 13, 10))
|
assert external.MAGIC_NUMBER == bytes((0x16, 0xDA, 13, 10))
|
||||||
assert sys.implementation.cache_tag == "rustpython-315-devabi2"
|
assert sys.implementation.cache_tag == (
|
||||||
|
f"{sys.implementation.name}-{sys.version_info.major}{sys.version_info.minor}"
|
||||||
|
)
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
root = Path(directory)
|
root = Path(directory)
|
||||||
|
|||||||
Reference in new issue
Block a user