use std::alloc::GlobalAlloc;
use std::alloc::Layout;
use std::alloc::System;
use std::cell::Cell;
use serde::Deserialize;
use serde::Deserializer;
use serde::de::Visitor;
use serde::de::value::BorrowedStrDeserializer;
use serde::de::value::Error as ValueError;
use serde::de::value::StrDeserializer;
use serde::de::value::StringDeserializer;
use serde::forward_to_deserialize_any;
struct ByteBufIdentifierDeserializer(Vec<u8>);
impl<'de> Deserializer<'de> for ByteBufIdentifierDeserializer {
type Error = ValueError;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.deserialize_identifier(visitor)
}
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_byte_buf(self.0)
}
forward_to_deserialize_any! {
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes
byte_buf option unit unit_struct newtype_struct seq tuple tuple_struct map struct
enum ignored_any
}
}
struct CountingAllocator;
thread_local! {
static ENABLED: Cell<bool> = const { Cell::new(false) };
static ALLOCATIONS: Cell<usize> = const { Cell::new(0) };
static BYTES: Cell<usize> = const { Cell::new(0) };
}
#[global_allocator]
static ALLOCATOR: CountingAllocator = CountingAllocator;
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let pointer = unsafe { System.alloc(layout) };
if !pointer.is_null() {
record(layout.size());
}
pointer
}
unsafe fn dealloc(&self, pointer: *mut u8, layout: Layout) {
unsafe { System.dealloc(pointer, layout) };
}
unsafe fn realloc(&self, pointer: *mut u8, layout: Layout, size: usize) -> *mut u8 {
let result = unsafe { System.realloc(pointer, layout, size) };
if !result.is_null() {
record(size);
}
result
}
}
fn record(size: usize) {
ENABLED.with(|enabled| {
if enabled.get() {
ALLOCATIONS.with(|count| count.set(count.get().saturating_add(1)));
BYTES.with(|bytes| bytes.set(bytes.get().saturating_add(size)));
}
});
}
struct Guard;
impl Guard {
fn start() -> Self {
ALLOCATIONS.with(|count| count.set(0));
BYTES.with(|bytes| bytes.set(0));
ENABLED.with(|enabled| enabled.set(true));
Self
}
fn stop(self) -> (usize, usize) {
ENABLED.with(|enabled| enabled.set(false));
let allocations = ALLOCATIONS.with(Cell::get);
let bytes = BYTES.with(Cell::get);
std::mem::forget(self);
(allocations, bytes)
}
}
impl Drop for Guard {
fn drop(&mut self) {
ENABLED.with(|enabled| enabled.set(false));
}
}
fn measure<T>(action: impl FnOnce() -> T) -> (T, usize, usize) {
let guard = Guard::start();
let result = action();
let (allocations, bytes) = guard.stop();
(result, allocations, bytes)
}
pub(crate) fn assert_string_forms_allocate_nothing<T>(field: &str)
where
for<'de> T: Deserialize<'de>,
{
let (from_str, allocations, bytes) =
measure(|| T::deserialize(StrDeserializer::<ValueError>::new(field)));
assert!(
from_str.is_ok(),
"expected field {field:?} to be recognized"
);
assert_eq!((allocations, bytes), (0, 0), "StrDeserializer");
let (from_borrowed, allocations, bytes) =
measure(|| T::deserialize(BorrowedStrDeserializer::<ValueError>::new(field)));
assert!(
from_borrowed.is_ok(),
"expected field {field:?} to be recognized"
);
assert_eq!((allocations, bytes), (0, 0), "BorrowedStrDeserializer");
let owned_field = field.to_owned();
let (from_owned, allocations, bytes) =
measure(|| T::deserialize(StringDeserializer::<ValueError>::new(owned_field)));
assert!(
from_owned.is_ok(),
"expected field {field:?} to be recognized"
);
assert_eq!((allocations, bytes), (0, 0), "StringDeserializer");
let json = format!("\"{field}\"");
let (from_json, allocations, bytes) = measure(|| serde_json::from_str::<T>(&json));
assert!(
from_json.is_ok(),
"expected JSON field {field:?} to be recognized"
);
assert_eq!((allocations, bytes), (0, 0), "unescaped serde_json field");
}
pub(crate) fn assert_key_forms_match_string<T>(field: &str)
where
for<'de> T: Deserialize<'de>,
{
use serde::de::value::BytesDeserializer;
use serde::de::value::U64Deserializer;
let valid_bytes = field.as_bytes();
let old_valid = String::deserialize(BytesDeserializer::<ValueError>::new(valid_bytes));
let new_valid = T::deserialize(BytesDeserializer::<ValueError>::new(valid_bytes));
assert_eq!(old_valid.is_ok(), new_valid.is_ok());
let owned_bytes = valid_bytes.to_vec();
let old_owned_bytes = String::deserialize(ByteBufIdentifierDeserializer(owned_bytes.clone()));
let new_owned_bytes = T::deserialize(ByteBufIdentifierDeserializer(owned_bytes));
assert_eq!(old_owned_bytes.is_ok(), new_owned_bytes.is_ok());
let old_json_string = String::deserialize(serde_json::Value::String(field.to_owned()));
let new_json_string = T::deserialize(serde_json::Value::String(field.to_owned()));
assert_eq!(old_json_string.is_ok(), new_json_string.is_ok());
let invalid_utf8 = [0xff];
let old_invalid = String::deserialize(BytesDeserializer::<ValueError>::new(&invalid_utf8));
let new_invalid = T::deserialize(BytesDeserializer::<ValueError>::new(&invalid_utf8));
assert_eq!(old_invalid.is_ok(), new_invalid.is_ok());
assert_eq!(
old_invalid.err().map(|error| error.to_string()),
new_invalid.err().map(|error| error.to_string())
);
let old_integer = String::deserialize(U64Deserializer::<ValueError>::new(7));
let new_integer = T::deserialize(U64Deserializer::<ValueError>::new(7));
assert_eq!(old_integer.is_ok(), new_integer.is_ok());
assert_eq!(
old_integer.as_ref().err().map(ToString::to_string),
new_integer.as_ref().err().map(ToString::to_string)
);
assert!(
old_integer.is_err(),
"String key deserialization rejects integers"
);
}
pub(crate) fn assert_escaped_field_is_recognized<T>(json: &str)
where
for<'de> T: Deserialize<'de>,
{
assert!(
serde_json::from_str::<T>(json).is_ok(),
"expected escaped field {json}"
);
}