Skip to content

Commit f4efa20

Browse files
committed
Fix off-by-one bug in serde impl for Symbol types
1 parent e92b6b6 commit f4efa20

File tree

1 file changed

+67
-3
lines changed

1 file changed

+67
-3
lines changed

src/serde_impl.rs

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,16 +93,22 @@ macro_rules! impl_serde_for_symbol {
9393
&self,
9494
serializer: T,
9595
) -> ::core::result::Result<T::Ok, T::Error> {
96-
self.value.serialize(serializer)
96+
// When the value is deserialized below, it will be converted back to a non-zero
97+
// type by adding 1 to the value, so we need to account for that here by subtracting
98+
// 1 from the value.
99+
let zeroable_value = self.value.get() - 1;
100+
zeroable_value.serialize(serializer)
97101
}
98102
}
99103

100104
impl<'de> ::serde::Deserialize<'de> for $crate::symbol::$name {
101105
fn deserialize<D: ::serde::Deserializer<'de>>(
102106
deserializer: D,
103107
) -> ::core::result::Result<Self, D::Error> {
104-
let index = <$ty as ::serde::Deserialize<'de>>::deserialize(deserializer)?;
105-
let ::core::option::Option::Some(symbol) = Self::new(index) else {
108+
let zeroable_value = <$ty as ::serde::Deserialize<'de>>::deserialize(deserializer)?;
109+
// Symbol::new() will add 1 to the value to guarantee that the resulting underlying
110+
// index will be non-zero.
111+
let ::core::option::Option::Some(symbol) = Self::new(zeroable_value) else {
106112
return ::core::result::Result::Err(<D::Error as ::serde::de::Error>::custom(
107113
::core::concat!(
108114
"invalid index value for `",
@@ -119,3 +125,61 @@ macro_rules! impl_serde_for_symbol {
119125
impl_serde_for_symbol!(SymbolU16, u16);
120126
impl_serde_for_symbol!(SymbolU32, u32);
121127
impl_serde_for_symbol!(SymbolUsize, usize);
128+
129+
#[cfg(test)]
130+
mod tests {
131+
use crate::{
132+
symbol::{SymbolU16, SymbolU32, SymbolUsize},
133+
Symbol,
134+
};
135+
use serde_json;
136+
137+
fn symbol_round_trip_serializes<S>(symbol: S) -> bool
138+
where
139+
S: Symbol + std::fmt::Debug + serde::Serialize + serde::de::DeserializeOwned + PartialEq,
140+
{
141+
let serialized = serde_json::to_string(&symbol).expect("serialization should succeed");
142+
let deserialized: S =
143+
serde_json::from_str(&serialized).expect("deserialization should succeed");
144+
symbol == deserialized
145+
}
146+
147+
#[test]
148+
fn symbol_u16_round_trips() {
149+
assert!(symbol_round_trip_serializes(
150+
SymbolU16::try_from_usize(0).unwrap()
151+
));
152+
assert!(symbol_round_trip_serializes(
153+
SymbolU16::try_from_usize(42).unwrap()
154+
));
155+
assert!(symbol_round_trip_serializes(
156+
SymbolU16::try_from_usize(u16::MAX as usize - 1).unwrap()
157+
));
158+
}
159+
160+
#[test]
161+
fn symbol_u32_round_trips() {
162+
assert!(symbol_round_trip_serializes(
163+
SymbolU32::try_from_usize(0).unwrap()
164+
));
165+
assert!(symbol_round_trip_serializes(
166+
SymbolU32::try_from_usize(42).unwrap()
167+
));
168+
assert!(symbol_round_trip_serializes(
169+
SymbolU32::try_from_usize(u32::MAX as usize - 1).unwrap()
170+
));
171+
}
172+
173+
#[test]
174+
fn symbol_usize_round_trips() {
175+
assert!(symbol_round_trip_serializes(
176+
SymbolUsize::try_from_usize(0).unwrap()
177+
));
178+
assert!(symbol_round_trip_serializes(
179+
SymbolUsize::try_from_usize(42).unwrap()
180+
));
181+
assert!(symbol_round_trip_serializes(
182+
SymbolUsize::try_from_usize(usize::MAX as usize - 1).unwrap()
183+
));
184+
}
185+
}

0 commit comments

Comments
 (0)