Skip to content

Commit ef90d09

Browse files
authored
Merge pull request RustPython#3506 from jakearmendariz/main
Marshaling support for ints, floats, strs, lists, dicts, tuples
2 parents d1eb4d0 + 502d5c2 commit ef90d09

5 files changed

Lines changed: 313 additions & 21 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import unittest
2+
import marshal
3+
4+
class MarshalTests(unittest.TestCase):
5+
"""
6+
Testing the (incomplete) marshal module.
7+
"""
8+
9+
def dump_then_load(self, data):
10+
return marshal.loads(marshal.dumps(data))
11+
12+
def test_dump_and_load_int(self):
13+
self.assertEqual(self.dump_then_load(0), 0)
14+
self.assertEqual(self.dump_then_load(-1), -1)
15+
self.assertEqual(self.dump_then_load(1), 1)
16+
self.assertEqual(self.dump_then_load(100000000), 100000000)
17+
18+
def test_dump_and_load_int(self):
19+
self.assertEqual(self.dump_then_load(0.0), 0.0)
20+
self.assertEqual(self.dump_then_load(-10.0), -10.0)
21+
self.assertEqual(self.dump_then_load(10), 10)
22+
23+
def test_dump_and_load_str(self):
24+
self.assertEqual(self.dump_then_load(""), "")
25+
self.assertEqual(self.dump_then_load("Hello, World"), "Hello, World")
26+
27+
def test_dump_and_load_list(self):
28+
self.assertEqual(self.dump_then_load([]), [])
29+
self.assertEqual(self.dump_then_load([1, "hello", 1.0]), [1, "hello", 1.0])
30+
self.assertEqual(self.dump_then_load([[0], ['a','b']]),[[0], ['a','b']])
31+
32+
def test_dump_and_load_tuple(self):
33+
self.assertEqual(self.dump_then_load(()), ())
34+
self.assertEqual(self.dump_then_load((1, "hello", 1.0)), (1, "hello", 1.0))
35+
36+
def test_dump_and_load_dict(self):
37+
self.assertEqual(self.dump_then_load({}), {})
38+
self.assertEqual(self.dump_then_load({'a':1, 1:'a'}), {'a':1, 1:'a'})
39+
self.assertEqual(self.dump_then_load({'a':{'b':2}, 'c':[0.0, 4.0, 6, 9]}), {'a':{'b':2}, 'c':[0.0, 4.0, 6, 9]})
40+
41+
if __name__ == "__main__":
42+
unittest.main()

vm/src/builtins/dict.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ impl PyDict {
6969
&self.entries
7070
}
7171

72+
pub(crate) fn from_entries(entries: DictContentType) -> Self {
73+
Self { entries }
74+
}
75+
7276
#[pyslot]
7377
fn slot_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
7478
PyDict::default().into_pyresult_with_type(vm, cls)

vm/src/dictdatatype.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,22 @@ struct DictEntry<T> {
114114
value: T,
115115
}
116116

117+
impl<T: Clone> DictEntry<T> {
118+
pub(crate) fn as_tuple(&self) -> (PyObjectRef, T) {
119+
(self.key.clone(), self.value.clone())
120+
}
121+
}
122+
123+
impl<T: Clone> Dict<T> {
124+
pub(crate) fn as_kvpairs(&self) -> Vec<(PyObjectRef, T)> {
125+
let entries = &self.inner.read().entries;
126+
entries
127+
.iter()
128+
.filter_map(|entry| entry.as_ref().map(|dict_entry| dict_entry.as_tuple()))
129+
.collect()
130+
}
131+
}
132+
117133
#[derive(Debug, PartialEq)]
118134
pub struct DictSize {
119135
indices_size: usize,

vm/src/protocol/buffer.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
},
1111
sliceable::wrap_index,
1212
types::{Constructor, Unconstructible},
13-
PyObject, PyObjectPayload, PyObjectRef, PyObjectView, PyObjectWrap, PyRef, PyResult,
13+
PyObject, PyObjectPayload, PyObjectRef, PyObjectView, PyObjectWrap, PyRef, PyResult, PyValue,
1414
TryFromBorrowedObject, TypeProtocol, VirtualMachine,
1515
};
1616
use std::{borrow::Cow, fmt::Debug, ops::Range};
@@ -63,6 +63,15 @@ impl PyBuffer {
6363
.then(|| unsafe { self.contiguous_mut_unchecked() })
6464
}
6565

