Skip to content

Commit

Permalink
RUST-880 Fix crash when deserializing/serializing Document that con…
Browse files Browse the repository at this point in the history
…tains decimal128 (#285)
  • Loading branch information
patrickfreed authored Aug 4, 2021
1 parent e81ffef commit bed4ff4
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 9 deletions.
1 change: 1 addition & 0 deletions serde-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition = "2018"
[dependencies]
bson = { path = ".." }
serde = { version = "1.0", features = ["derive"] }
hex = "0.4"

[lib]
name = "serde_tests"
Expand Down
22 changes: 21 additions & 1 deletion serde-tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use serde::{self, de::Unexpected, Deserialize, Serialize};
use std::collections::{BTreeMap, HashSet};

use bson::{Bson, Deserializer, Serializer};
use bson::{Bson, Deserializer, Document, Serializer};

macro_rules! bson {
([]) => {{ bson::Bson::Array(Vec::new()) }};
Expand Down Expand Up @@ -635,3 +635,23 @@ fn empty_arrays2() {
});
assert_eq!(v, t!(Deserialize::deserialize(d)));
}

#[test]
fn decimal128() {
#[derive(Serialize, Deserialize)]
struct Wrapper {
d: Bson,
}

// { "d": Decimal128("2") } from "Regular - 2" corpus test
let bytes = hex::decode("180000001364000200000000000000000000000000403000").unwrap();
let doc = Document::from_reader(&mut bytes.as_slice()).unwrap();

assert!(matches!(doc.get("d"), Some(Bson::Decimal128(_))));

let deserialized: Wrapper = bson::from_document(doc.clone()).unwrap();
assert!(matches!(deserialized.d, Bson::Decimal128(_)));

let round = bson::to_document(&deserialized).unwrap();
assert_eq!(round, doc);
}
21 changes: 20 additions & 1 deletion src/bson.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ impl Display for Bson {
}) => write!(fmt, "/{}/{}", pattern, options),
Bson::JavaScriptCode(ref code)
| Bson::JavaScriptCodeWithScope(JavaScriptCodeWithScope { ref code, .. }) => {
fmt.write_str(&code)
fmt.write_str(code)
}
Bson::Int32(i) => write!(fmt, "{}", i),
Bson::Int64(i) => write!(fmt, "{}", i),
Expand Down Expand Up @@ -574,6 +574,15 @@ impl Bson {
"$numberDecimal": (v.to_string())
}
}
#[cfg(not(feature = "decimal128"))]
Bson::Decimal128(ref v) => {
doc! {
"$numberDecimalBytes": Bson::Binary(Binary {
bytes: v.bytes.to_vec(),
subtype: BinarySubtype::Generic,
}).to_extended_document()
}
}
Bson::Undefined => {
doc! {
"$undefined": true,
Expand Down Expand Up @@ -666,6 +675,16 @@ impl Bson {
}
}

#[cfg(not(feature = "decimal128"))]
["$numberDecimalBytes"] => {
if let Ok(d) = doc.get_binary_generic("$numberDecimalBytes") {
let boxed_slice = d.clone().into_boxed_slice();
if let Ok(ba) = Box::<[u8; 128 / 8]>::try_from(boxed_slice) {
return Bson::Decimal128(Decimal128 { bytes: *ba });
}
}
}

["$binary"] => {
if let Some(binary) = Binary::from_extended_doc(&doc) {
return Bson::Binary(binary);
Expand Down
2 changes: 1 addition & 1 deletion src/de/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ impl<'de> de::Deserializer<'de> for Deserializer {
Bson::Binary(Binary {
subtype: BinarySubtype::Generic,
ref bytes,
}) => visitor.visit_bytes(&bytes),
}) => visitor.visit_bytes(bytes),
binary @ Bson::Binary(..) => visitor.visit_map(MapDeserializer {
iter: binary.to_extended_document().into_iter(),
value: None,
Expand Down
2 changes: 1 addition & 1 deletion src/extjson/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ impl TryFrom<serde_json::Value> for Bson {
.or_else(|| x.as_f64().map(Bson::from))
.ok_or_else(|| {
Error::invalid_value(
Unexpected::Other(&format!("{}", x).as_str()),
Unexpected::Other(format!("{}", x).as_str()),
&"a number that could fit in i32, i64, or f64",
)
}),
Expand Down
8 changes: 4 additions & 4 deletions src/ser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ pub(crate) fn serialize_bson<W: Write + ?Sized>(

match *val {
Bson::Double(v) => write_f64(writer, v),
Bson::String(ref v) => write_string(writer, &v),
Bson::Array(ref v) => serialize_array(writer, &v),
Bson::String(ref v) => write_string(writer, v),
Bson::Array(ref v) => serialize_array(writer, v),
Bson::Document(ref v) => v.to_writer(writer),
Bson::Boolean(v) => writer
.write_all(&[if v { 0x01 } else { 0x00 }])
Expand All @@ -123,7 +123,7 @@ pub(crate) fn serialize_bson<W: Write + ?Sized>(
write_cstring(writer, pattern)?;
write_cstring(writer, options)
}
Bson::JavaScriptCode(ref code) => write_string(writer, &code),
Bson::JavaScriptCode(ref code) => write_string(writer, code),
Bson::ObjectId(ref id) => writer.write_all(&id.bytes()).map_err(From::from),
Bson::JavaScriptCodeWithScope(JavaScriptCodeWithScope {
ref code,
Expand Down Expand Up @@ -160,7 +160,7 @@ pub(crate) fn serialize_bson<W: Write + ?Sized>(
(v.timestamp() * 1000) + (v.nanosecond() / 1_000_000) as i64,
),
Bson::Null => Ok(()),
Bson::Symbol(ref v) => write_string(writer, &v),
Bson::Symbol(ref v) => write_string(writer, v),
#[cfg(not(feature = "decimal128"))]
Bson::Decimal128(ref v) => {
writer.write_all(&v.bytes)?;
Expand Down
2 changes: 1 addition & 1 deletion src/tests/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ fn test_datetime_helpers() {
}
}
}"#;
let json: Value = serde_json::from_str(&date).unwrap();
let json: Value = serde_json::from_str(date).unwrap();
let b: B = serde_json::from_value(json).unwrap();
let expected: chrono::DateTime<chrono::Utc> =
chrono::DateTime::from_str("2020-06-09 10:58:07.095 UTC").unwrap();
Expand Down

0 comments on commit bed4ff4

Please sign in to comment.