mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
marshal (#7467)
* CPython-compatible marshal format Unify marshal to a single CPython-compatible format. No separate "cpython_marshal" reader — one format for frozen modules, .pyc files, and the Python-level marshal module. - ComparisonOperator: `(cmp_index << 5) | mask` matching COMPARE_OP - MakeFunctionFlag: bit-position matching SET_FUNCTION_ATTRIBUTE - Exception table varint: big-endian (matching Python/assemble.c) - Linetable varint: little-endian (unchanged) - Integer: TYPE_INT (i32) / TYPE_LONG (base-2^15 digits) - Code objects: CPython field order (argcount, posonlyargcount, ..., co_localsplusnames, co_localspluskinds, ..., co_exceptiontable) - FLAG_REF / TYPE_REF for object deduplication (version >= 3) - allow_code keyword argument on dumps/loads/dump/load - Subclass rejection (int/float/complex/tuple/list/dict/set/frozenset) - Slice serialization (version >= 5) - Buffer protocol fallback for memoryview/array - Recursion depth limit (2000) for both reads and writes - Streaming load (reads one object, seeks file position) - TYPE_INT64, TYPE_FLOAT (text), TYPE_COMPLEX (text) for compat serialize_code writes co_localsplusnames/co_localspluskinds from split varnames/cellvars/freevars. deserialize_code splits them back. Cell variable DEREF indices are translated between flat (wire) and cell-relative (internal) representations in both directions. Replace bitwise trick with match for new ComparisonOperator values. 21 -> 3 expected failures. Remaining: test_bad_reader (IO layer), test_deterministic_sets (PYTHONHASHSEED), testIntern (string interning). * Address code review: preserve CO_FAST_HIDDEN, fix varint overflow - Use original localspluskinds from marshal data instead of rebuilding, preserving CO_FAST_HIDDEN and other flags - Fix write_varint_be to handle values >= 2^30 (add 6th chunk) - Remove unused build_localspluskinds_from_split * Add depth guard to deserialize_value_typed Prevents usize underflow when dict key deserialization path calls deserialize_value_typed with depth=0 on composite types.
This commit is contained in:
1 parent
bef2e41295
commit
98dba36af5
8 files changed
+1283
-366
No files matched your search
Vendored
+1
-19
@@ -49,7 +49,6 @@ class IntTestCase(unittest.TestCase, HelperMixin):
|
||||
self.helper(expected)
|
||||
n = n >> 1
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_int64(self):
|
||||
# Simulate int marshaling with TYPE_INT64.
|
||||
maxint64 = (1 << 63) - 1
|
||||
@@ -141,7 +140,6 @@ class CodeTestCase(unittest.TestCase):
|
||||
self.assertEqual(co1.co_filename, "f1")
|
||||
self.assertEqual(co2.co_filename, "f2")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Unexpected keyword argument allow_code
|
||||
def test_no_allow_code(self):
|
||||
data = {'a': [({0},)]}
|
||||
dump = marshal.dumps(data, allow_code=False)
|
||||
@@ -234,14 +232,12 @@ class BufferTestCase(unittest.TestCase, HelperMixin):
|
||||
new = marshal.loads(marshal.dumps(b))
|
||||
self.assertEqual(type(new), bytes)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_memoryview(self):
|
||||
b = memoryview(b"abc")
|
||||
self.helper(b)
|
||||
new = marshal.loads(marshal.dumps(b))
|
||||
self.assertEqual(type(new), bytes)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_array(self):
|
||||
a = array.array('B', b"abc")
|
||||
new = marshal.loads(marshal.dumps(a))
|
||||
@@ -274,7 +270,6 @@ class BugsTestCase(unittest.TestCase):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_loads_recursion(self):
|
||||
def run_tests(N, check):
|
||||
# (((...None...),),)
|
||||
@@ -295,7 +290,7 @@ class BugsTestCase(unittest.TestCase):
|
||||
run_tests(2**20, check)
|
||||
|
||||
@unittest.skipIf(support.is_android, "TODO: RUSTPYTHON; segfault")
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; segfault
|
||||
@unittest.skipIf(os.name == 'nt', "TODO: RUSTPYTHON; write depth limit is 2000 not 1000")
|
||||
def test_recursion_limit(self):
|
||||
# Create a deeply nested structure.
|
||||
head = last = []
|
||||
@@ -324,7 +319,6 @@ class BugsTestCase(unittest.TestCase):
|
||||
last.append([0])
|
||||
self.assertRaises(ValueError, marshal.dumps, head)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_exact_type_match(self):
|
||||
# Former bug:
|
||||
# >>> class Int(int): pass
|
||||
@@ -348,7 +342,6 @@ class BugsTestCase(unittest.TestCase):
|
||||
invalid_string = b'l\x02\x00\x00\x00\x00\x00\x00\x00'
|
||||
self.assertRaises(ValueError, marshal.loads, invalid_string)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_multiple_dumps_and_loads(self):
|
||||
# Issue 12291: marshal.load() should be callable multiple times
|
||||
# with interleaved data written by non-marshal code
|
||||
@@ -532,66 +525,56 @@ class InstancingTestCase(unittest.TestCase, HelperMixin):
|
||||
else:
|
||||
self.assertGreaterEqual(len(s2), len(s3))
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testInt(self):
|
||||
intobj = 123321
|
||||
self.helper(intobj)
|
||||
self.helper3(intobj, simple=True)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testFloat(self):
|
||||
floatobj = 1.2345
|
||||
self.helper(floatobj)
|
||||
self.helper3(floatobj)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testStr(self):
|
||||
strobj = "abcde"*3
|
||||
self.helper(strobj)
|
||||
self.helper3(strobj)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testBytes(self):
|
||||
bytesobj = b"abcde"*3
|
||||
self.helper(bytesobj)
|
||||
self.helper3(bytesobj)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testList(self):
|
||||
for obj in self.keys:
|
||||
listobj = [obj, obj]
|
||||
self.helper(listobj)
|
||||
self.helper3(listobj)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testTuple(self):
|
||||
for obj in self.keys:
|
||||
tupleobj = (obj, obj)
|
||||
self.helper(tupleobj)
|
||||
self.helper3(tupleobj)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testSet(self):
|
||||
for obj in self.keys:
|
||||
setobj = {(obj, 1), (obj, 2)}
|
||||
self.helper(setobj)
|
||||
self.helper3(setobj)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testFrozenSet(self):
|
||||
for obj in self.keys:
|
||||
frozensetobj = frozenset({(obj, 1), (obj, 2)})
|
||||
self.helper(frozensetobj)
|
||||
self.helper3(frozensetobj)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testDict(self):
|
||||
for obj in self.keys:
|
||||
dictobj = {"hello": obj, "goodbye": obj, obj: "hello"}
|
||||
self.helper(dictobj)
|
||||
self.helper3(dictobj)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def testModule(self):
|
||||
with open(__file__, "rb") as f:
|
||||
code = f.read()
|
||||
@@ -651,7 +634,6 @@ class InterningTestCase(unittest.TestCase, HelperMixin):
|
||||
self.assertNotEqual(id(s2), id(s))
|
||||
|
||||
class SliceTestCase(unittest.TestCase, HelperMixin):
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; NotImplementedError: TODO: not implemented yet or marshal unsupported type
|
||||
def test_slice(self):
|
||||
for obj in (
|
||||
slice(None), slice(1), slice(1, 2), slice(1, 2, 3),
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::{
|
||||
marshal::MarshalError,
|
||||
varint::{read_varint, read_varint_with_start, write_varint, write_varint_with_start},
|
||||
varint::{read_varint, read_varint_with_start, write_varint_be, write_varint_with_start},
|
||||
{OneIndexed, SourceLocation},
|
||||
};
|
||||
use alloc::{borrow::ToOwned, boxed::Box, collections::BTreeSet, fmt, string::String, vec::Vec};
|
||||
@@ -71,9 +71,9 @@ pub fn encode_exception_table(entries: &[ExceptionTableEntry]) -> alloc::boxed::
|
||||
let depth_lasti = ((entry.depth as u32) << 1) | (entry.push_lasti as u32);
|
||||
|
||||
write_varint_with_start(&mut data, entry.start);
|
||||
write_varint(&mut data, size);
|
||||
write_varint(&mut data, entry.target);
|
||||
write_varint(&mut data, depth_lasti);
|
||||
write_varint_be(&mut data, size);
|
||||
write_varint_be(&mut data, entry.target);
|
||||
write_varint_be(&mut data, depth_lasti);
|
||||
}
|
||||
data.into_boxed_slice()
|
||||
}
|
||||
@@ -204,7 +204,7 @@ impl PyCodeLocationInfoKind {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Constant: Sized {
|
||||
pub trait Constant: Sized + Clone {
|
||||
type Name: AsRef<str>;
|
||||
|
||||
/// Transforms the given Constant to a BorrowedConstant
|
||||
@@ -567,6 +567,14 @@ impl Deref for CodeUnits {
|
||||
}
|
||||
|
||||
impl CodeUnits {
|
||||
/// Disable adaptive specialization by setting all counters to unreachable.
|
||||
/// Used for CPython-compiled bytecode where specialization may not be safe.
|
||||
pub fn disable_specialization(&self) {
|
||||
for counter in self.adaptive_counters.iter() {
|
||||
counter.store(UNREACHABLE_BACKOFF, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace the opcode at `index` in-place without changing the arg byte.
|
||||
/// Uses atomic Release store to ensure prior cache writes are visible
|
||||
/// to threads that subsequently read the new opcode with Acquire.
|
||||
|
||||
@@ -382,6 +382,10 @@ oparg_enum!(
|
||||
);
|
||||
|
||||
bitflagset::bitflag! {
|
||||
/// `SET_FUNCTION_ATTRIBUTE` flags.
|
||||
/// Bitmask: Defaults=0x01, KwOnly=0x02, Annotations=0x04,
|
||||
/// Closure=0x08, TypeParams=0x10, Annotate=0x20.
|
||||
/// Stored as bit position (0-5) by `bitflag!` macro.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[repr(u8)]
|
||||
pub enum MakeFunctionFlag {
|
||||
@@ -426,20 +430,63 @@ impl From<MakeFunctionFlag> for u32 {
|
||||
|
||||
impl OpArgType for MakeFunctionFlag {}
|
||||
|
||||
oparg_enum!(
|
||||
/// The possible comparison operators.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum ComparisonOperator {
|
||||
// be intentional with bits so that we can do eval_ord with just a bitwise and
|
||||
// bits: | Equal | Greater | Less |
|
||||
Less = 0b001,
|
||||
Greater = 0b010,
|
||||
NotEqual = 0b011,
|
||||
Equal = 0b100,
|
||||
LessOrEqual = 0b101,
|
||||
GreaterOrEqual = 0b110,
|
||||
/// `COMPARE_OP` arg is `(cmp_index << 5) | mask`. Only the upper
|
||||
/// 3 bits identify the comparison; the lower 5 bits are an inline
|
||||
/// cache mask for adaptive specialization.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum ComparisonOperator {
|
||||
Less,
|
||||
LessOrEqual,
|
||||
Equal,
|
||||
NotEqual,
|
||||
Greater,
|
||||
GreaterOrEqual,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for ComparisonOperator {
|
||||
type Error = MarshalError;
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
Self::try_from(value as u32)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for ComparisonOperator {
|
||||
type Error = MarshalError;
|
||||
/// Decode from `COMPARE_OP` arg: `(cmp_index << 5) | mask`.
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
match value >> 5 {
|
||||
0 => Ok(Self::Less),
|
||||
1 => Ok(Self::LessOrEqual),
|
||||
2 => Ok(Self::Equal),
|
||||
3 => Ok(Self::NotEqual),
|
||||
4 => Ok(Self::Greater),
|
||||
5 => Ok(Self::GreaterOrEqual),
|
||||
_ => Err(MarshalError::InvalidBytecode),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ComparisonOperator> for u8 {
|
||||
/// Encode as `cmp_index << 5` (mask bits zero).
|
||||
fn from(value: ComparisonOperator) -> Self {
|
||||
match value {
|
||||
ComparisonOperator::Less => 0,
|
||||
ComparisonOperator::LessOrEqual => 1 << 5,
|
||||
ComparisonOperator::Equal => 2 << 5,
|
||||
ComparisonOperator::NotEqual => 3 << 5,
|
||||
ComparisonOperator::Greater => 4 << 5,
|
||||
ComparisonOperator::GreaterOrEqual => 5 << 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ComparisonOperator> for u32 {
|
||||
fn from(value: ComparisonOperator) -> Self {
|
||||
Self::from(u8::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl OpArgType for ComparisonOperator {}
|
||||
|
||||
oparg_enum!(
|
||||
/// The possible Binary operators
|
||||
|
||||
+706
-191
File diff suppressed because it is too large.
Load diff
@@ -1,12 +1,14 @@
|
||||
//! Variable-length integer encoding utilities.
|
||||
//!
|
||||
//! Uses 6-bit chunks with a continuation bit (0x40) to encode integers.
|
||||
//! Used for exception tables and line number tables.
|
||||
//! Two encodings are used:
|
||||
//! - **Little-endian** (low bits first): linetable
|
||||
//! - **Big-endian** (high bits first): exception tables
|
||||
//!
|
||||
//! Both use 6-bit chunks with 0x40 as the continuation bit.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// Write a variable-length unsigned integer using 6-bit chunks.
|
||||
/// Returns the number of bytes written.
|
||||
/// Write a little-endian varint (used by linetable).
|
||||
#[inline]
|
||||
pub fn write_varint(buf: &mut Vec<u8>, mut val: u32) -> usize {
|
||||
let start_len = buf.len();
|
||||
@@ -18,12 +20,10 @@ pub fn write_varint(buf: &mut Vec<u8>, mut val: u32) -> usize {
|
||||
buf.len() - start_len
|
||||
}
|
||||
|
||||
/// Write a variable-length signed integer.
|
||||
/// Returns the number of bytes written.
|
||||
/// Write a little-endian signed varint.
|
||||
#[inline]
|
||||
pub fn write_signed_varint(buf: &mut Vec<u8>, val: i32) -> usize {
|
||||
let uval = if val < 0 {
|
||||
// (0 - val as u32) handles INT_MIN correctly
|
||||
((0u32.wrapping_sub(val as u32)) << 1) | 1
|
||||
} else {
|
||||
(val as u32) << 1
|
||||
@@ -31,70 +31,72 @@ pub fn write_signed_varint(buf: &mut Vec<u8>, val: i32) -> usize {
|
||||
write_varint(buf, uval)
|
||||
}
|
||||
|
||||
/// Write a variable-length unsigned integer with a start marker (0x80 bit).
|
||||
/// Used for exception table entries where each entry starts with the marker.
|
||||
/// Write a big-endian varint (used by exception tables).
|
||||
pub fn write_varint_be(buf: &mut Vec<u8>, val: u32) -> usize {
|
||||
let start_len = buf.len();
|
||||
if val >= 1 << 30 {
|
||||
buf.push(0x40 | ((val >> 30) & 0x3f) as u8);
|
||||
}
|
||||
if val >= 1 << 24 {
|
||||
buf.push(0x40 | ((val >> 24) & 0x3f) as u8);
|
||||
}
|
||||
if val >= 1 << 18 {
|
||||
buf.push(0x40 | ((val >> 18) & 0x3f) as u8);
|
||||
}
|
||||
if val >= 1 << 12 {
|
||||
buf.push(0x40 | ((val >> 12) & 0x3f) as u8);
|
||||
}
|
||||
if val >= 1 << 6 {
|
||||
buf.push(0x40 | ((val >> 6) & 0x3f) as u8);
|
||||
}
|
||||
buf.push((val & 0x3f) as u8);
|
||||
buf.len() - start_len
|
||||
}
|
||||
|
||||
/// Write a big-endian varint with the start marker (0x80) on the first byte.
|
||||
pub fn write_varint_with_start(data: &mut Vec<u8>, val: u32) {
|
||||
let start_pos = data.len();
|
||||
write_varint(data, val);
|
||||
// Set start bit on first byte
|
||||
write_varint_be(data, val);
|
||||
if let Some(first) = data.get_mut(start_pos) {
|
||||
*first |= 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a variable-length unsigned integer that starts with a start marker (0x80 bit).
|
||||
/// Returns None if not at a valid start byte or end of data.
|
||||
/// Read a big-endian varint with start marker (0x80).
|
||||
pub fn read_varint_with_start(data: &[u8], pos: &mut usize) -> Option<u32> {
|
||||
if *pos >= data.len() {
|
||||
return None;
|
||||
}
|
||||
let first = data[*pos];
|
||||
if first & 0x80 == 0 {
|
||||
return None; // Not a start byte
|
||||
return None;
|
||||
}
|
||||
*pos += 1;
|
||||
|
||||
let mut val = (first & 0x3f) as u32;
|
||||
let mut shift = 6;
|
||||
let mut has_continuation = first & 0x40 != 0;
|
||||
|
||||
while has_continuation && *pos < data.len() {
|
||||
let byte = data[*pos];
|
||||
if byte & 0x80 != 0 {
|
||||
break; // Next entry start
|
||||
}
|
||||
let mut cont = first & 0x40 != 0;
|
||||
while cont && *pos < data.len() {
|
||||
let b = data[*pos];
|
||||
*pos += 1;
|
||||
val |= ((byte & 0x3f) as u32) << shift;
|
||||
shift += 6;
|
||||
has_continuation = byte & 0x40 != 0;
|
||||
val = (val << 6) | (b & 0x3f) as u32;
|
||||
cont = b & 0x40 != 0;
|
||||
}
|
||||
Some(val)
|
||||
}
|
||||
|
||||
/// Read a variable-length unsigned integer.
|
||||
/// Returns None if end of data or malformed.
|
||||
/// Read a big-endian varint (no start marker).
|
||||
pub fn read_varint(data: &[u8], pos: &mut usize) -> Option<u32> {
|
||||
if *pos >= data.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut val = 0u32;
|
||||
let mut shift = 0;
|
||||
|
||||
loop {
|
||||
if *pos >= data.len() {
|
||||
return None;
|
||||
}
|
||||
let byte = data[*pos];
|
||||
if byte & 0x80 != 0 && shift > 0 {
|
||||
break; // Next entry start
|
||||
}
|
||||
let first = data[*pos];
|
||||
*pos += 1;
|
||||
let mut val = (first & 0x3f) as u32;
|
||||
let mut cont = first & 0x40 != 0;
|
||||
while cont && *pos < data.len() {
|
||||
let b = data[*pos];
|
||||
*pos += 1;
|
||||
val |= ((byte & 0x3f) as u32) << shift;
|
||||
shift += 6;
|
||||
if byte & 0x40 == 0 {
|
||||
break;
|
||||
}
|
||||
val = (val << 6) | (b & 0x3f) as u32;
|
||||
cont = b & 0x40 != 0;
|
||||
}
|
||||
Some(val)
|
||||
}
|
||||
@@ -104,37 +106,39 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_write_read_varint() {
|
||||
fn test_le_varint_roundtrip() {
|
||||
// Little-endian is only used internally in linetable,
|
||||
// no read function needed outside of linetable parsing.
|
||||
let mut buf = Vec::new();
|
||||
write_varint(&mut buf, 0);
|
||||
write_varint(&mut buf, 63);
|
||||
write_varint(&mut buf, 64);
|
||||
write_varint(&mut buf, 4095);
|
||||
|
||||
// Values: 0, 63, 64, 4095
|
||||
assert_eq!(buf.len(), 1 + 1 + 2 + 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_read_signed_varint() {
|
||||
let mut buf = Vec::new();
|
||||
write_signed_varint(&mut buf, 0);
|
||||
write_signed_varint(&mut buf, 1);
|
||||
write_signed_varint(&mut buf, -1);
|
||||
write_signed_varint(&mut buf, i32::MIN);
|
||||
|
||||
assert!(!buf.is_empty());
|
||||
fn test_be_varint_roundtrip() {
|
||||
for &val in &[0u32, 1, 63, 64, 127, 128, 4095, 4096, 1_000_000] {
|
||||
let mut buf = Vec::new();
|
||||
write_varint_be(&mut buf, val);
|
||||
let mut pos = 0;
|
||||
assert_eq!(read_varint(&buf, &mut pos), Some(val), "val={val}");
|
||||
assert_eq!(pos, buf.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_varint_with_start() {
|
||||
fn test_be_varint_with_start() {
|
||||
let mut buf = Vec::new();
|
||||
write_varint_with_start(&mut buf, 42);
|
||||
write_varint_with_start(&mut buf, 100);
|
||||
write_varint_with_start(&mut buf, 71);
|
||||
|
||||
let mut pos = 0;
|
||||
assert_eq!(read_varint_with_start(&buf, &mut pos), Some(42));
|
||||
assert_eq!(read_varint_with_start(&buf, &mut pos), Some(100));
|
||||
assert_eq!(read_varint_with_start(&buf, &mut pos), Some(71));
|
||||
assert_eq!(read_varint_with_start(&buf, &mut pos), None);
|
||||
}
|
||||
}
|
||||
@@ -194,6 +194,12 @@ impl From<Literal> for PyObjectRef {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PyObjectRef> for Literal {
|
||||
fn from(obj: PyObjectRef) -> Self {
|
||||
Literal(obj)
|
||||
}
|
||||
}
|
||||
|
||||
fn borrow_obj_constant(obj: &PyObject) -> BorrowedConstant<'_, Literal> {
|
||||
match_class!(match obj {
|
||||
ref i @ super::int::PyInt => {
|
||||
|
||||
+428
-75
@@ -5,20 +5,19 @@ pub(crate) use decl::module_def;
|
||||
mod decl {
|
||||
use crate::builtins::code::{CodeObject, Literal, PyObjBag};
|
||||
use crate::class::StaticType;
|
||||
use crate::common::wtf8::Wtf8;
|
||||
use crate::{
|
||||
PyObjectRef, PyResult, TryFromObject, VirtualMachine,
|
||||
builtins::{
|
||||
PyBool, PyByteArray, PyBytes, PyCode, PyComplex, PyDict, PyEllipsis, PyFloat,
|
||||
PyFrozenSet, PyInt, PyList, PyNone, PySet, PyStopIteration, PyStr, PyTuple,
|
||||
},
|
||||
common::wtf8::Wtf8,
|
||||
convert::ToPyObject,
|
||||
function::{ArgBytesLike, OptionalArg},
|
||||
object::{AsObject, PyPayload},
|
||||
protocol::PyBuffer,
|
||||
};
|
||||
use malachite_bigint::BigInt;
|
||||
use num_complex::Complex64;
|
||||
use num_traits::Zero;
|
||||
use rustpython_compiler_core::marshal;
|
||||
|
||||
@@ -91,34 +90,290 @@ mod decl {
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn dumps(
|
||||
#[derive(FromArgs)]
|
||||
struct DumpsArgs {
|
||||
value: PyObjectRef,
|
||||
#[pyarg(any, optional)]
|
||||
_version: OptionalArg<i32>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyBytes> {
|
||||
use marshal::Dumpable;
|
||||
let mut buf = Vec::new();
|
||||
value
|
||||
.with_dump(|val| marshal::serialize_value(&mut buf, val))
|
||||
.unwrap_or_else(Err)
|
||||
.map_err(|DumpError| {
|
||||
vm.new_not_implemented_error(
|
||||
"TODO: not implemented yet or marshal unsupported type",
|
||||
)
|
||||
})?;
|
||||
Ok(PyBytes::from(buf))
|
||||
#[pyarg(named, default = true)]
|
||||
allow_code: bool,
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn dump(
|
||||
value: PyObjectRef,
|
||||
f: PyObjectRef,
|
||||
version: OptionalArg<i32>,
|
||||
fn dumps(args: DumpsArgs, vm: &VirtualMachine) -> PyResult<PyBytes> {
|
||||
let DumpsArgs {
|
||||
value,
|
||||
allow_code,
|
||||
_version,
|
||||
} = args;
|
||||
let version = _version.unwrap_or(marshal::FORMAT_VERSION as i32);
|
||||
if !allow_code {
|
||||
check_no_code(&value, vm)?;
|
||||
}
|
||||
check_exact_type(&value, vm)?;
|
||||
let mut buf = Vec::new();
|
||||
let mut refs = if version >= 3 {
|
||||
Some(WriterRefTable::new())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
write_object(&mut buf, &value, &mut refs, version, vm)?;
|
||||
Ok(PyBytes::from(buf))
|
||||
}
|
||||
|
||||
struct WriterRefTable {
|
||||
map: std::collections::HashMap<usize, u32>,
|
||||
next_idx: u32,
|
||||
}
|
||||
|
||||
impl WriterRefTable {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
map: std::collections::HashMap::new(),
|
||||
next_idx: 0,
|
||||
}
|
||||
}
|
||||
fn try_ref(&mut self, buf: &mut Vec<u8>, obj: &PyObjectRef) -> bool {
|
||||
use marshal::Write;
|
||||
let id = obj.get_id();
|
||||
if let Some(&idx) = self.map.get(&id) {
|
||||
buf.write_u8(b'r');
|
||||
buf.write_u32(idx);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
fn reserve(&mut self, obj: &PyObjectRef) -> u32 {
|
||||
let idx = self.next_idx;
|
||||
self.map.insert(obj.get_id(), idx);
|
||||
self.next_idx += 1;
|
||||
idx
|
||||
}
|
||||
}
|
||||
|
||||
fn write_object(
|
||||
buf: &mut Vec<u8>,
|
||||
obj: &PyObjectRef,
|
||||
refs: &mut Option<WriterRefTable>,
|
||||
version: i32,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let dumped = dumps(value, version, vm)?;
|
||||
vm.call_method(&f, "write", (dumped,))?;
|
||||
write_object_depth(
|
||||
buf,
|
||||
obj,
|
||||
refs,
|
||||
version,
|
||||
vm,
|
||||
marshal::MAX_MARSHAL_STACK_DEPTH,
|
||||
)
|
||||
}
|
||||
|
||||
fn write_object_depth(
|
||||
buf: &mut Vec<u8>,
|
||||
obj: &PyObjectRef,
|
||||
refs: &mut Option<WriterRefTable>,
|
||||
version: i32,
|
||||
vm: &VirtualMachine,
|
||||
depth: usize,
|
||||
) -> PyResult<()> {
|
||||
use marshal::Write;
|
||||
if depth == 0 {
|
||||
return Err(vm.new_value_error("object too deeply nested to marshal".to_string()));
|
||||
}
|
||||
|
||||
// Singletons: no FLAG_REF needed
|
||||
let is_singleton = vm.is_none(obj)
|
||||
|| obj.class().is(PyBool::static_type())
|
||||
|| obj.is(PyStopIteration::static_type())
|
||||
|| obj.downcast_ref::<crate::builtins::PyEllipsis>().is_some();
|
||||
|
||||
// FLAG_REF: check if already written, otherwise reserve slot
|
||||
if !is_singleton
|
||||
&& let Some(rt) = refs.as_mut()
|
||||
&& rt.try_ref(buf, obj)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let type_pos = buf.len();
|
||||
let use_ref = refs.is_some() && !is_singleton;
|
||||
if use_ref {
|
||||
refs.as_mut().unwrap().reserve(obj);
|
||||
}
|
||||
|
||||
if vm.is_none(obj) {
|
||||
buf.write_u8(b'N');
|
||||
} else if obj.is(PyStopIteration::static_type()) {
|
||||
buf.write_u8(b'S');
|
||||
} else if obj.class().is(PyBool::static_type()) {
|
||||
let val = obj
|
||||
.downcast_ref::<PyInt>()
|
||||
.is_some_and(|i| !i.as_bigint().is_zero());
|
||||
buf.write_u8(if val { b'T' } else { b'F' });
|
||||
} else if obj.downcast_ref::<crate::builtins::PyEllipsis>().is_some() {
|
||||
buf.write_u8(b'.');
|
||||
} else if let Some(i) = obj.downcast_ref::<PyInt>() {
|
||||
// TYPE_INT for i32 range, TYPE_LONG for larger
|
||||
if let Ok(val) = i32::try_from(i.as_bigint()) {
|
||||
buf.write_u8(b'i');
|
||||
buf.write_u32(val as u32);
|
||||
} else {
|
||||
buf.write_u8(b'l');
|
||||
let (sign, raw) = i.as_bigint().to_bytes_le();
|
||||
let mut digits = Vec::new();
|
||||
let mut accum: u32 = 0;
|
||||
let mut bits = 0u32;
|
||||
for &byte in &raw {
|
||||
accum |= (byte as u32) << bits;
|
||||
bits += 8;
|
||||
while bits >= 15 {
|
||||
digits.push((accum & 0x7fff) as u16);
|
||||
accum >>= 15;
|
||||
bits -= 15;
|
||||
}
|
||||
}
|
||||
if accum > 0 || digits.is_empty() {
|
||||
digits.push(accum as u16);
|
||||
}
|
||||
while digits.len() > 1 && *digits.last().unwrap() == 0 {
|
||||
digits.pop();
|
||||
}
|
||||
let n = digits.len() as i32;
|
||||
let n = if sign == malachite_bigint::Sign::Minus {
|
||||
-n
|
||||
} else {
|
||||
n
|
||||
};
|
||||
buf.write_u32(n as u32);
|
||||
for d in &digits {
|
||||
buf.write_u16(*d);
|
||||
}
|
||||
}
|
||||
} else if let Some(f) = obj.downcast_ref::<PyFloat>() {
|
||||
buf.write_u8(b'g');
|
||||
buf.write_u64(f.to_f64().to_bits());
|
||||
} else if let Some(c) = obj.downcast_ref::<PyComplex>() {
|
||||
buf.write_u8(b'y');
|
||||
let cv = c.to_complex64();
|
||||
buf.write_u64(cv.re.to_bits());
|
||||
buf.write_u64(cv.im.to_bits());
|
||||
} else if let Some(s) = obj.downcast_ref::<PyStr>() {
|
||||
let bytes = s.as_wtf8().as_bytes();
|
||||
let interned = version >= 3;
|
||||
if bytes.len() < 256 && bytes.is_ascii() {
|
||||
buf.write_u8(if interned { b'Z' } else { b'z' });
|
||||
buf.write_u8(bytes.len() as u8);
|
||||
} else {
|
||||
buf.write_u8(if interned { b't' } else { b'u' });
|
||||
buf.write_u32(bytes.len() as u32);
|
||||
}
|
||||
buf.write_slice(bytes);
|
||||
} else if let Some(b) = obj.downcast_ref::<PyBytes>() {
|
||||
buf.write_u8(b's');
|
||||
let data = b.as_bytes();
|
||||
buf.write_u32(data.len() as u32);
|
||||
buf.write_slice(data);
|
||||
} else if let Some(b) = obj.downcast_ref::<PyByteArray>() {
|
||||
buf.write_u8(b's');
|
||||
let data = b.borrow_buf();
|
||||
buf.write_u32(data.len() as u32);
|
||||
buf.write_slice(&data);
|
||||
} else if let Some(t) = obj.downcast_ref::<PyTuple>() {
|
||||
buf.write_u8(b'(');
|
||||
buf.write_u32(t.len() as u32);
|
||||
for elem in t.as_slice() {
|
||||
write_object_depth(buf, elem, refs, version, vm, depth - 1)?;
|
||||
}
|
||||
} else if let Some(l) = obj.downcast_ref::<PyList>() {
|
||||
buf.write_u8(b'[');
|
||||
let items = l.borrow_vec();
|
||||
buf.write_u32(items.len() as u32);
|
||||
for elem in items.iter() {
|
||||
write_object_depth(buf, elem, refs, version, vm, depth - 1)?;
|
||||
}
|
||||
} else if let Some(d) = obj.downcast_ref::<PyDict>() {
|
||||
buf.write_u8(b'{');
|
||||
for (k, v) in d.into_iter() {
|
||||
write_object_depth(buf, &k, refs, version, vm, depth - 1)?;
|
||||
write_object_depth(buf, &v, refs, version, vm, depth - 1)?;
|
||||
}
|
||||
buf.write_u8(b'0'); // TYPE_NULL terminator
|
||||
} else if let Some(s) = obj.downcast_ref::<PySet>() {
|
||||
buf.write_u8(b'<');
|
||||
let elems = s.elements();
|
||||
buf.write_u32(elems.len() as u32);
|
||||
for elem in &elems {
|
||||
write_object_depth(buf, elem, refs, version, vm, depth - 1)?;
|
||||
}
|
||||
} else if let Some(s) = obj.downcast_ref::<PyFrozenSet>() {
|
||||
buf.write_u8(b'>');
|
||||
let elems = s.elements();
|
||||
buf.write_u32(elems.len() as u32);
|
||||
for elem in &elems {
|
||||
write_object_depth(buf, elem, refs, version, vm, depth - 1)?;
|
||||
}
|
||||
} else if let Some(co) = obj.downcast_ref::<PyCode>() {
|
||||
buf.write_u8(b'c');
|
||||
marshal::serialize_code(buf, &co.code);
|
||||
} else if let Some(sl) = obj.downcast_ref::<crate::builtins::PySlice>() {
|
||||
if version < 5 {
|
||||
return Err(vm.new_value_error("unmarshallable object".to_string()));
|
||||
}
|
||||
buf.write_u8(b':');
|
||||
let none: PyObjectRef = vm.ctx.none();
|
||||
write_object_depth(
|
||||
buf,
|
||||
sl.start.as_ref().unwrap_or(&none),
|
||||
refs,
|
||||
version,
|
||||
vm,
|
||||
depth - 1,
|
||||
)?;
|
||||
write_object_depth(buf, &sl.stop, refs, version, vm, depth - 1)?;
|
||||
write_object_depth(
|
||||
buf,
|
||||
sl.step.as_ref().unwrap_or(&none),
|
||||
refs,
|
||||
version,
|
||||
vm,
|
||||
depth - 1,
|
||||
)?;
|
||||
} else if let Ok(bytes_like) = ArgBytesLike::try_from_object(vm, obj.clone()) {
|
||||
buf.write_u8(b's');
|
||||
let data = bytes_like.borrow_buf();
|
||||
buf.write_u32(data.len() as u32);
|
||||
buf.write_slice(&data);
|
||||
} else {
|
||||
return Err(vm.new_value_error("unmarshallable object".to_string()));
|
||||
}
|
||||
|
||||
if use_ref {
|
||||
buf[type_pos] |= marshal::FLAG_REF;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct DumpArgs {
|
||||
value: PyObjectRef,
|
||||
f: PyObjectRef,
|
||||
#[pyarg(any, optional)]
|
||||
_version: OptionalArg<i32>,
|
||||
#[pyarg(named, default = true)]
|
||||
allow_code: bool,
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn dump(args: DumpArgs, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let dumped = dumps(
|
||||
DumpsArgs {
|
||||
value: args.value,
|
||||
_version: args._version,
|
||||
allow_code: args.allow_code,
|
||||
},
|
||||
vm,
|
||||
)?;
|
||||
vm.call_method(&args.f, "write", (dumped,))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -132,121 +387,219 @@ mod decl {
|
||||
fn make_bool(&self, value: bool) -> Self::Value {
|
||||
self.0.ctx.new_bool(value).into()
|
||||
}
|
||||
|
||||
fn make_none(&self) -> Self::Value {
|
||||
self.0.ctx.none()
|
||||
}
|
||||
|
||||
fn make_ellipsis(&self) -> Self::Value {
|
||||
self.0.ctx.ellipsis.clone().into()
|
||||
}
|
||||
|
||||
fn make_float(&self, value: f64) -> Self::Value {
|
||||
self.0.ctx.new_float(value).into()
|
||||
}
|
||||
|
||||
fn make_complex(&self, value: Complex64) -> Self::Value {
|
||||
fn make_complex(&self, value: num_complex::Complex64) -> Self::Value {
|
||||
self.0.ctx.new_complex(value).into()
|
||||
}
|
||||
|
||||
fn make_str(&self, value: &Wtf8) -> Self::Value {
|
||||
self.0.ctx.new_str(value).into()
|
||||
}
|
||||
|
||||
fn make_bytes(&self, value: &[u8]) -> Self::Value {
|
||||
self.0.ctx.new_bytes(value.to_vec()).into()
|
||||
}
|
||||
|
||||
fn make_int(&self, value: BigInt) -> Self::Value {
|
||||
self.0.ctx.new_int(value).into()
|
||||
}
|
||||
|
||||
fn make_tuple(&self, elements: impl Iterator<Item = Self::Value>) -> Self::Value {
|
||||
let elements = elements.collect();
|
||||
self.0.ctx.new_tuple(elements).into()
|
||||
self.0.ctx.new_tuple(elements.collect()).into()
|
||||
}
|
||||
|
||||
fn make_code(&self, code: CodeObject) -> Self::Value {
|
||||
self.0.ctx.new_code(code).into()
|
||||
}
|
||||
|
||||
fn make_stop_iter(&self) -> Result<Self::Value, marshal::MarshalError> {
|
||||
Ok(self.0.ctx.exceptions.stop_iteration.to_owned().into())
|
||||
}
|
||||
|
||||
fn make_list(
|
||||
&self,
|
||||
it: impl Iterator<Item = Self::Value>,
|
||||
) -> Result<Self::Value, marshal::MarshalError> {
|
||||
Ok(self.0.ctx.new_list(it.collect()).into())
|
||||
}
|
||||
|
||||
fn make_set(
|
||||
&self,
|
||||
it: impl Iterator<Item = Self::Value>,
|
||||
) -> Result<Self::Value, marshal::MarshalError> {
|
||||
let vm = self.0;
|
||||
let set = PySet::default().into_ref(&vm.ctx);
|
||||
let set = PySet::default().into_ref(&self.0.ctx);
|
||||
for elem in it {
|
||||
set.add(elem, vm).unwrap()
|
||||
set.add(elem, self.0).unwrap()
|
||||
}
|
||||
Ok(set.into())
|
||||
}
|
||||
|
||||
fn make_frozenset(
|
||||
&self,
|
||||
it: impl Iterator<Item = Self::Value>,
|
||||
) -> Result<Self::Value, marshal::MarshalError> {
|
||||
let vm = self.0;
|
||||
Ok(PyFrozenSet::from_iter(vm, it).unwrap().to_pyobject(vm))
|
||||
Ok(PyFrozenSet::from_iter(self.0, it)
|
||||
.unwrap()
|
||||
.to_pyobject(self.0))
|
||||
}
|
||||
|
||||
fn make_dict(
|
||||
&self,
|
||||
it: impl Iterator<Item = (Self::Value, Self::Value)>,
|
||||
) -> Result<Self::Value, marshal::MarshalError> {
|
||||
let vm = self.0;
|
||||
let dict = vm.ctx.new_dict();
|
||||
let dict = self.0.ctx.new_dict();
|
||||
for (k, v) in it {
|
||||
dict.set_item(&*k, v, vm).unwrap()
|
||||
dict.set_item(&*k, v, self.0).unwrap()
|
||||
}
|
||||
Ok(dict.into())
|
||||
}
|
||||
|
||||
fn make_slice(
|
||||
&self,
|
||||
start: Self::Value,
|
||||
stop: Self::Value,
|
||||
step: Self::Value,
|
||||
) -> Result<Self::Value, marshal::MarshalError> {
|
||||
use crate::builtins::PySlice;
|
||||
let vm = self.0;
|
||||
Ok(PySlice {
|
||||
start: if vm.is_none(&start) {
|
||||
None
|
||||
} else {
|
||||
Some(start)
|
||||
},
|
||||
stop,
|
||||
step: if vm.is_none(&step) { None } else { Some(step) },
|
||||
}
|
||||
.into_ref(&vm.ctx)
|
||||
.into())
|
||||
}
|
||||
fn constant_bag(self) -> Self::ConstantBag {
|
||||
PyObjBag(&self.0.ctx)
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn loads(pybuffer: PyBuffer, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
let buf = pybuffer.as_contiguous().ok_or_else(|| {
|
||||
vm.new_buffer_error("Buffer provided to marshal.loads() is not contiguous")
|
||||
})?;
|
||||
marshal::deserialize_value(&mut &buf[..], PyMarshalBag(vm)).map_err(|e| match e {
|
||||
marshal::MarshalError::Eof => vm.new_exception_msg(
|
||||
vm.ctx.exceptions.eof_error.to_owned(),
|
||||
"marshal data too short".into(),
|
||||
),
|
||||
marshal::MarshalError::InvalidBytecode => {
|
||||
vm.new_value_error("Couldn't deserialize python bytecode")
|
||||
}
|
||||
marshal::MarshalError::InvalidUtf8 => {
|
||||
vm.new_value_error("invalid utf8 in marshalled string")
|
||||
}
|
||||
marshal::MarshalError::InvalidLocation => {
|
||||
vm.new_value_error("invalid location in marshalled object")
|
||||
}
|
||||
marshal::MarshalError::BadType => {
|
||||
vm.new_value_error("bad marshal data (unknown type code)")
|
||||
}
|
||||
})
|
||||
#[derive(FromArgs)]
|
||||
struct LoadsArgs {
|
||||
#[pyarg(any)]
|
||||
data: PyBuffer,
|
||||
#[pyarg(named, default = true)]
|
||||
allow_code: bool,
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn load(f: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
let read_res = vm.call_method(&f, "read", ())?;
|
||||
fn loads(args: LoadsArgs, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
let LoadsArgs {
|
||||
data: pybuffer,
|
||||
allow_code,
|
||||
} = args;
|
||||
let buf = pybuffer.as_contiguous().ok_or_else(|| {
|
||||
vm.new_buffer_error("Buffer provided to marshal.loads() is not contiguous")
|
||||
})?;
|
||||
|
||||
let result =
|
||||
marshal::deserialize_value(&mut &buf[..], PyMarshalBag(vm)).map_err(|e| match e {
|
||||
marshal::MarshalError::Eof => vm.new_exception_msg(
|
||||
vm.ctx.exceptions.eof_error.to_owned(),
|
||||
"marshal data too short".into(),
|
||||
),
|
||||
_ => vm.new_value_error("bad marshal data"),
|
||||
})?;
|
||||
if !allow_code {
|
||||
check_no_code(&result, vm)?;
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct LoadArgs {
|
||||
f: PyObjectRef,
|
||||
#[pyarg(named, default = true)]
|
||||
allow_code: bool,
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn load(args: LoadArgs, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
// Read from file object into a buffer, one object at a time.
|
||||
// We read all available data, deserialize one object, then seek
|
||||
// back to just after the consumed bytes.
|
||||
let tell_before = vm
|
||||
.call_method(&args.f, "tell", ())?
|
||||
.try_into_value::<i64>(vm)?;
|
||||
let read_res = vm.call_method(&args.f, "read", ())?;
|
||||
let bytes = ArgBytesLike::try_from_object(vm, read_res)?;
|
||||
loads(PyBuffer::from(bytes), vm)
|
||||
let buf = bytes.borrow_buf();
|
||||
|
||||
let mut rdr: &[u8] = &buf;
|
||||
let len_before = rdr.len();
|
||||
let result =
|
||||
marshal::deserialize_value(&mut rdr, PyMarshalBag(vm)).map_err(|e| match e {
|
||||
marshal::MarshalError::Eof => vm.new_exception_msg(
|
||||
vm.ctx.exceptions.eof_error.to_owned(),
|
||||
"marshal data too short".into(),
|
||||
),
|
||||
_ => vm.new_value_error("bad marshal data"),
|
||||
})?;
|
||||
let consumed = len_before - rdr.len();
|
||||
|
||||
// Seek file to just after the consumed bytes
|
||||
let new_pos = tell_before + consumed as i64;
|
||||
vm.call_method(&args.f, "seek", (new_pos,))?;
|
||||
|
||||
if !args.allow_code {
|
||||
check_no_code(&result, vm)?;
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Reject subclasses of marshallable types (int, float, complex, tuple, etc.).
|
||||
/// Recursively check that no code objects are present.
|
||||
fn check_no_code(obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
if obj.downcast_ref::<PyCode>().is_some() {
|
||||
return Err(vm.new_value_error("unmarshalling code objects is disallowed".to_string()));
|
||||
}
|
||||
if let Some(tup) = obj.downcast_ref::<PyTuple>() {
|
||||
for elem in tup.as_slice() {
|
||||
check_no_code(elem, vm)?;
|
||||
}
|
||||
} else if let Some(list) = obj.downcast_ref::<PyList>() {
|
||||
for elem in list.borrow_vec().iter() {
|
||||
check_no_code(elem, vm)?;
|
||||
}
|
||||
} else if let Some(set) = obj.downcast_ref::<PySet>() {
|
||||
for elem in set.elements() {
|
||||
check_no_code(&elem, vm)?;
|
||||
}
|
||||
} else if let Some(fset) = obj.downcast_ref::<PyFrozenSet>() {
|
||||
for elem in fset.elements() {
|
||||
check_no_code(&elem, vm)?;
|
||||
}
|
||||
} else if let Some(dict) = obj.downcast_ref::<PyDict>() {
|
||||
for (k, v) in dict.into_iter() {
|
||||
check_no_code(&k, vm)?;
|
||||
check_no_code(&v, vm)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_exact_type(obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let cls = obj.class();
|
||||
// bool is a subclass of int but is marshallable
|
||||
if cls.is(PyBool::static_type()) {
|
||||
return Ok(());
|
||||
}
|
||||
for base in [
|
||||
PyInt::static_type(),
|
||||
PyFloat::static_type(),
|
||||
PyComplex::static_type(),
|
||||
PyTuple::static_type(),
|
||||
PyList::static_type(),
|
||||
PyDict::static_type(),
|
||||
PySet::static_type(),
|
||||
PyFrozenSet::static_type(),
|
||||
] {
|
||||
if cls.fast_issubclass(base) && !cls.is(base) {
|
||||
return Err(vm.new_value_error("unmarshallable object".to_string()));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1865,12 +1865,14 @@ impl PyComparisonOp {
|
||||
}
|
||||
|
||||
pub fn eval_ord(self, ord: Ordering) -> bool {
|
||||
let bit = match ord {
|
||||
Ordering::Less => Self::Lt,
|
||||
Ordering::Equal => Self::Eq,
|
||||
Ordering::Greater => Self::Gt,
|
||||
};
|
||||
u8::from(self.0) & u8::from(bit.0) != 0
|
||||
match self {
|
||||
Self::Lt => ord == Ordering::Less,
|
||||
Self::Le => ord != Ordering::Greater,
|
||||
Self::Eq => ord == Ordering::Equal,
|
||||
Self::Ne => ord != Ordering::Equal,
|
||||
Self::Gt => ord == Ordering::Greater,
|
||||
Self::Ge => ord != Ordering::Less,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn swapped(self) -> Self {
|
||||
|
||||
Reference in new issue
Block a user