66+
pub fn from_byte_vector(bytes: Vec<u8>, vm: &VirtualMachine) -> Self {
67+
let bytes_len = bytes.len();
68+
PyBuffer::new(
69+
PyValue::into_object(VecBuffer::from(bytes), vm),
70+
BufferDescriptor::simple(bytes_len, true),
71+
&VEC_BUFFER_METHODS,
72+
)
73+
}
74+
6675
/// # Safety
6776
/// assume the buffer is contiguous
6877
pub unsafe fn contiguous_unchecked(&self) -> BorrowedValue<[u8]> {

vm/src/stdlib/marshal.rs

Lines changed: 241 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,274 @@ pub(crate) use decl::make_module;
22

33
#[pymodule(name = "marshal")]
44
mod decl {
5+
/// TODO add support for Booleans, Sets, etc
6+
use ascii::AsciiStr;
7+
use num_bigint::{BigInt, Sign};
8+
use std::slice::Iter;
9+
510
use crate::{
6-
builtins::{PyBytes, PyCode},
11+
builtins::{
12+
dict::DictContentType, PyBytes, PyCode, PyDict, PyFloat, PyInt, PyList, PyStr, PyTuple,
13+
},
714
bytecode,
8-
function::ArgBytesLike,
15+
function::{ArgBytesLike, IntoPyObject},
16+
protocol::PyBuffer,
917
PyObjectRef, PyResult, TryFromObject, VirtualMachine,
1018
};
1119

12-
#[pyfunction]
13-
fn dumps(value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyBytes> {
20+
const STR_BYTE: u8 = b's';
21+
const INT_BYTE: u8 = b'i';
22+
const FLOAT_BYTE: u8 = b'f';
23+
const LIST_BYTE: u8 = b'[';
24+
const TUPLE_BYTE: u8 = b'(';
25+
const DICT_BYTE: u8 = b',';
26+
27+
/// Safely convert usize to 4 le bytes
28+
fn size_to_bytes(x: usize, vm: &VirtualMachine) -> PyResult<[u8; 4]> {
29+
// For marshalling we want to convert lengths to bytes. To save space
30+
// we limit the size to u32 to keep marshalling smaller.
31+
match u32::try_from(x) {
32+
Ok(n) => Ok(n.to_le_bytes()),
33+
Err(_) => {
34+
Err(vm.new_value_error("Size exceeds 2^32 capacity for marshaling.".to_owned()))
35+
}
36+
}
37+
}
38+
39+
/// Dumps a iterator of objects into binary vector.
40+
fn dump_list(pyobjs: Iter<PyObjectRef>, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
41+
let mut byte_list = size_to_bytes(pyobjs.len(), vm)?.to_vec();
42+
// For each element, dump into binary, then add its length and value.
43+
for element in pyobjs {
44+
let element_bytes: Vec<u8> = _dumps(element.clone(), vm)?;
45+
byte_list.extend(size_to_bytes(element_bytes.len(), vm)?);
46+
byte_list.extend(element_bytes)
47+
}
48+
Ok(byte_list)
49+
}
50+
51+
fn _dumps(value: PyObjectRef, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
1452
let r = match_class!(match value {
53+
pyint @ PyInt => {
54+
let (sign, mut int_bytes) = pyint.as_bigint().to_bytes_le();
55+
let sign_byte = match sign {
56+
Sign::Minus => b'-',
57+
Sign::NoSign => b'0',
58+
Sign::Plus => b'+',
59+
};
60+
// Return as [TYPE, SIGN, uint bytes]
61+
int_bytes.insert(0, sign_byte);
62+
int_bytes.push(INT_BYTE);
63+
int_bytes
64+
}
65+
pyfloat @ PyFloat => {
66+
let mut float_bytes = pyfloat.to_f64().to_le_bytes().to_vec();
67+
float_bytes.push(FLOAT_BYTE);
68+
float_bytes
69+
}
70+
pystr @ PyStr => {
71+
let mut str_bytes = pystr.as_str().as_bytes().to_vec();
72+
str_bytes.push(STR_BYTE);
73+
str_bytes
74+
}
75+
pylist @ PyList => {
76+
let pylist_items = pylist.borrow_vec();
77+
let mut list_bytes = dump_list(pylist_items.iter(), vm)?;
78+
list_bytes.push(LIST_BYTE);
79+
list_bytes
80+
}
81+
pytuple @ PyTuple => {
82+
let mut tuple_bytes = dump_list(pytuple.as_slice().iter(), vm)?;
83+
tuple_bytes.push(TUPLE_BYTE);
84+
tuple_bytes
85+
}
86+
pydict @ PyDict => {
87+
let key_value_pairs = pydict._as_dict_inner().clone().as_kvpairs();
88+
// Converts list of tuples to PyObjectRefs of tuples
89+
let elements: Vec<PyObjectRef> = key_value_pairs
90+
.into_iter()
91+
.map(|(k, v)| PyTuple::new_ref(vec![k, v], &vm.ctx).into_pyobject(vm))
92+
.collect();
93+
// Converts list of tuples to list, dump into binary
94+
let mut dict_bytes = dump_list(elements.iter(), vm)?;
95+
dict_bytes.push(LIST_BYTE);
96+
dict_bytes.push(DICT_BYTE);
97+
dict_bytes
98+
}
1599
co @ PyCode => {
16-
PyBytes::from(co.code.map_clone_bag(&bytecode::BasicBag).to_bytes())
100+
// Code is default, doesn't have prefix.
101+
co.code.map_clone_bag(&bytecode::BasicBag).to_bytes()
17102
}
18-
_ =>
103+
_ => {
19104
return Err(vm.new_not_implemented_error(
20-
"TODO: not implemented yet or marshal unsupported type".to_owned()
21-
)),
105+
"TODO: not implemented yet or marshal unsupported type".to_owned(),
106+
));
107+
}
22108
});
23109
Ok(r)
24110
}
25111

112+
#[pyfunction]
113+
fn dumps(value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyBytes> {
114+
Ok(PyBytes::from(_dumps(value, vm)?))
115+
}
116+
26117
#[pyfunction]
27118
fn dump(value: PyObjectRef, f: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
28119
let dumped = dumps(value, vm)?;
29120
vm.call_method(&f, "write", (dumped,))?;
30121
Ok(())
31122
}
32123

124+
/// Read the next 4 bytes of a slice, read as u32, pass as usize.
125+
/// Returns the rest of buffer with the value.
126+
fn eat_length<'a>(bytes: &'a [u8], vm: &VirtualMachine) -> PyResult<(usize, &'a [u8])> {
127+
let (u32_bytes, rest) = bytes.split_at(4);
128+
let length = u32::from_le_bytes(u32_bytes.try_into().map_err(|_| {
129+
vm.new_value_error("Could not read u32 size from byte array".to_owned())
130+
})?);
131+
Ok((length as usize, rest))
132+
}
133+
134+
/// Reads next element from a python list. First by getting element size
135+
/// then by building a pybuffer and "loading" the pyobject.
136+
/// Returns rest of buffer with object.
137+
fn next_element_of_list<'a>(
138+
buf: &'a [u8],
139+
vm: &VirtualMachine,
140+
) -> PyResult<(PyObjectRef, &'a [u8])> {
141+
let (element_length, element_and_rest) = eat_length(buf, vm)?;
142+
let (element_buff, rest) = element_and_rest.split_at(element_length);
143+
let pybuffer = PyBuffer::from_byte_vector(element_buff.to_vec(), vm);
144+
Ok((loads(pybuffer, vm)?, rest))
145+
}
146+
147+
/// Reads a list (or tuple) from a buffer.
148+
fn read_list(buf: &[u8], vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
149+
let (expected_array_len, mut buffer) = eat_length(buf, vm)?;
150+
let mut elements: Vec<PyObjectRef> = Vec::new();
151+
while !buffer.is_empty() {
152+
let (element, rest_of_buffer) = next_element_of_list(buffer, vm)?;
153+
elements.push(element);
154+
buffer = rest_of_buffer;
155+
}
156+
debug_assert!(expected_array_len == elements.len());
157+
Ok(elements)
158+
}
159+
160+
/// Builds a PyDict from iterator of tuple objects
161+
pub fn from_tuples(iterable: Iter<PyObjectRef>, vm: &VirtualMachine) -> PyResult<PyDict> {
162+
let dict = DictContentType::default();
163+
for elem in iterable {
164+
let items = match_class!(match elem.clone() {
165+
pytuple @ PyTuple => pytuple.as_slice().to_vec(),
166+
_ =>
167+
return Err(vm.new_value_error(
168+
"Couldn't unmarshal key:value pair of dictionary".to_owned()
169+
)),
170+
});
171+
// Marshalled tuples are always in format key:value.
172+
dict.insert(
173+
vm,
174+
items.get(0).unwrap().clone(),
175+
items.get(1).unwrap().clone(),
176+
)?;
177+
}
178+
Ok(PyDict::from_entries(dict))
179+
}
180+
33181
#[pyfunction]
34-
fn loads(code_bytes: ArgBytesLike, vm: &VirtualMachine) -> PyResult<PyCode> {
35-
let buf = &*code_bytes.borrow_buf();
36-
let code = bytecode::CodeObject::from_bytes(buf).map_err(|e| match e {
37-
bytecode::CodeDeserializeError::Eof => vm.new_exception_msg(
182+
fn loads(pybuffer: PyBuffer, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
183+
let full_buff = pybuffer.as_contiguous().ok_or_else(|| {
184+
vm.new_buffer_error("Buffer provided to marshal.loads() is not contiguous".to_owned())
185+
})?;
186+
let (type_indicator, buf) = full_buff.split_last().ok_or_else(|| {
187+
vm.new_exception_msg(
38188
vm.ctx.exceptions.eof_error.clone(),
39-
"end of file while deserializing bytecode".to_owned(),
40-
),
41-
_ => vm.new_value_error("Couldn't deserialize python bytecode".to_owned()),
189+
"EOF where object expected.".to_owned(),
190+
)
42191
})?;
43-
Ok(PyCode {
44-
code: vm.map_codeobj(code),
45-
})
192+
match *type_indicator {
193+
INT_BYTE => {
194+
let (sign_byte, uint_bytes) = buf
195+
.split_first()
196+
.ok_or_else(|| vm.new_value_error("EOF where object expected.".to_owned()))?;
197+
let sign = match sign_byte {
198+
b'-' => Sign::Minus,
199+
b'0' => Sign::NoSign,
200+
b'+' => Sign::Plus,
201+
_ => {
202+
return Err(vm.new_value_error(
203+
"Unknown sign byte when trying to unmarshal integer".to_owned(),
204+
))
205+
}
206+
};
207+
let pyint = BigInt::from_bytes_le(sign, uint_bytes);
208+
Ok(pyint.into_pyobject(vm))
209+
}
210+
FLOAT_BYTE => {
211+
let number = f64::from_le_bytes(match buf[..].try_into() {
212+
Ok(byte_array) => byte_array,
213+
Err(e) => {
214+
return Err(vm.new_value_error(format!(
215+
"Expected float, could not load from bytes. {}",
216+
e
217+
)))
218+
}
219+
});
220+
let pyfloat = PyFloat::from(number);
221+
Ok(pyfloat.into_pyobject(vm))
222+
}
223+
STR_BYTE => {
224+
let pystr = PyStr::from(match AsciiStr::from_ascii(buf) {
225+
Ok(ascii_str) => ascii_str,
226+
Err(e) => {
227+
return Err(
228+
vm.new_value_error(format!("Cannot unmarshal bytes to string, {}", e))
229+
)
230+
}
231+
});
232+
Ok(pystr.into_pyobject(vm))
233+
}
234+
LIST_BYTE => {
235+
let elements = read_list(buf, vm)?;
236+
Ok(elements.into_pyobject(vm))
237+
}
238+
TUPLE_BYTE => {
239+
let elements = read_list(buf, vm)?;
240+
let pytuple = PyTuple::new_ref(elements, &vm.ctx).into_pyobject(vm);
241+
Ok(pytuple)
242+
}
243+
DICT_BYTE => {
244+
let pybuffer = PyBuffer::from_byte_vector(buf[..].to_vec(), vm);
245+
let pydict = match_class!(match loads(pybuffer, vm)? {
246+
pylist @ PyList => from_tuples(pylist.borrow_vec().iter(), vm)?,
247+
_ =>
248+
return Err(vm.new_value_error("Couldn't unmarshal dicitionary.".to_owned())),
249+
});
250+
Ok(pydict.into_pyobject(vm))
251+
}
252+
_ => {
253+
// If prefix is not identifiable, assume CodeObject, error out if it doesn't match.
254+
let code = bytecode::CodeObject::from_bytes(&full_buff).map_err(|e| match e {
255+
bytecode::CodeDeserializeError::Eof => vm.new_exception_msg(
256+
vm.ctx.exceptions.eof_error.clone(),
257+
"End of file while deserializing bytecode".to_owned(),
258+
),
259+
_ => vm.new_value_error("Couldn't deserialize python bytecode".to_owned()),
260+
})?;
261+
Ok(PyCode {
262+
code: vm.map_codeobj(code),
263+
}
264+
.into_pyobject(vm))
265+
}
266+
}
46267
}
47268

48269
#[pyfunction]
49-
fn load(f: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyCode> {
270+
fn load(f: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
50271
let read_res = vm.call_method(&f, "read", ())?;
51272
let bytes = ArgBytesLike::try_from_object(vm, read_res)?;
52-
loads(bytes, vm)
273+
loads(PyBuffer::from(bytes), vm)
53274
}
54275
}

0 commit comments

Comments
 (0)