Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,35 @@ pub const INPUT_PADDING: usize = SIMDINPUT_LENGTH;
/// `s[..len]`. The padding is unchanged on return, so it can hold the next document of a
/// buffer that packs several back to back.
///
/// With Serde enabled, the tape can deserialize directly into a typed value, including
/// strings borrowed from the input. [`Deserializer::from_tape`] lets the caller recover
/// the tape allocation afterward; `Tape::deserialize` consumes it instead:
///
/// ```
/// # #[cfg(feature = "serde_impl")]
/// # {
/// use serde::Deserialize;
/// use simd_json::{Buffers, Deserializer, INPUT_PADDING, Tape, fill_tape_padded};
///
/// #[derive(Deserialize)]
/// struct Message<'a> {
/// content: &'a str,
/// }
///
/// let mut input = br#"{"content":"hello\nworld"}"#.to_vec();
/// let len = input.len();
/// input.resize(len + INPUT_PADDING, 0);
/// let mut buffers = Buffers::new(len);
/// let mut tape = Tape::null();
/// fill_tape_padded(&mut input, len, &mut buffers, &mut tape).unwrap();
/// let mut deserializer = Deserializer::from_tape(tape);
/// let message = Message::deserialize(&mut deserializer).unwrap();
/// let tape = deserializer.into_tape().reset();
/// assert_eq!(message.content, "hello\nworld");
/// assert!(tape.0.is_empty()); // Ready for another input, retaining its allocation.
/// # }
/// ```
///
/// # Errors
///
/// Will return `Err` if `s[..len]` is invalid JSON or `s` is shorter than
Expand Down Expand Up @@ -882,6 +911,16 @@ impl Deserializer<'_> {
}

impl<'de> Deserializer<'de> {
/// Creates a deserializer from an existing tape without copying or reparsing it.
/// The allocation can be recovered with [`Self::into_tape`] after deserialization.
#[must_use]
pub fn from_tape(tape: Tape<'de>) -> Self {
Self {
tape: tape.0,
idx: 0,
}
}

/// Extracts the tape from the Deserializer
#[must_use]
pub fn into_tape(self) -> Tape<'de> {
Expand Down
5 changes: 1 addition & 4 deletions src/value/tape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,7 @@ impl<'input> Tape<'input> {
{
use crate::Deserializer;

let mut deserializer = Deserializer {
tape: self.0,
idx: 0,
};
let mut deserializer = Deserializer::from_tape(self);

T::deserialize(&mut deserializer)
}
Expand Down
83 changes: 83 additions & 0 deletions tests/fill_tape_padded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,89 @@ fn invalid_utf8_matches_fill_tape() {
]);
}

#[cfg(feature = "serde_impl")]
#[derive(Debug, serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct Message<'a> {
content: &'a str,
}

#[cfg(feature = "serde_impl")]
#[test]
fn padded_tape_deserializes_borrowed_strings() {
let mut buffers = Buffers::new(256);
for offset in [0, 15, 16, 31, 32, 63, 64] {
let prefix = "a".repeat(offset);
let doc = format!(r#"{{"content":"{prefix}\n\ud83c\udf33"}}"#);
let mut input = doc.into_bytes();
let len = input.len();
input.resize(len + INPUT_PADDING, PAD);
let start = input.as_ptr();
let end = start.wrapping_add(len);
let mut tape = Tape::null();
fill_tape_padded(&mut input, len, &mut buffers, &mut tape).unwrap();
let message: Message<'_> = tape.deserialize().unwrap();
assert_eq!(message.content, format!("{prefix}\n🌳"));
assert!((start..end).contains(&message.content.as_ptr()));
assert!(input[len..].iter().all(|&byte| byte == PAD));
}
}

#[cfg(feature = "serde_impl")]
#[test]
fn padded_tape_preserves_struct_field_rejections() {
let mut buffers = Buffers::new(256);
for doc in [
br#"{"content":"a","content":"b"}"#.as_slice(),
br#"{"content":"a","\u0063ontent":"b"}"#,
br#"{"content":"a","extra":1}"#,
] {
let mut reference = doc.to_vec();
assert!(simd_json::serde::from_slice::<Message<'_>>(&mut reference).is_err());
let mut input = doc.to_vec();
input.resize(doc.len() + INPUT_PADDING, PAD);
let mut tape = Tape::null();
fill_tape_padded(&mut input, doc.len(), &mut buffers, &mut tape).unwrap();
assert!(tape.deserialize::<Message<'_>>().is_err());
assert!(input[doc.len()..].iter().all(|&byte| byte == PAD));
}
}

#[cfg(feature = "serde_impl")]
#[test]
fn padded_tape_allocation_survives_typed_success_and_failure() {
use serde::Deserialize as _;
use simd_json::Deserializer;

let mut buffers = Buffers::new(256);
let mut tape = Tape::null();
tape.0.reserve(16);
let allocation = tape.0.as_ptr();
let capacity = tape.0.capacity();
let mut input = br#"{"content":"first\nmessage"}"#.to_vec();
let len = input.len();
input.resize(len + INPUT_PADDING, PAD);
fill_tape_padded(&mut input, len, &mut buffers, &mut tape).unwrap();
let mut deserializer = Deserializer::from_tape(tape);
let message = Message::deserialize(&mut deserializer).unwrap();
let mut tape = deserializer.into_tape().reset();
assert_eq!(tape.0.as_ptr(), allocation);
assert_eq!(tape.0.capacity(), capacity);
assert!(tape.0.is_empty());

let mut other = br#"{"content":"second","extra":true}"#.to_vec();
let len = other.len();
other.resize(len + INPUT_PADDING, PAD);
fill_tape_padded(&mut other, len, &mut buffers, &mut tape).unwrap();
let mut deserializer = Deserializer::from_tape(tape);
assert!(Message::deserialize(&mut deserializer).is_err());
let tape = deserializer.into_tape().reset();
assert_eq!(tape.0.as_ptr(), allocation);
assert_eq!(tape.0.capacity(), capacity);
assert!(tape.0.is_empty());
assert_eq!(message.content, "first\nmessage");
}

/// Deterministic xorshift so the corpus is reproducible (no rand dev-dependency).
struct Rng(u64);

Expand Down
Loading