Skip to content

Commit 0dbc2d1

Browse files
committed
Fix serializing replaced dict keys
1 parent fadf02a commit 0dbc2d1

5 files changed

Lines changed: 77 additions & 138 deletions

File tree

src/ffi/dict.rs

Lines changed: 1 addition & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,9 @@
11
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
22

3-
use pyo3_ffi::{PyObject, Py_hash_t, Py_ssize_t};
4-
use std::os::raw::{c_char, c_void};
3+
use pyo3_ffi::{PyDictObject, PyObject, Py_ssize_t};
54

65
#[allow(non_snake_case)]
76
#[inline(always)]
87
pub unsafe fn PyDict_GET_SIZE(op: *mut PyObject) -> Py_ssize_t {
98
(*op.cast::<PyDictObject>()).ma_used
109
}
11-
12-
// dictobject.h
13-
#[repr(C)]
14-
#[derive(Debug, Copy, Clone)]
15-
pub struct PyDictObject {
16-
pub ob_refcnt: pyo3_ffi::Py_ssize_t,
17-
pub ob_type: *mut pyo3_ffi::PyTypeObject,
18-
pub ma_used: pyo3_ffi::Py_ssize_t,
19-
pub ma_version_tag: u64,
20-
pub ma_keys: *mut PyDictKeysObject,
21-
pub ma_values: *mut *mut PyObject,
22-
}
23-
24-
// dict-common.h
25-
#[repr(C)]
26-
#[derive(Debug, Copy, Clone)]
27-
pub struct PyDictKeyEntry {
28-
pub me_hash: Py_hash_t,
29-
pub me_key: *mut PyObject,
30-
pub me_value: *mut PyObject,
31-
}
32-
33-
// dict-common.h
34-
#[repr(C)]
35-
#[derive(Debug, Copy, Clone)]
36-
pub struct PyDictKeysObject {
37-
pub dk_refcnt: Py_ssize_t,
38-
pub dk_size: Py_ssize_t,
39-
pub dk_lookup: *mut c_void, // dict_lookup_func
40-
pub dk_usable: Py_ssize_t,
41-
pub dk_nentries: Py_ssize_t,
42-
pub dk_indices: [c_char; 1],
43-
}
44-
45-
// dictobject.c
46-
#[allow(non_snake_case)]
47-
#[cfg(target_pointer_width = "64")]
48-
fn DK_IXSIZE(dk: *mut PyDictKeysObject) -> isize {
49-
unsafe {
50-
if (*dk).dk_size <= 0xff {
51-
1
52-
} else if (*dk).dk_size <= 0xffff {
53-
2
54-
} else if (*dk).dk_size <= 0xffffffff {
55-
4
56-
} else {
57-
8
58-
}
59-
}
60-
}
61-
62-
// dictobject.c
63-
#[allow(non_snake_case)]
64-
#[cfg(target_pointer_width = "32")]
65-
fn DK_IXSIZE(dk: *mut PyDictKeysObject) -> isize {
66-
unsafe {
67-
if (*dk).dk_size <= 0xff {
68-
1
69-
} else if (*dk).dk_size <= 0xffff {
70-
2
71-
} else {
72-
4
73-
}
74-
}
75-
}
76-
77-
pub struct PyDictIter {
78-
dict_ptr: *mut PyDictObject,
79-
indices_ptr: *mut PyDictKeyEntry,
80-
idx: usize,
81-
len: usize,
82-
}
83-
84-
impl PyDictIter {
85-
pub fn new(dict_ptr: *mut PyDictObject) -> Self {
86-
unsafe {
87-
let dk = (*dict_ptr).ma_keys;
88-
let offset = (*dk).dk_size * DK_IXSIZE(dk);
89-
let indices_ptr = std::mem::transmute::<*mut [c_char; 1], *mut u8>(
90-
std::ptr::addr_of_mut!((*dk).dk_indices),
91-
)
92-
.offset(offset) as *mut PyDictKeyEntry;
93-
let len = PyDict_GET_SIZE(dict_ptr as *mut pyo3_ffi::PyObject) as usize;
94-
PyDictIter {
95-
dict_ptr: dict_ptr,
96-
indices_ptr: indices_ptr,
97-
idx: 0,
98-
len: len,
99-
}
100-
}
101-
}
102-
}
103-
104-
impl Iterator for PyDictIter {
105-
type Item = (*mut pyo3_ffi::PyObject, *mut pyo3_ffi::PyObject);
106-
107-
fn next(&mut self) -> Option<Self::Item> {
108-
unsafe {
109-
if unlikely!(self.idx == self.len) {
110-
None
111-
} else if !(*self.dict_ptr).ma_values.is_null() {
112-
let entry_ptr: *mut PyDictKeyEntry = self.indices_ptr.add(self.idx);
113-
let value = (*(*self.dict_ptr).ma_values).add(self.idx);
114-
self.idx += 1;
115-
Some(((*entry_ptr).me_key, value))
116-
} else {
117-
let mut entry_ptr: *mut PyDictKeyEntry = self.indices_ptr.add(self.idx);
118-
while self.idx < self.len && (*entry_ptr).me_value.is_null() {
119-
entry_ptr = entry_ptr.add(1);
120-
}
121-
let value = (*entry_ptr).me_value;
122-
self.idx += 1;
123-
Some(((*entry_ptr).me_key, value))
124-
}
125-
}
126-
}
127-
}
128-
129-
impl ExactSizeIterator for PyDictIter {
130-
fn len(&self) -> usize {
131-
self.idx - self.len
132-
}
133-
}

src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,12 @@ pub unsafe extern "C" fn dumps(
355355
}
356356

357357
if !kwds.is_null() {
358-
for (arg, val) in crate::ffi::PyDictIter::new(kwds as *mut crate::ffi::PyDictObject) {
358+
let len = unsafe { crate::ffi::PyDict_GET_SIZE(kwds) };
359+
let mut pos = 0isize;
360+
let mut arg: *mut PyObject = null_mut();
361+
let mut val: *mut PyObject = null_mut();
362+
for _ in 0..=len.saturating_sub(1) {
363+
unsafe { _PyDict_Next(kwds, &mut pos, &mut arg, &mut val, null_mut()) };
359364
if arg == typeref::DEFAULT {
360365
if unlikely!(num_args & 2 == 2) {
361366
return raise_dumps_exception(Cow::Borrowed(

src/serialize/dict.rs

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
22

3-
use crate::ffi::{PyDictIter, PyDictObject, PyDict_GET_SIZE};
3+
use crate::ffi::PyDict_GET_SIZE;
44
use crate::opt::*;
55
use crate::serialize::datetime::*;
66
use crate::serialize::datetimelike::*;
@@ -13,6 +13,7 @@ use crate::unicode::*;
1313
use inlinable_string::InlinableString;
1414
use serde::ser::{Serialize, SerializeMap, Serializer};
1515
use smallvec::SmallVec;
16+
use std::ptr::addr_of_mut;
1617
use std::ptr::NonNull;
1718

1819
pub struct Dict {
@@ -48,8 +49,27 @@ impl Serialize for Dict {
4849
S: Serializer,
4950
{
5051
let mut map = serializer.serialize_map(None).unwrap();
52+
let mut pos = 0isize;
5153
let mut str_size: pyo3_ffi::Py_ssize_t = 0;
52-
for (key, value) in PyDictIter::new(self.ptr as *mut PyDictObject) {
54+
let mut key: *mut pyo3_ffi::PyObject = std::ptr::null_mut();
55+
let mut value: *mut pyo3_ffi::PyObject = std::ptr::null_mut();
56+
for _ in 0..=unsafe { PyDict_GET_SIZE(self.ptr) as usize } - 1 {
57+
unsafe {
58+
pyo3_ffi::_PyDict_Next(
59+
self.ptr,
60+
addr_of_mut!(pos),
61+
addr_of_mut!(key),
62+
addr_of_mut!(value),
63+
std::ptr::null_mut(),
64+
)
65+
};
66+
let value = PyObjectSerializer::new(
67+
value,
68+
self.opts,
69+
self.default_calls,
70+
self.recursion + 1,
71+
self.default,
72+
);
5373
if unlikely!(unsafe { ob_type!(key) != STR_TYPE }) {
5474
err!(SerializeError::KeyMustBeStr)
5575
}
@@ -60,14 +80,8 @@ impl Serialize for Dict {
6080
}
6181
map.serialize_key(str_from_slice!(data, str_size)).unwrap();
6282
}
63-
let pyvalue = PyObjectSerializer::new(
64-
value,
65-
self.opts,
66-
self.default_calls,
67-
self.recursion + 1,
68-
self.default,
69-
);
70-
map.serialize_value(&pyvalue)?;
83+
84+
map.serialize_value(&value)?;
7185
}
7286
map.end()
7387
}
@@ -108,8 +122,20 @@ impl Serialize for DictSortedKey {
108122
let len = unsafe { PyDict_GET_SIZE(self.ptr) as usize };
109123
let mut items: SmallVec<[(&str, *mut pyo3_ffi::PyObject); 8]> =
110124
SmallVec::with_capacity(len);
125+
let mut pos = 0isize;
111126
let mut str_size: pyo3_ffi::Py_ssize_t = 0;
112-
for (key, value) in PyDictIter::new(self.ptr as *mut PyDictObject) {
127+
let mut key: *mut pyo3_ffi::PyObject = std::ptr::null_mut();
128+
let mut value: *mut pyo3_ffi::PyObject = std::ptr::null_mut();
129+
for _ in 0..=len - 1 {
130+
unsafe {
131+
pyo3_ffi::_PyDict_Next(
132+
self.ptr,
133+
addr_of_mut!(pos),
134+
addr_of_mut!(key),
135+
addr_of_mut!(value),
136+
std::ptr::null_mut(),
137+
)
138+
};
113139
if unlikely!(unsafe { ob_type!(key) != STR_TYPE }) {
114140
err!(SerializeError::KeyMustBeStr)
115141
}
@@ -274,9 +300,21 @@ impl Serialize for DictNonStrKey {
274300
let len = unsafe { PyDict_GET_SIZE(self.ptr) as usize };
275301
let mut items: SmallVec<[(InlinableString, *mut pyo3_ffi::PyObject); 8]> =
276302
SmallVec::with_capacity(len);
303+
let mut pos = 0isize;
277304
let mut str_size: pyo3_ffi::Py_ssize_t = 0;
305+
let mut key: *mut pyo3_ffi::PyObject = std::ptr::null_mut();
306+
let mut value: *mut pyo3_ffi::PyObject = std::ptr::null_mut();
278307
let opts = self.opts & NOT_PASSTHROUGH;
279-
for (key, value) in PyDictIter::new(self.ptr as *mut PyDictObject) {
308+
for _ in 0..=len - 1 {
309+
unsafe {
310+
pyo3_ffi::_PyDict_Next(
311+
self.ptr,
312+
addr_of_mut!(pos),
313+
addr_of_mut!(key),
314+
addr_of_mut!(value),
315+
std::ptr::null_mut(),
316+
)
317+
};
280318
if is_type!(ob_type!(key), STR_TYPE) {
281319
let data = read_utf8_from_str(key, &mut str_size);
282320
if unlikely!(data.is_null()) {

test/test_issue221.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
2+
13
import pytest
24

35
import orjson

test/test_issue274.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
2+
3+
import orjson
4+
5+
6+
def test_pop():
7+
data = {"id": "any", "static": "msg"}
8+
data.pop("id")
9+
data["id"] = "new"
10+
# not b'{"static":"msg","static":"msg"}'
11+
assert orjson.dumps(data) == b'{"static":"msg","id":"new"}'
12+
13+
14+
def test_in_place():
15+
# not an issue
16+
data = {"id": "any", "static": "msg"}
17+
data["id"] = "new"
18+
assert orjson.dumps(data) == b'{"id":"new","static":"msg"}'

0 commit comments

Comments
 (0)