diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e74809fe9..8a3db72a5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,7 +18,7 @@ jobs: strategy: matrix: - rust: [1.56.0, stable, nightly] + rust: [1.64.0, stable, nightly] steps: - uses: actions/checkout@v3 @@ -84,7 +84,7 @@ jobs: profile: minimal components: llvm-tools-preview override: true - - run: cargo install grcov --force --locked + - run: cargo install grcov --force env: CARGO_TARGET_DIR: target/ - run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index be5160e99..fd9ec5977 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## [0.8.1] - 2023-08-17 +- Fix issues [#277](https://github.com/ron-rs/ron/issues/277) and [#405](https://github.com/ron-rs/ron/issues/405) with `Value::Map` `IntoIter` and extraneous item check for `Value::Seq` ([#406](https://github.com/ron-rs/ron/pull/406)) +- Fix issue [#401](https://github.com/ron-rs/ron/issues/401) with correct raw struct name identifier parsing ([#402](https://github.com/ron-rs/ron/pull/402)) +- Fix issue [#410](https://github.com/ron-rs/ron/issues/410) trailing comma parsing in tuples and `Some` ([#412](https://github.com/ron-rs/ron/pull/412)) +- Error instead of panic when deserializing non-identifiers as field names ([#415](https://github.com/ron-rs/ron/pull/415)) +- [Non-API] Breaking: Fix issue [#307](https://github.com/ron-rs/ron/issues/307) stack overflow with explicit recursion limits in serialising and deserialising ([#420](https://github.com/ron-rs/ron/pull/420)) +- Fix issue [#423](https://github.com/ron-rs/ron/issues/423) deserialising an identifier into a borrowed str ([#424](https://github.com/ron-rs/ron/pull/424)) +- Bump MSRV to 1.57.0 and bump dependency: `base64` to 0.20 ([#431](https://github.com/ron-rs/ron/pull/431)) +- Bump dependency `base64` to 0.21 ([#433](https://github.com/ron-rs/ron/pull/433)) +- Depend on `serde_derive` directly to potentially enable more compilation parallelism ([#441](https://github.com/ron-rs/ron/pull/441)) +- [Non-API] Breaking: Bump `bitflags` dependency to 2.0, changes `serde` impls of `Extensions` ([#443](https://github.com/ron-rs/ron/pull/443)) +- Add `Map::retain` method ([#460](https://github.com/ron-rs/ron/pull/460)) +- Bump MSRV to 1.64.0 and bump dependency: `indexmap` to 2.0 ([#459](https://github.com/ron-rs/ron/pull/459)) + ## [0.8.0] - 2022-08-17 - Bump dependencies: `bitflags` to 1.3, `indexmap` to 1.9 ([#399](https://github.com/ron-rs/ron/pull/399)) diff --git a/Cargo.toml b/Cargo.toml index 7e353ce74..0873de96f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "ron" # Memo: update version in src/lib.rs too (doc link) -version = "0.8.0" +version = "0.8.1" license = "MIT OR Apache-2.0" keywords = ["parser", "serde", "serialization"] authors = [ "Christopher Durham ", "Dzmitry Malyshau ", "Thomas Schaller ", - "Juniper Langenstein ", + "Juniper Tyree ", ] edition = "2021" description = "Rusty Object Notation" @@ -17,20 +17,22 @@ readme = "README.md" homepage = "https://github.com/ron-rs/ron" repository = "https://github.com/ron-rs/ron" documentation = "https://docs.rs/ron/" -rust-version = "1.56.0" +rust-version = "1.64.0" [features] default = [] integer128 = [] [dependencies] -base64 = "0.13" -bitflags = "1.3.2" -indexmap = { version = "1.9.1", features = ["serde-1"], optional = true } -serde = { version = "1.0.60", features = ["serde_derive"] } +base64 = "0.21" +bitflags = { version = "2.0", features = ["serde"] } +indexmap = { version = "2.0", features = ["serde"], optional = true } +# serde supports i128/u128 from 1.0.60 onwards +serde = "1.0.60" +serde_derive = "1.0" [dev-dependencies] +serde = { version = "1.0", features = ["derive"] } serde_bytes = "0.11" -serde_json = "1" -bitflags-serial = { git = "https://github.com/kvark/bitflags-serial" } -option_set = "0.1" +serde_json = "1.0" +option_set = "0.2" diff --git a/README.md b/README.md index 6ed590823..b78422262 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![CI](https://github.com/ron-rs/ron/actions/workflows/ci.yaml/badge.svg)](https://github.com/ron-rs/ron/actions/workflows/ci.yaml) [![codecov](https://img.shields.io/codecov/c/github/ron-rs/ron/codecov?token=x4Q5KA51Ul)](https://codecov.io/gh/ron-rs/ron) [![Crates.io](https://img.shields.io/crates/v/ron.svg)](https://crates.io/crates/ron) -[![MSRV](https://img.shields.io/badge/MSRV-1.56.0-orange)](https://github.com/ron-rs/ron) +[![MSRV](https://img.shields.io/badge/MSRV-1.64.0-orange)](https://github.com/ron-rs/ron) [![Docs](https://docs.rs/ron/badge.svg)](https://docs.rs/ron) [![Matrix](https://img.shields.io/matrix/ron-rs:matrix.org.svg)](https://matrix.to/#/#ron-rs:matrix.org) @@ -18,23 +18,23 @@ GameConfig( // optional struct name window_size: (800, 600), window_title: "PAC-MAN", fullscreen: false, - + mouse_sensitivity: 1.4, key_bindings: { "up": Up, "down": Down, "left": Left, "right": Right, - + // Uncomment to enable WASD controls /* "W": Up, - "A": Down, - "S": Left, + "S": Down, + "A": Left, "D": Right, */ }, - + difficulty_options: ( start_difficulty: Easy, adaptive: false, @@ -102,6 +102,13 @@ Note the following advantages of RON over JSON: * optional struct names improve readability * enums are supported (and less verbose than their JSON representation) +## Limitations + +RON is not designed to be a fully self-describing format (unlike JSON) and is thus not guaranteed to work when [`deserialize_any`](https://docs.rs/serde/latest/serde/trait.Deserializer.html#tymethod.deserialize_any) is used instead of its typed alternatives. In particular, the following Serde attributes are not yet supported: +- `#[serde(tag = "type")]`, i.e. internally tagged enums +- `#[serde(untagged)]`, i.e. untagged enums +- `#[serde(flatten)]`, i.e. flattening an inner struct into its outer container + ## RON syntax overview * Numbers: `42`, `3.14`, `0xFF`, `0b0110` @@ -139,8 +146,12 @@ struct MyStruct { fn main() { let x: MyStruct = ron::from_str("(boolean: true, float: 1.23)").unwrap(); - + println!("RON: {}", ron::to_string(&x).unwrap()); + + println!("Pretty RON: {}", ron::ser::to_string_pretty( + &x, ron::ser::PrettyConfig::default()).unwrap(), + ); } ``` diff --git a/clippy.toml b/clippy.toml index 9e89f43f4..cb8cfcd1c 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,2 +1,2 @@ -msrv = "1.56.0" +msrv = "1.64.0" blacklisted-names = [] diff --git a/docs/grammar.md b/docs/grammar.md index 5c88107e7..3ef2b7f7e 100644 --- a/docs/grammar.md +++ b/docs/grammar.md @@ -18,7 +18,8 @@ RON = [extensions], ws, value, ws; ```ebnf ws = { ws_single | comment }; ws_single = "\n" | "\t" | "\r" | " "; -comment = ["//", { no_newline }, "\n"] | ["/*", { ? any character ? }, "*/"]; +comment = ["//", { no_newline }, "\n"] | ["/*", nested_block_comment, "*/"]; +nested_block_comment = { ? any characters except "/*" or "*/" ? }, [ "/*", nested_block_comment, "*/", nested_block_comment ]; ``` ## Commas @@ -138,3 +139,14 @@ enum_variant_unit = ident; enum_variant_tuple = ident, ws, tuple; enum_variant_named = ident, ws, "(", [named_field, { comma, named_field }, [comma]], ")"; ``` + +## Identifier + +```ebnf +ident = ident_std | ident_raw; +ident_std = ident_std_first, { ident_std_rest }; +ident_std_first = "A" | ... | "Z" | "a" | ... | "z" | "_"; +ident_std_rest = ident_std_first | digit; +ident_raw = "r", "#", ident_raw_rest, { ident_raw_rest }; +ident_raw_rest = ident_std_rest | "." | "+" | "-"; +``` diff --git a/examples/decode.rs b/examples/decode.rs index 56a9f3007..5aa2042fa 100644 --- a/examples/decode.rs +++ b/examples/decode.rs @@ -1,8 +1,9 @@ #![allow(dead_code)] +use std::collections::HashMap; + use ron::de::from_str; use serde::Deserialize; -use std::collections::HashMap; #[derive(Debug, Deserialize)] struct Config { diff --git a/examples/decode_file.rs b/examples/decode_file.rs index 691e9367e..72ca8a05f 100644 --- a/examples/decode_file.rs +++ b/examples/decode_file.rs @@ -1,8 +1,9 @@ #![allow(dead_code)] +use std::{collections::HashMap, fs::File}; + use ron::de::from_reader; use serde::Deserialize; -use std::{collections::HashMap, fs::File}; #[derive(Debug, Deserialize)] struct Config { diff --git a/examples/encode.rs b/examples/encode.rs index ad1a237ba..30e968895 100644 --- a/examples/encode.rs +++ b/examples/encode.rs @@ -1,6 +1,7 @@ +use std::{collections::HashMap, iter::FromIterator}; + use ron::ser::{to_string_pretty, PrettyConfig}; use serde::Serialize; -use std::{collections::HashMap, iter::FromIterator}; #[derive(Serialize)] struct Config { diff --git a/fuzz/.gitignore b/fuzz/.gitignore index 572e03bdf..7fa3e6967 100644 --- a/fuzz/.gitignore +++ b/fuzz/.gitignore @@ -1,4 +1,4 @@ - -target -corpus -artifacts +/artifacts +/corpus +/target +/Cargo.lock diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock deleted file mode 100644 index 254d6fa6f..000000000 --- a/fuzz/Cargo.lock +++ /dev/null @@ -1,109 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "arbitrary" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cc" -version = "1.0.67" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "libfuzzer-sys" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arbitrary 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.67 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "proc-macro2" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "quote" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ron" -version = "0.6.0" -dependencies = [ - "base64 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ron-fuzz" -version = "0.0.0" -dependencies = [ - "libfuzzer-sys 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ron 0.6.0", -] - -[[package]] -name = "serde" -version = "1.0.124" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde_derive 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_derive" -version = "1.0.124" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "syn" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unicode-xid" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[metadata] -"checksum arbitrary 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "698b65a961a9d730fb45b6b0327e20207810c9f61ee421b082b27ba003f49e2b" -"checksum base64 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum cc 1.0.67 (registry+https://github.com/rust-lang/crates.io-index)" = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" -"checksum libfuzzer-sys 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "86c975d637bc2a2f99440932b731491fc34c7f785d239e38af3addd3c2fd0e46" -"checksum proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" -"checksum quote 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -"checksum serde 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)" = "bd761ff957cb2a45fbb9ab3da6512de9de55872866160b23c25f1a841e99d29f" -"checksum serde_derive 1.0.124 (registry+https://github.com/rust-lang/crates.io-index)" = "1800f7693e94e186f5e25a28291ae1570da908aff7d97a095dec1e56ff99069b" -"checksum syn 1.0.64 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f" -"checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 4de1f17b7..a48994356 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -2,7 +2,6 @@ [package] name = "ron-fuzz" version = "0.0.0" -authors = ["Automatically generated"] publish = false edition = "2018" @@ -10,10 +9,10 @@ edition = "2018" cargo-fuzz = true [dependencies] +arbitrary = { version = "1.0", features = ["derive"] } libfuzzer-sys = "0.4" - -[dependencies.ron] -path = ".." +ron = { path = "..", features = ["integer128"] } +serde = { version = "1.0", features = ["derive"] } # Prevent this from interfering with workspaces [workspace] @@ -24,3 +23,9 @@ name = "from_str" path = "fuzz_targets/from_str.rs" test = false doc = false + +[[bin]] +name = "arbitrary" +path = "fuzz_targets/arbitrary.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/arbitrary.rs b/fuzz/fuzz_targets/arbitrary.rs new file mode 100644 index 000000000..eee678a21 --- /dev/null +++ b/fuzz/fuzz_targets/arbitrary.rs @@ -0,0 +1,224 @@ +#![no_main] + +use std::borrow::Cow; +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use arbitrary::{Arbitrary, Unstructured}; +use libfuzzer_sys::fuzz_target; +use serde::{ + de::{MapAccess, Visitor}, + ser::SerializeMap, + Deserialize, Deserializer, Serialize, Serializer, +}; + +fuzz_target!(|data: &[u8]| { + if let Ok(value) = SerdeData::arbitrary(&mut Unstructured::new(data)) { + let ron = match ron::to_string(&value) { + Ok(ron) => ron, + Err(ron::error::Error::ExceededRecursionLimit) => return, + Err(err) => panic!("{:?} -! {:?}", value, err), + }; + let de = match ron::from_str::(&ron) { + Ok(de) => de, + Err(err) if err.code == ron::error::Error::ExceededRecursionLimit => return, + Err(err) => panic!("{:?} -> {:?} -! {:?}", value, ron, err), + }; + assert_eq!(value, de, "{:?} -> {:?} -> {:?}", value, ron, de); + } +}); + +#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Arbitrary)] +enum SerdeData<'a> { + Bool(bool), + I8(i8), + I16(i16), + I32(i32), + I64(i64), + I128(i128), + ISize(isize), + U8(u8), + U16(u16), + U32(u32), + U128(u128), + USize(usize), + F32(F32), + F64(F64), + Char(char), + #[serde(borrow)] + Str(Cow<'a, str>), + String(String), + #[serde(borrow)] + Bytes(Cow<'a, [u8]>), + ByteBuf(Vec), + Option(#[arbitrary(with = arbitrary_recursion_guard)] Option>), + Unit(()), + #[serde(borrow)] + Map(#[arbitrary(with = arbitrary_recursion_guard)] SerdeMap<'a>), + Seq(#[arbitrary(with = arbitrary_recursion_guard)] Vec), + #[serde(borrow)] + Enum(#[arbitrary(with = arbitrary_recursion_guard)] SerdeEnum<'a>), + #[serde(borrow)] + Struct(#[arbitrary(with = arbitrary_recursion_guard)] SerdeStruct<'a>), +} + +fn arbitrary_recursion_guard<'a, T: Arbitrary<'a> + Default>( + u: &mut Unstructured<'a>, +) -> arbitrary::Result { + static RECURSION_DEPTH: AtomicUsize = AtomicUsize::new(0); + + let max_depth = ron::Options::default() + .recursion_limit + .map_or(256, |limit| limit * 2); + + let result = if RECURSION_DEPTH.fetch_add(1, Ordering::Relaxed) < max_depth { + T::arbitrary(u) + } else { + Ok(T::default()) + }; + + RECURSION_DEPTH.fetch_sub(1, Ordering::Relaxed); + + result +} + +#[allow(clippy::enum_variant_names)] +#[derive(Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize, Arbitrary)] +enum SerdeEnum<'a> { + #[default] + UnitVariant, + #[serde(borrow)] + NewtypeVariant(Box>), + TupleVariant(Box>, Box>, Box>), + StructVariant { + a: Box>, + r#fn: Box>, + c: Box>, + }, +} + +#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Arbitrary)] +enum SerdeStruct<'a> { + Unit(SerdeUnitStruct), + #[serde(borrow)] + Newtype(SerdeNewtypeStruct<'a>), + #[serde(borrow)] + Tuple(SerdeTupleStruct<'a>), + #[serde(borrow)] + Struct(SerdeStructStruct<'a>), +} + +impl<'a> Default for SerdeStruct<'a> { + fn default() -> Self { + Self::Unit(SerdeUnitStruct) + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Arbitrary)] +struct SerdeUnitStruct; + +#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Arbitrary)] +#[repr(transparent)] +struct SerdeNewtypeStruct<'a>(#[serde(borrow)] Box>); + +#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Arbitrary)] +struct SerdeTupleStruct<'a>( + #[serde(borrow)] Box>, + Box>, + Box>, +); + +#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Arbitrary)] +struct SerdeStructStruct<'a> { + #[serde(borrow)] + a: Box>, + #[serde(borrow)] + r#fn: Box>, + #[serde(borrow)] + c: Box>, +} + +#[derive(Debug, Serialize, Deserialize, Arbitrary)] +#[repr(transparent)] +struct F32(f32); + +impl PartialEq for F32 { + fn eq(&self, other: &Self) -> bool { + if self.0.is_nan() && other.0.is_nan() { + return true; + } + self.0.to_bits() == other.0.to_bits() + } +} + +impl Eq for F32 {} + +impl Hash for F32 { + fn hash(&self, state: &mut H) { + state.write_u32(self.0.to_bits()) + } +} + +#[derive(Debug, Serialize, Deserialize, Arbitrary)] +#[repr(transparent)] +struct F64(f64); + +impl PartialEq for F64 { + fn eq(&self, other: &Self) -> bool { + if self.0.is_nan() && other.0.is_nan() { + return true; + } + self.0.to_bits() == other.0.to_bits() + } +} + +impl Eq for F64 {} + +impl Hash for F64 { + fn hash(&self, state: &mut H) { + state.write_u64(self.0.to_bits()) + } +} + +#[derive(Debug, Default, PartialEq, Eq, Hash, Arbitrary)] +struct SerdeMap<'a>(Vec<(SerdeData<'a>, SerdeData<'a>)>); + +impl<'a> Serialize for SerdeMap<'a> { + fn serialize(&self, serializer: S) -> Result { + let mut map = serializer.serialize_map(Some(self.0.len()))?; + + for (key, value) in &self.0 { + map.serialize_entry(key, value)?; + } + + map.end() + } +} + +impl<'a, 'de: 'a> Deserialize<'de> for SerdeMap<'a> { + fn deserialize>(deserializer: D) -> Result { + struct SerdeMapVisitor<'a>(PhantomData<&'a ()>); + + impl<'a, 'de: 'a> Visitor<'de> for SerdeMapVisitor<'a> { + type Value = SerdeMap<'a>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a map") + } + + fn visit_map>(self, mut map: A) -> Result { + let mut values = Vec::with_capacity(map.size_hint().unwrap_or(0)); + + while let Some(entry) = map.next_entry()? { + values.push(entry); + } + + Ok(SerdeMap(values)) + } + } + + deserializer.deserialize_map(SerdeMapVisitor(PhantomData)) + } +} diff --git a/fuzz/fuzz_targets/from_str.rs b/fuzz/fuzz_targets/from_str.rs index a6754b0eb..b388385b9 100644 --- a/fuzz/fuzz_targets/from_str.rs +++ b/fuzz/fuzz_targets/from_str.rs @@ -1,6 +1,9 @@ #![no_main] + use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &str| { - let _ = ron::from_str::(data); + if let Ok(value) = ron::from_str::(data) { + let _ = ron::to_string(&value); + } }); diff --git a/rustfmt.toml b/rustfmt.toml index ff0319191..95127ec82 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,4 @@ hard_tabs = false use_field_init_shorthand = true use_try_shorthand = true -edition = "2018" +edition = "2021" diff --git a/src/de/id.rs b/src/de/id.rs index 537ce5690..e7230da5f 100644 --- a/src/de/id.rs +++ b/src/de/id.rs @@ -40,35 +40,35 @@ impl<'a, 'b: 'a, 'c> de::Deserializer<'b> for &'c mut IdDeserializer<'a, 'b> { where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_i8(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_i16(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_i32(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_i64(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } #[cfg(feature = "integer128")] @@ -76,35 +76,35 @@ impl<'a, 'b: 'a, 'c> de::Deserializer<'b> for &'c mut IdDeserializer<'a, 'b> { where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_u8(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_u16(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_u32(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_u64(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } #[cfg(feature = "integer128")] @@ -112,105 +112,105 @@ impl<'a, 'b: 'a, 'c> de::Deserializer<'b> for &'c mut IdDeserializer<'a, 'b> { where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_f32(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_f64(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_char(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_string(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_bytes(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_byte_buf(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_option(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_unit(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_unit_struct(self, _: &'static str, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_newtype_struct(self, _: &'static str, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_seq(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_tuple(self, _: usize, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_tuple_struct(self, _: &'static str, _: usize, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_map(self, _: V) -> Result where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_struct( @@ -222,7 +222,7 @@ impl<'a, 'b: 'a, 'c> de::Deserializer<'b> for &'c mut IdDeserializer<'a, 'b> { where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_enum( @@ -234,7 +234,7 @@ impl<'a, 'b: 'a, 'c> de::Deserializer<'b> for &'c mut IdDeserializer<'a, 'b> { where V: Visitor<'b>, { - unimplemented!("IdDeserializer may only be used for identifiers") + Err(Error::ExpectedIdentifier) } fn deserialize_ignored_any(self, visitor: V) -> Result diff --git a/src/de/mod.rs b/src/de/mod.rs index 0920a67b4..c0e62494b 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -1,15 +1,19 @@ /// Deserialization module. -pub use crate::error::{Error, Position, SpannedError}; - -use serde::de::{self, DeserializeSeed, Deserializer as SerdeError, Visitor}; use std::{borrow::Cow, io, str}; +use base64::Engine; +use serde::{ + de::{self, DeserializeSeed, Deserializer as _, Visitor}, + Deserialize, +}; + use self::{id::IdDeserializer, tag::TagDeserializer}; +pub use crate::error::{Error, Position, SpannedError}; use crate::{ error::{Result, SpannedResult}, extensions::Extensions, options::Options, - parse::{AnyNum, Bytes, ParsedStr}, + parse::{AnyNum, Bytes, ParsedStr, BASE64_ENGINE}, }; mod id; @@ -21,11 +25,12 @@ mod value; /// The RON deserializer. /// /// If you just want to simply deserialize a value, -/// you can use the `from_str` convenience function. +/// you can use the [`from_str`] convenience function. pub struct Deserializer<'de> { bytes: Bytes<'de>, newtype_variant: bool, last_identifier: Option<&'de str>, + recursion_limit: Option, } impl<'de> Deserializer<'de> { @@ -48,6 +53,7 @@ impl<'de> Deserializer<'de> { bytes: Bytes::new(input)?, newtype_variant: false, last_identifier: None, + recursion_limit: options.recursion_limit, }; deserializer.bytes.exts |= options.default_extensions; @@ -92,6 +98,26 @@ where Options::default().from_bytes(s) } +macro_rules! guard_recursion { + ($self:expr => $expr:expr) => {{ + if let Some(limit) = &mut $self.recursion_limit { + if let Some(new_limit) = limit.checked_sub(1) { + *limit = new_limit; + } else { + return Err(Error::ExceededRecursionLimit); + } + } + + let result = $expr; + + if let Some(limit) = &mut $self.recursion_limit { + *limit = limit.saturating_add(1); + } + + result + }}; +} + impl<'de> Deserializer<'de> { /// Check if the remaining bytes are whitespace only, /// otherwise return an error. @@ -105,9 +131,9 @@ impl<'de> Deserializer<'de> { } } - /// Called from `deserialize_any` when a struct was detected. Decides if - /// there is a unit, tuple or usual struct and deserializes it - /// accordingly. + /// Called from [`deserialize_any`][serde::Deserializer::deserialize_any] + /// when a struct was detected. Decides if there is a unit, tuple or usual + /// struct and deserializes it accordingly. /// /// This method assumes there is no identifier left. fn handle_any_struct(&mut self, visitor: V) -> Result @@ -124,13 +150,61 @@ impl<'de> Deserializer<'de> { // first argument is technically incorrect, but ignored anyway self.deserialize_tuple(0, visitor) } else { - // first two arguments are technically incorrect, but ignored anyway - self.deserialize_struct("", &[], visitor) + // giving no name results in worse errors but is necessary here + self.handle_struct_after_name("", visitor) } } else { visitor.visit_unit() } } + + /// Called from + /// [`deserialize_struct`][serde::Deserializer::deserialize_struct], + /// [`struct_variant`][serde::de::VariantAccess::struct_variant], and + /// [`handle_any_struct`][Self::handle_any_struct]. Handles + /// deserialising the enclosing parentheses and everything in between. + /// + /// This method assumes there is no struct name identifier left. + fn handle_struct_after_name( + &mut self, + name_for_pretty_errors_only: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + if self.newtype_variant || self.bytes.consume("(") { + let old_newtype_variant = self.newtype_variant; + self.newtype_variant = false; + + let value = guard_recursion! { self => + visitor + .visit_map(CommaSeparated::new(b')', self)) + .map_err(|err| { + struct_error_name( + err, + if !old_newtype_variant && !name_for_pretty_errors_only.is_empty() { + Some(name_for_pretty_errors_only) + } else { + None + }, + ) + })? + }; + + self.bytes.skip_ws()?; + + if old_newtype_variant || self.bytes.consume(")") { + Ok(value) + } else { + Err(Error::ExpectedStructLikeEnd) + } + } else if name_for_pretty_errors_only.is_empty() { + Err(Error::ExpectedStructLike) + } else { + Err(Error::ExpectedNamedStructLike(name_for_pretty_errors_only)) + } + } } impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { @@ -330,13 +404,18 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { where V: Visitor<'de>, { + if let Some(b'[') = self.bytes.peek() { + let bytes = Vec::::deserialize(self)?; + return visitor.visit_byte_buf(bytes); + } + let res = { let string = self.bytes.string()?; let base64_str = match string { ParsedStr::Allocated(ref s) => s.as_str(), ParsedStr::Slice(s) => s, }; - base64::decode(base64_str) + BASE64_ENGINE.decode(base64_str) }; match res { @@ -357,9 +436,9 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { } { self.bytes.skip_ws()?; - let v = visitor.visit_some(&mut *self)?; + let v = guard_recursion! { self => visitor.visit_some(&mut *self)? }; - self.bytes.skip_ws()?; + self.bytes.comma()?; if self.bytes.consume(")") { Ok(v) @@ -367,7 +446,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { Err(Error::ExpectedOptionEnd) } } else if self.bytes.exts.contains(Extensions::IMPLICIT_SOME) { - visitor.visit_some(&mut *self) + guard_recursion! { self => visitor.visit_some(&mut *self) } } else { Err(Error::ExpectedOption) } @@ -407,7 +486,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { if self.bytes.exts.contains(Extensions::UNWRAP_NEWTYPES) || self.newtype_variant { self.newtype_variant = false; - return visitor.visit_newtype_struct(&mut *self); + return guard_recursion! { self => visitor.visit_newtype_struct(&mut *self) }; } self.bytes.consume_struct_name(name)?; @@ -416,7 +495,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { if self.bytes.consume("(") { self.bytes.skip_ws()?; - let value = visitor.visit_newtype_struct(&mut *self)?; + let value = guard_recursion! { self => visitor.visit_newtype_struct(&mut *self)? }; self.bytes.comma()?; if self.bytes.consume(")") { @@ -431,15 +510,17 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { } } - fn deserialize_seq(mut self, visitor: V) -> Result + fn deserialize_seq(self, visitor: V) -> Result where V: Visitor<'de>, { self.newtype_variant = false; if self.bytes.consume("[") { - let value = visitor.visit_seq(CommaSeparated::new(b']', self))?; - self.bytes.comma()?; + let value = guard_recursion! { self => + visitor.visit_seq(CommaSeparated::new(b']', self))? + }; + self.bytes.skip_ws()?; if self.bytes.consume("]") { Ok(value) @@ -451,7 +532,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { } } - fn deserialize_tuple(mut self, _len: usize, visitor: V) -> Result + fn deserialize_tuple(self, _len: usize, visitor: V) -> Result where V: Visitor<'de>, { @@ -459,8 +540,10 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { let old_newtype_variant = self.newtype_variant; self.newtype_variant = false; - let value = visitor.visit_seq(CommaSeparated::new(b')', self))?; - self.bytes.comma()?; + let value = guard_recursion! { self => + visitor.visit_seq(CommaSeparated::new(b')', self))? + }; + self.bytes.skip_ws()?; if old_newtype_variant || self.bytes.consume(")") { Ok(value) @@ -491,15 +574,17 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { }) } - fn deserialize_map(mut self, visitor: V) -> Result + fn deserialize_map(self, visitor: V) -> Result where V: Visitor<'de>, { self.newtype_variant = false; if self.bytes.consume("{") { - let value = visitor.visit_map(CommaSeparated::new(b'}', self))?; - self.bytes.comma()?; + let value = guard_recursion! { self => + visitor.visit_map(CommaSeparated::new(b'}', self))? + }; + self.bytes.skip_ws()?; if self.bytes.consume("}") { Ok(value) @@ -512,7 +597,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { } fn deserialize_struct( - mut self, + self, name: &'static str, _fields: &'static [&'static str], visitor: V, @@ -526,35 +611,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { self.bytes.skip_ws()?; - if self.newtype_variant || self.bytes.consume("(") { - let old_newtype_variant = self.newtype_variant; - self.newtype_variant = false; - - let value = visitor - .visit_map(CommaSeparated::new(b')', self)) - .map_err(|err| { - struct_error_name( - err, - if !old_newtype_variant && !name.is_empty() { - Some(name) - } else { - None - }, - ) - })?; - - self.bytes.comma()?; - - if old_newtype_variant || self.bytes.consume(")") { - Ok(value) - } else { - Err(Error::ExpectedStructLikeEnd) - } - } else if name.is_empty() { - Err(Error::ExpectedStructLike) - } else { - Err(Error::ExpectedNamedStructLike(name)) - } + self.handle_struct_after_name(name, visitor) } fn deserialize_enum( @@ -568,7 +625,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { { self.newtype_variant = false; - match visitor.visit_enum(Enum::new(self)) { + match guard_recursion! { self => visitor.visit_enum(Enum::new(self)) } { Ok(value) => Ok(value), Err(Error::NoSuchEnumVariant { expected, @@ -591,7 +648,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { self.last_identifier = Some(identifier); - visitor.visit_str(identifier) + visitor.visit_borrowed_str(identifier) } fn deserialize_ignored_any(self, visitor: V) -> Result @@ -642,7 +699,7 @@ impl<'de, 'a> de::SeqAccess<'de> for CommaSeparated<'a, 'de> { T: DeserializeSeed<'de>, { if self.has_element()? { - let res = seed.deserialize(&mut *self.de)?; + let res = guard_recursion! { self.de => seed.deserialize(&mut *self.de)? }; self.had_comma = self.de.bytes.comma()?; @@ -662,10 +719,11 @@ impl<'de, 'a> de::MapAccess<'de> for CommaSeparated<'a, 'de> { { if self.has_element()? { if self.terminator == b')' { - seed.deserialize(&mut IdDeserializer::new(&mut *self.de)) - .map(Some) + guard_recursion! { self.de => + seed.deserialize(&mut IdDeserializer::new(&mut *self.de)).map(Some) + } } else { - seed.deserialize(&mut *self.de).map(Some) + guard_recursion! { self.de => seed.deserialize(&mut *self.de).map(Some) } } } else { Ok(None) @@ -681,7 +739,9 @@ impl<'de, 'a> de::MapAccess<'de> for CommaSeparated<'a, 'de> { if self.de.bytes.consume(":") { self.de.bytes.skip_ws()?; - let res = seed.deserialize(&mut TagDeserializer::new(&mut *self.de))?; + let res = guard_recursion! { self.de => + seed.deserialize(&mut TagDeserializer::new(&mut *self.de))? + }; self.had_comma = self.de.bytes.comma()?; @@ -712,7 +772,7 @@ impl<'de, 'a> de::EnumAccess<'de> for Enum<'a, 'de> { { self.de.bytes.skip_ws()?; - let value = seed.deserialize(&mut *self.de)?; + let value = guard_recursion! { self.de => seed.deserialize(&mut *self.de)? }; Ok((value, self)) } @@ -742,9 +802,11 @@ impl<'de, 'a> de::VariantAccess<'de> for Enum<'a, 'de> { .exts .contains(Extensions::UNWRAP_VARIANT_NEWTYPES); - let val = seed - .deserialize(&mut *self.de) - .map_err(|err| struct_error_name(err, newtype_variant))?; + let val = guard_recursion! { self.de => + seed + .deserialize(&mut *self.de) + .map_err(|err| struct_error_name(err, newtype_variant))? + }; self.de.newtype_variant = false; @@ -769,7 +831,7 @@ impl<'de, 'a> de::VariantAccess<'de> for Enum<'a, 'de> { self.de.deserialize_tuple(len, visitor) } - fn struct_variant(self, fields: &'static [&'static str], visitor: V) -> Result + fn struct_variant(self, _fields: &'static [&'static str], visitor: V) -> Result where V: Visitor<'de>, { @@ -778,7 +840,7 @@ impl<'de, 'a> de::VariantAccess<'de> for Enum<'a, 'de> { self.de.bytes.skip_ws()?; self.de - .deserialize_struct("", fields, visitor) + .handle_struct_after_name("", visitor) .map_err(|err| struct_error_name(err, struct_variant)) } } diff --git a/src/de/tests.rs b/src/de/tests.rs index 3e37e4e9d..2c46eb68f 100644 --- a/src/de/tests.rs +++ b/src/de/tests.rs @@ -1,5 +1,5 @@ -use serde::Deserialize; use serde_bytes; +use serde_derive::Deserialize; use crate::{ de::from_str, @@ -162,9 +162,10 @@ fn err(kind: Error, line: usize, col: usize) -> SpannedResult { #[test] fn test_err_wrong_value() { - use self::Error::*; use std::collections::HashMap; + use self::Error::*; + assert_eq!(from_str::("'c'"), err(ExpectedFloat, 1, 1)); assert_eq!(from_str::("'c'"), err(ExpectedString, 1, 1)); assert_eq!(from_str::>("'c'"), err(ExpectedMap, 1, 1)); diff --git a/src/de/value.rs b/src/de/value.rs index 544ee05e3..5883a37ad 100644 --- a/src/de/value.rs +++ b/src/de/value.rs @@ -5,8 +5,10 @@ use serde::{ Deserialize, Deserializer, }; -use crate::error::SpannedResult; -use crate::value::{Map, Number, Value}; +use crate::{ + error::SpannedResult, + value::{Map, Number, Value}, +}; impl std::str::FromStr for Value { type Err = crate::error::SpannedError; @@ -181,9 +183,10 @@ impl<'de> Visitor<'de> for ValueVisitor { #[cfg(test)] mod tests { - use super::*; use std::str::FromStr; + use super::*; + fn eval(s: &str) -> Value { s.parse().expect("Failed to parse") } diff --git a/src/error.rs b/src/error.rs index 7f7cc9b93..406fb058c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,9 @@ -use serde::{de, ser}; use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error}; +use serde::{de, ser}; + +use crate::parse::{is_ident_first_char, is_ident_other_char, is_ident_raw_char, BASE64_ENGINE}; + /// This type represents all possible errors that can occur when /// serializing or deserializing RON data. #[derive(Clone, Debug, PartialEq, Eq)] @@ -85,6 +88,9 @@ pub enum Error { field: &'static str, outer: Option, }, + InvalidIdentifier(String), + SuggestRawIdentifier(String), + ExceededRecursionLimit, } impl fmt::Display for SpannedError { @@ -124,10 +130,19 @@ impl fmt::Display for Error { Error::ExpectedDifferentStructName { expected, ref found, - } => write!(f, "Expected struct `{}` but found `{}`", expected, found), + } => write!( + f, + "Expected struct {} but found {}", + Identifier(expected), + Identifier(found) + ), Error::ExpectedStructLike => f.write_str("Expected opening `(`"), Error::ExpectedNamedStructLike(name) => { - write!(f, "Expected opening `(` for struct `{}`", name) + if name.is_empty() { + f.write_str("Expected only opening `(`, no name, for un-nameable struct") + } else { + write!(f, "Expected opening `(` for struct {}", Identifier(name)) + } } Error::ExpectedStructLikeEnd => f.write_str("Expected closing `)`"), Error::ExpectedUnit => f.write_str("Expected unit"), @@ -136,7 +151,9 @@ impl fmt::Display for Error { Error::ExpectedIdentifier => f.write_str("Expected identifier"), Error::InvalidEscape(s) => f.write_str(s), Error::IntegerOutOfBounds => f.write_str("Integer is out of bounds"), - Error::NoSuchExtension(ref name) => write!(f, "No RON extension named `{}`", name), + Error::NoSuchExtension(ref name) => { + write!(f, "No RON extension named {}", Identifier(name)) + } Error::Utf8Error(ref e) => fmt::Display::fmt(e, f), Error::UnclosedBlockComment => f.write_str("Unclosed block comment"), Error::UnderscoreAtBeginning => { @@ -175,10 +192,10 @@ impl fmt::Display for Error { f.write_str("enum ")?; } - write!(f, "variant named `{}`", found)?; + write!(f, "variant named {}", Identifier(found))?; if let Some(outer) = outer { - write!(f, "in enum `{}`", outer)?; + write!(f, "in enum {}", Identifier(outer))?; } write!( @@ -195,10 +212,10 @@ impl fmt::Display for Error { ref found, ref outer, } => { - write!(f, "Unexpected field named `{}`", found)?; + write!(f, "Unexpected field named {}", Identifier(found))?; if let Some(outer) = outer { - write!(f, "in `{}`", outer)?; + write!(f, "in {}", Identifier(outer))?; } write!( @@ -211,21 +228,28 @@ impl fmt::Display for Error { ) } Error::MissingStructField { field, ref outer } => { - write!(f, "Unexpected missing field `{}`", field)?; + write!(f, "Unexpected missing field {}", Identifier(field))?; match outer { - Some(outer) => write!(f, " in `{}`", outer), + Some(outer) => write!(f, " in {}", Identifier(outer)), None => Ok(()), } } Error::DuplicateStructField { field, ref outer } => { - write!(f, "Unexpected duplicate field `{}`", field)?; + write!(f, "Unexpected duplicate field {}", Identifier(field))?; match outer { - Some(outer) => write!(f, " in `{}`", outer), + Some(outer) => write!(f, " in {}", Identifier(outer)), None => Ok(()), } } + Error::InvalidIdentifier(ref invalid) => write!(f, "Invalid identifier {:?}", invalid), + Error::SuggestRawIdentifier(ref identifier) => write!( + f, + "Found invalid std identifier `{}`, try the raw identifier `r#{}` instead", + identifier, identifier + ), + Error::ExceededRecursionLimit => f.write_str("Exceeded recursion limit, try increasing the limit and using `serde_stacker` to protect against a stack overflow"), } } } @@ -276,15 +300,9 @@ impl de::Error for Error { Float(n) => write!(f, "the floating point number `{}`", n), Char(c) => write!(f, "the UTF-8 character `{}`", c), Str(s) => write!(f, "the string {:?}", s), - Bytes(b) => { - f.write_str("the bytes b\"")?; - - for b in b { - write!(f, "\\x{:02x}", b)?; - } - - f.write_str("\"") - } + Bytes(b) => write!(f, "the bytes \"{}\"", { + base64::display::Base64Display::new(b, &BASE64_ENGINE) + }), Unit => write!(f, "a unit value"), Option => write!(f, "an optional value"), NewtypeStruct => write!(f, "a newtype struct"), @@ -388,13 +406,18 @@ impl fmt::Display for OneOf { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.alts { [] => write!(f, "there are no {}", self.none), - [a1] => write!(f, "expected `{}` instead", a1), - [a1, a2] => write!(f, "expected either `{}` or `{}` instead", a1, a2), + [a1] => write!(f, "expected {} instead", Identifier(a1)), + [a1, a2] => write!( + f, + "expected either {} or {} instead", + Identifier(a1), + Identifier(a2) + ), [a1, ref alts @ ..] => { - write!(f, "expected one of `{}`", a1)?; + write!(f, "expected one of {}", Identifier(a1))?; for alt in alts { - write!(f, ", `{}`", alt)?; + write!(f, ", {}", Identifier(alt))?; } f.write_str(" instead") @@ -402,3 +425,21 @@ impl fmt::Display for OneOf { } } } + +struct Identifier<'a>(&'a str); + +impl<'a> fmt::Display for Identifier<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.0.is_empty() || !self.0.as_bytes().iter().copied().all(is_ident_raw_char) { + return write!(f, "{:?}_[invalid identifier]", self.0); + } + + let mut bytes = self.0.as_bytes().iter().copied(); + + if !bytes.next().map_or(false, is_ident_first_char) || !bytes.all(is_ident_other_char) { + write!(f, "`r#{}`", self.0) + } else { + write!(f, "`{}`", self.0) + } + } +} diff --git a/src/extensions.rs b/src/extensions.rs index 39357ac7d..0455a34dd 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -1,7 +1,7 @@ -use serde::{Deserialize, Serialize}; +use serde_derive::{Deserialize, Serialize}; bitflags::bitflags! { - #[derive(Serialize, Deserialize)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Extensions: usize { const UNWRAP_NEWTYPES = 0x1; const IMPLICIT_SOME = 0x2; @@ -26,3 +26,30 @@ impl Default for Extensions { Extensions::empty() } } + +#[cfg(test)] +mod tests { + use super::Extensions; + + fn roundtrip_extensions(ext: Extensions) { + let ron = crate::to_string(&ext).unwrap(); + let ext2: Extensions = crate::from_str(&ron).unwrap(); + assert_eq!(ext, ext2); + } + + #[test] + fn test_extension_serde() { + roundtrip_extensions(Extensions::default()); + roundtrip_extensions(Extensions::UNWRAP_NEWTYPES); + roundtrip_extensions(Extensions::IMPLICIT_SOME); + roundtrip_extensions(Extensions::UNWRAP_VARIANT_NEWTYPES); + roundtrip_extensions(Extensions::UNWRAP_NEWTYPES | Extensions::IMPLICIT_SOME); + roundtrip_extensions(Extensions::UNWRAP_NEWTYPES | Extensions::UNWRAP_VARIANT_NEWTYPES); + roundtrip_extensions(Extensions::IMPLICIT_SOME | Extensions::UNWRAP_VARIANT_NEWTYPES); + roundtrip_extensions( + Extensions::UNWRAP_NEWTYPES + | Extensions::IMPLICIT_SOME + | Extensions::UNWRAP_VARIANT_NEWTYPES, + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index af7b06bcc..638af2cb0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ #![doc = include_str!("../README.md")] -#![doc(html_root_url = "https://docs.rs/ron/0.8.0")] +#![doc(html_root_url = "https://docs.rs/ron/0.8.1")] pub mod de; pub mod ser; diff --git a/src/options.rs b/src/options.rs index 1e3511812..b0c5816bd 100644 --- a/src/options.rs +++ b/src/options.rs @@ -2,12 +2,15 @@ use std::io; -use serde::{de, ser, Deserialize, Serialize}; +use serde::{de, ser}; +use serde_derive::{Deserialize, Serialize}; -use crate::de::Deserializer; -use crate::error::{Result, SpannedResult}; -use crate::extensions::Extensions; -use crate::ser::{PrettyConfig, Serializer}; +use crate::{ + de::Deserializer, + error::{Result, SpannedResult}, + extensions::Extensions, + ser::{PrettyConfig, Serializer}, +}; /// Roundtrip serde options. /// @@ -36,12 +39,19 @@ pub struct Options { /// activation is NOT included in the output RON. /// No extensions are enabled by default. pub default_extensions: Extensions, + /// Default recursion limit that is checked during serialization and + /// deserialization. + /// If set to `None`, infinite recursion is allowed and stack overflow + /// errors can crash the serialization or deserialization process. + /// Defaults to `Some(128)`, i.e. 128 recursive calls are allowed. + pub recursion_limit: Option, } impl Default for Options { fn default() -> Self { Self { default_extensions: Extensions::empty(), + recursion_limit: Some(128), } } } @@ -60,6 +70,23 @@ impl Options { self.default_extensions &= !default_extension; self } + + #[must_use] + /// Set a maximum recursion limit during serialization and deserialization. + pub fn with_recursion_limit(mut self, recursion_limit: usize) -> Self { + self.recursion_limit = Some(recursion_limit); + self + } + + #[must_use] + /// Disable the recursion limit during serialization and deserialization. + /// + /// If you expect to handle highly recursive datastructures, consider wrapping + /// `ron` with [`serde_stacker`](https://docs.rs/serde_stacker/latest/serde_stacker/). + pub fn without_recursion_limit(mut self) -> Self { + self.recursion_limit = None; + self + } } impl Options { @@ -136,7 +163,11 @@ impl Options { Ok(value) } - /// Serializes `value` into `writer` + /// Serializes `value` into `writer`. + /// + /// This function does not generate any newlines or nice formatting; + /// if you want that, you can use + /// [`to_writer_pretty`][Self::to_writer_pretty] instead. pub fn to_writer(&self, writer: W, value: &T) -> Result<()> where W: io::Write, @@ -159,7 +190,8 @@ impl Options { /// Serializes `value` and returns it as string. /// /// This function does not generate any newlines or nice formatting; - /// if you want that, you can use `to_string_pretty` instead. + /// if you want that, you can use + /// [`to_string_pretty`][Self::to_string_pretty] instead. pub fn to_string(&self, value: &T) -> Result where T: ?Sized + ser::Serialize, diff --git a/src/parse.rs b/src/parse.rs index b0cc632f3..bed7f4491 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -5,11 +5,15 @@ use std::{ str::{from_utf8, from_utf8_unchecked, FromStr}, }; +use base64::engine::general_purpose::{GeneralPurpose, STANDARD}; + use crate::{ error::{Error, Position, Result, SpannedError, SpannedResult}, extensions::Extensions, }; +pub const BASE64_ENGINE: GeneralPurpose = STANDARD; + // We have the following char categories. const INT_CHAR: u8 = 1 << 0; // [0-9A-Fa-f_] const FLOAT_CHAR: u8 = 1 << 1; // [0-9\.Ee+-_] @@ -77,7 +81,7 @@ pub const fn is_ident_other_char(c: u8) -> bool { ENCODINGS[c as usize] & IDENT_OTHER_CHAR != 0 } -const fn is_ident_raw_char(c: u8) -> bool { +pub const fn is_ident_raw_char(c: u8) -> bool { ENCODINGS[c as usize] & IDENT_RAW_CHAR != 0 } @@ -105,7 +109,7 @@ pub enum AnyNum { #[derive(Clone, Copy, Debug)] pub struct Bytes<'a> { - /// Bits set according to the `Extensions` enum. + /// Bits set according to the [`Extensions`] enum. pub exts: Extensions, bytes: &'a [u8], cursor: Position, @@ -175,7 +179,7 @@ impl<'a> Bytes<'a> { fn any_integer(&mut self, sign: i8) -> Result { let base = if self.peek() == Some(b'0') { - match self.bytes.get(1).cloned() { + match self.bytes.get(1).copied() { Some(b'x') => 16, Some(b'b') => 2, Some(b'o') => 8, @@ -450,27 +454,25 @@ impl<'a> Bytes<'a> { pub fn consume_struct_name(&mut self, ident: &'static str) -> Result { if self.check_ident("") { - Ok(false) - } else if ident.is_empty() { - Err(Error::ExpectedStructLike) - } else if self.check_ident(ident) { - let _ = self.advance(ident.len()); - - Ok(true) - } else { - // If the following is not even an identifier, then a missing - // opening `(` seems more likely - let maybe_ident = self - .identifier() - .map_err(|_| Error::ExpectedNamedStructLike(ident))?; + return Ok(false); + } - let found = std::str::from_utf8(maybe_ident).map_err(Error::from)?; + let found_ident = match self.identifier() { + Ok(maybe_ident) => std::str::from_utf8(maybe_ident)?, + Err(Error::SuggestRawIdentifier(found_ident)) if found_ident == ident => { + return Err(Error::SuggestRawIdentifier(found_ident)) + } + Err(_) => return Err(Error::ExpectedNamedStructLike(ident)), + }; - Err(Error::ExpectedDifferentStructName { + if found_ident != ident { + return Err(Error::ExpectedDifferentStructName { expected: ident, - found: String::from(found), - }) + found: String::from(found_ident), + }); } + + Ok(true) } pub fn consume(&mut self, s: &str) -> bool { @@ -584,6 +586,14 @@ impl<'a> Bytes<'a> { pub fn identifier(&mut self) -> Result<&'a [u8]> { let next = self.peek_or_eof()?; if !is_ident_first_char(next) { + if is_ident_raw_char(next) { + let ident_bytes = &self.bytes[..self.next_bytes_contained_in(is_ident_raw_char)]; + + if let Ok(ident) = std::str::from_utf8(ident_bytes) { + return Err(Error::SuggestRawIdentifier(String::from(ident))); + } + } + return Err(Error::ExpectedIdentifier); } @@ -593,7 +603,7 @@ impl<'a> Bytes<'a> { match self.bytes.get(1).ok_or(Error::Eof)? { b'"' => return Err(Error::ExpectedIdentifier), b'#' => { - let after_next = self.bytes.get(2).cloned().unwrap_or_default(); + let after_next = self.bytes.get(2).copied().unwrap_or_default(); // Note: it's important to check this before advancing forward, so that // the value-type deserializer can fall back to parsing it differently. if !is_ident_raw_char(after_next) { @@ -603,10 +613,30 @@ impl<'a> Bytes<'a> { let _ = self.advance(2); self.next_bytes_contained_in(is_ident_raw_char) } - _ => self.next_bytes_contained_in(is_ident_other_char), + _ => { + let std_ident_length = self.next_bytes_contained_in(is_ident_other_char); + let raw_ident_length = self.next_bytes_contained_in(is_ident_raw_char); + + if raw_ident_length > std_ident_length { + if let Ok(ident) = std::str::from_utf8(&self.bytes[..raw_ident_length]) { + return Err(Error::SuggestRawIdentifier(String::from(ident))); + } + } + + std_ident_length + } } } else { - self.next_bytes_contained_in(is_ident_other_char) + let std_ident_length = self.next_bytes_contained_in(is_ident_other_char); + let raw_ident_length = self.next_bytes_contained_in(is_ident_raw_char); + + if raw_ident_length > std_ident_length { + if let Ok(ident) = std::str::from_utf8(&self.bytes[..raw_ident_length]) { + return Err(Error::SuggestRawIdentifier(String::from(ident))); + } + } + + std_ident_length }; let ident = &self.bytes[..length]; @@ -656,11 +686,11 @@ impl<'a> Bytes<'a> { } pub fn peek(&self) -> Option { - self.bytes.first().cloned() + self.bytes.first().copied() } pub fn peek_or_eof(&self) -> Result { - self.bytes.first().cloned().ok_or(Error::Eof) + self.bytes.first().copied().ok_or(Error::Eof) } pub fn signed_integer(&mut self) -> Result diff --git a/src/ser/mod.rs b/src/ser/mod.rs index f723d15e0..b1aefbcba 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -1,18 +1,27 @@ -use serde::{ser, Deserialize, Serialize}; use std::io; +use base64::Engine; +use serde::{ser, ser::Serialize}; +use serde_derive::{Deserialize, Serialize}; + use crate::{ error::{Error, Result}, extensions::Extensions, options::Options, - parse::{is_ident_first_char, is_ident_other_char, LargeSInt, LargeUInt}, + parse::{ + is_ident_first_char, is_ident_other_char, is_ident_raw_char, LargeSInt, LargeUInt, + BASE64_ENGINE, + }, }; #[cfg(test)] mod tests; mod value; -/// Serializes `value` into `writer` +/// Serializes `value` into `writer`. +/// +/// This function does not generate any newlines or nice formatting; +/// if you want that, you can use [`to_writer_pretty`] instead. pub fn to_writer(writer: W, value: &T) -> Result<()> where W: io::Write, @@ -33,7 +42,7 @@ where /// Serializes `value` and returns it as string. /// /// This function does not generate any newlines or nice formatting; -/// if you want that, you can use `to_string_pretty` instead. +/// if you want that, you can use [`to_string_pretty`] instead. pub fn to_string(value: &T) -> Result where T: ?Sized + Serialize, @@ -93,7 +102,7 @@ pub struct PrettyConfig { } impl PrettyConfig { - /// Creates a default `PrettyConfig`. + /// Creates a default [`PrettyConfig`]. pub fn new() -> Self { Default::default() } @@ -103,7 +112,7 @@ impl PrettyConfig { /// (indentation level) 6, everything will be put into the same line, /// without pretty formatting. /// - /// Default: [std::usize::MAX] + /// Default: [usize::MAX] pub fn depth_limit(mut self, depth_limit: usize) -> Self { self.depth_limit = depth_limit; @@ -168,15 +177,22 @@ impl PrettyConfig { self } - /// Configures whether every array should be a single line (true) or a multi line one (false) - /// When false, `["a","b"]` (as well as any array) will serialize to - /// ` + /// Configures whether every array should be a single line (`true`) + /// or a multi line one (`false`). + /// + /// When `false`, `["a","b"]` (as well as any array) will serialize to + /// ``` /// [ /// "a", /// "b", /// ] - /// ` - /// When true, `["a","b"]` (as well as any array) will serialize to `["a","b"]` + /// # ; + /// ``` + /// When `true`, `["a","b"]` (as well as any array) will serialize to + /// ``` + /// ["a","b"] + /// # ; + /// ``` /// /// Default: `false` pub fn compact_arrays(mut self, compact_arrays: bool) -> Self { @@ -187,7 +203,7 @@ impl PrettyConfig { /// Configures extensions /// - /// Default: Extensions::empty() + /// Default: [Extensions::empty()] pub fn extensions(mut self, extensions: Extensions) -> Self { self.extensions = extensions; @@ -198,7 +214,7 @@ impl PrettyConfig { impl Default for PrettyConfig { fn default() -> Self { PrettyConfig { - depth_limit: !0, + depth_limit: usize::MAX, new_line: if cfg!(not(target_os = "windows")) { String::from("\n") } else { @@ -217,27 +233,30 @@ impl Default for PrettyConfig { /// The RON serializer. /// -/// You can just use `to_string` for deserializing a value. -/// If you want it pretty-printed, take a look at the `pretty` module. +/// You can just use [`to_string`] for deserializing a value. +/// If you want it pretty-printed, take a look at [`to_string_pretty`]. pub struct Serializer { output: W, pretty: Option<(PrettyConfig, Pretty)>, default_extensions: Extensions, is_empty: Option, newtype_variant: bool, + recursion_limit: Option, } impl Serializer { - /// Creates a new `Serializer`. + /// Creates a new [`Serializer`]. /// - /// Most of the time you can just use `to_string` or `to_string_pretty`. + /// Most of the time you can just use [`to_string`] or + /// [`to_string_pretty`]. pub fn new(writer: W, config: Option) -> Result { Self::with_options(writer, config, Options::default()) } - /// Creates a new `Serializer`. + /// Creates a new [`Serializer`]. /// - /// Most of the time you can just use `to_string` or `to_string_pretty`. + /// Most of the time you can just use [`to_string`] or + /// [`to_string_pretty`]. pub fn with_options( mut writer: W, config: Option, @@ -275,6 +294,7 @@ impl Serializer { default_extensions: options.default_extensions, is_empty: None, newtype_variant: false, + recursion_limit: options.recursion_limit, }) } @@ -366,8 +386,11 @@ impl Serializer { Ok(()) } - fn write_identifier(&mut self, name: &str) -> io::Result<()> { - let mut bytes = name.as_bytes().iter().cloned(); + fn write_identifier(&mut self, name: &str) -> Result<()> { + if name.is_empty() || !name.as_bytes().iter().copied().all(is_ident_raw_char) { + return Err(Error::InvalidIdentifier(name.into())); + } + let mut bytes = name.as_bytes().iter().copied(); if !bytes.next().map_or(false, is_ident_first_char) || !bytes.all(is_ident_other_char) { self.output.write_all(b"r#")?; } @@ -383,6 +406,26 @@ impl Serializer { } } +macro_rules! guard_recursion { + ($self:expr => $expr:expr) => {{ + if let Some(limit) = &mut $self.recursion_limit { + if let Some(new_limit) = limit.checked_sub(1) { + *limit = new_limit; + } else { + return Err(Error::ExceededRecursionLimit); + } + } + + let result = $expr; + + if let Some(limit) = &mut $self.recursion_limit { + *limit = limit.saturating_add(1); + } + + result + }}; +} + impl<'a, W: io::Write> ser::Serializer for &'a mut Serializer { type Error = Error; type Ok = (); @@ -474,7 +517,7 @@ impl<'a, W: io::Write> ser::Serializer for &'a mut Serializer { } fn serialize_bytes(self, v: &[u8]) -> Result<()> { - self.serialize_str(base64::encode(v).as_str()) + self.serialize_str(BASE64_ENGINE.encode(v).as_str()) } fn serialize_none(self) -> Result<()> { @@ -491,7 +534,7 @@ impl<'a, W: io::Write> ser::Serializer for &'a mut Serializer { if !implicit_some { self.output.write_all(b"Some(")?; } - value.serialize(&mut *self)?; + guard_recursion! { self => value.serialize(&mut *self)? }; if !implicit_some { self.output.write_all(b")")?; } @@ -532,7 +575,7 @@ impl<'a, W: io::Write> ser::Serializer for &'a mut Serializer { if self.extensions().contains(Extensions::UNWRAP_NEWTYPES) || self.newtype_variant { self.newtype_variant = false; - return value.serialize(&mut *self); + return guard_recursion! { self => value.serialize(&mut *self) }; } if self.struct_names() { @@ -540,7 +583,7 @@ impl<'a, W: io::Write> ser::Serializer for &'a mut Serializer { } self.output.write_all(b"(")?; - value.serialize(&mut *self)?; + guard_recursion! { self => value.serialize(&mut *self)? }; self.output.write_all(b")")?; Ok(()) } @@ -562,7 +605,7 @@ impl<'a, W: io::Write> ser::Serializer for &'a mut Serializer { .extensions() .contains(Extensions::UNWRAP_VARIANT_NEWTYPES); - value.serialize(&mut *self)?; + guard_recursion! { self => value.serialize(&mut *self)? }; self.newtype_variant = false; @@ -587,11 +630,7 @@ impl<'a, W: io::Write> ser::Serializer for &'a mut Serializer { pretty.sequence_index.push(0); } - Ok(Compound { - ser: self, - state: State::First, - newtype_variant: false, - }) + Compound::try_new(self, false) } fn serialize_tuple(self, len: usize) -> Result { @@ -608,11 +647,7 @@ impl<'a, W: io::Write> ser::Serializer for &'a mut Serializer { self.start_indent()?; } - Ok(Compound { - ser: self, - state: State::First, - newtype_variant: old_newtype_variant, - }) + Compound::try_new(self, old_newtype_variant) } fn serialize_tuple_struct( @@ -645,11 +680,7 @@ impl<'a, W: io::Write> ser::Serializer for &'a mut Serializer { self.start_indent()?; } - Ok(Compound { - ser: self, - state: State::First, - newtype_variant: false, - }) + Compound::try_new(self, false) } fn serialize_map(self, len: Option) -> Result { @@ -663,11 +694,7 @@ impl<'a, W: io::Write> ser::Serializer for &'a mut Serializer { self.start_indent()?; - Ok(Compound { - ser: self, - state: State::First, - newtype_variant: false, - }) + Compound::try_new(self, false) } fn serialize_struct(self, name: &'static str, len: usize) -> Result { @@ -684,11 +711,7 @@ impl<'a, W: io::Write> ser::Serializer for &'a mut Serializer { self.is_empty = Some(len == 0); self.start_indent()?; - Ok(Compound { - ser: self, - state: State::First, - newtype_variant: old_newtype_variant, - }) + Compound::try_new(self, old_newtype_variant) } fn serialize_struct_variant( @@ -706,11 +729,7 @@ impl<'a, W: io::Write> ser::Serializer for &'a mut Serializer { self.is_empty = Some(len == 0); self.start_indent()?; - Ok(Compound { - ser: self, - state: State::First, - newtype_variant: false, - }) + Compound::try_new(self, false) } } @@ -726,6 +745,32 @@ pub struct Compound<'a, W: io::Write> { newtype_variant: bool, } +impl<'a, W: io::Write> Compound<'a, W> { + fn try_new(ser: &'a mut Serializer, newtype_variant: bool) -> Result { + if let Some(limit) = &mut ser.recursion_limit { + if let Some(new_limit) = limit.checked_sub(1) { + *limit = new_limit; + } else { + return Err(Error::ExceededRecursionLimit); + } + } + + Ok(Compound { + ser, + state: State::First, + newtype_variant, + }) + } +} + +impl<'a, W: io::Write> Drop for Compound<'a, W> { + fn drop(&mut self) { + if let Some(limit) = &mut self.ser.recursion_limit { + *limit = limit.saturating_add(1); + } + } +} + impl<'a, W: io::Write> ser::SerializeSeq for Compound<'a, W> { type Error = Error; type Ok = (); @@ -759,7 +804,7 @@ impl<'a, W: io::Write> ser::SerializeSeq for Compound<'a, W> { } } - value.serialize(&mut *self.ser)?; + guard_recursion! { self.ser => value.serialize(&mut *self.ser)? }; Ok(()) } @@ -813,7 +858,7 @@ impl<'a, W: io::Write> ser::SerializeTuple for Compound<'a, W> { self.ser.indent()?; } - value.serialize(&mut *self.ser)?; + guard_recursion! { self.ser => value.serialize(&mut *self.ser)? }; Ok(()) } @@ -894,7 +939,7 @@ impl<'a, W: io::Write> ser::SerializeMap for Compound<'a, W> { } } self.ser.indent()?; - key.serialize(&mut *self.ser) + guard_recursion! { self.ser => key.serialize(&mut *self.ser) } } fn serialize_value(&mut self, value: &T) -> Result<()> @@ -907,7 +952,7 @@ impl<'a, W: io::Write> ser::SerializeMap for Compound<'a, W> { self.ser.output.write_all(config.separator.as_bytes())?; } - value.serialize(&mut *self.ser)?; + guard_recursion! { self.ser => value.serialize(&mut *self.ser)? }; Ok(()) } @@ -957,7 +1002,7 @@ impl<'a, W: io::Write> ser::SerializeStruct for Compound<'a, W> { self.ser.output.write_all(config.separator.as_bytes())?; } - value.serialize(&mut *self.ser)?; + guard_recursion! { self.ser => value.serialize(&mut *self.ser)? }; Ok(()) } diff --git a/src/ser/tests.rs b/src/ser/tests.rs index f1056e657..fce7f0071 100644 --- a/src/ser/tests.rs +++ b/src/ser/tests.rs @@ -1,5 +1,6 @@ +use serde_derive::Serialize; + use super::to_string; -use serde::Serialize; #[derive(Serialize)] struct EmptyStruct1; diff --git a/src/value.rs b/src/value.rs index 565bfa05e..ba843bc16 100644 --- a/src/value.rs +++ b/src/value.rs @@ -9,13 +9,13 @@ use std::{ use serde::{ de::{DeserializeOwned, DeserializeSeed, Deserializer, MapAccess, SeqAccess, Visitor}, - forward_to_deserialize_any, Deserialize, Serialize, + forward_to_deserialize_any, }; +use serde_derive::{Deserialize, Serialize}; -use crate::de::Error; -use crate::error::Result; +use crate::{de::Error, error::Result}; -/// A `Value` to `Value` map. +/// A [`Value`] to [`Value`] map. /// /// This structure either uses a [BTreeMap](std::collections::BTreeMap) or the /// [IndexMap](indexmap::IndexMap) internally. @@ -26,7 +26,7 @@ use crate::error::Result; pub struct Map(MapInner); impl Map { - /// Creates a new, empty `Map`. + /// Creates a new, empty [`Map`]. pub fn new() -> Map { Default::default() } @@ -76,6 +76,19 @@ impl Map { pub fn values_mut(&mut self) -> impl Iterator + DoubleEndedIterator { self.0.values_mut() } + + /// Retains only the elements specified by the `keep` predicate. + /// + /// In other words, remove all pairs `(k, v)` for which `keep(&k, &mut v)` + /// returns `false`. + /// + /// The elements are visited in iteration order. + pub fn retain(&mut self, keep: F) + where + F: FnMut(&Value, &mut Value) -> bool, + { + self.0.retain(keep); + } } impl FromIterator<(Value, Value)> for Map { @@ -84,6 +97,16 @@ impl FromIterator<(Value, Value)> for Map { } } +impl IntoIterator for Map { + type Item = (Value, Value); + + type IntoIter = ::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + /// Note: equality is only given if both values and order of values match impl Eq for Map {} @@ -131,20 +154,20 @@ type MapInner = std::collections::BTreeMap; #[cfg(feature = "indexmap")] type MapInner = indexmap::IndexMap; -/// A wrapper for a number, which can be either `f64` or `i64`. +/// A wrapper for a number, which can be either [`f64`] or [`i64`]. #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Hash, Ord)] pub enum Number { Integer(i64), Float(Float), } -/// A wrapper for `f64`, which guarantees that the inner value -/// is finite and thus implements `Eq`, `Hash` and `Ord`. +/// A wrapper for [`f64`], which guarantees that the inner value +/// is finite and thus implements [`Eq`], [`Hash`] and [`Ord`]. #[derive(Copy, Clone, Debug)] pub struct Float(f64); impl Float { - /// Construct a new `Float`. + /// Construct a new [`Float`]. pub fn new(v: f64) -> Self { Float(v) } @@ -161,8 +184,8 @@ impl Number { v.into() } - /// Returns the `f64` representation of the number regardless of whether the number is stored - /// as a float or integer. + /// Returns the [`f64`] representation of the [`Number`] regardless of + /// whether the number is stored as a float or integer. /// /// # Example /// @@ -177,7 +200,7 @@ impl Number { self.map_to(|i| i as f64, |f| f) } - /// If the `Number` is a float, return it. Otherwise return `None`. + /// If the [`Number`] is a float, return it. Otherwise return [`None`]. /// /// # Example /// @@ -192,7 +215,7 @@ impl Number { self.map_to(|_| None, Some) } - /// If the `Number` is an integer, return it. Otherwise return `None`. + /// If the [`Number`] is an integer, return it. Otherwise return [`None`]. /// /// # Example /// @@ -248,8 +271,9 @@ impl From for Number { } } -// The following number conversion checks if the integer fits losslessly into an i64, before -// constructing a Number::Integer variant. If not, the conversion defaults to float. +/// The following [`Number`] conversion checks if the integer fits losslessly +/// into an [`i64`], before constructing a [`Number::Integer`] variant. +/// If not, the conversion defaults to [`Number::Float`]. impl From for Number { fn from(i: u64) -> Number { @@ -262,9 +286,9 @@ impl From for Number { } /// Partial equality comparison -/// In order to be able to use `Number` as a mapping key, NaN floating values -/// wrapped in `Float` are equals to each other. It is not the case for -/// underlying `f64` values itself. +/// In order to be able to use [`Number`] as a mapping key, NaN floating values +/// wrapped in [`Float`] are equal to each other. It is not the case for +/// underlying [`f64`] values itself. impl PartialEq for Float { fn eq(&self, other: &Self) -> bool { self.0.is_nan() && other.0.is_nan() || self.0 == other.0 @@ -272,9 +296,9 @@ impl PartialEq for Float { } /// Equality comparison -/// In order to be able to use `Float` as a mapping key, NaN floating values -/// wrapped in `Float` are equals to each other. It is not the case for -/// underlying `f64` values itself. +/// In order to be able to use [`Float`] as a mapping key, NaN floating values +/// wrapped in [`Float`] are equal to each other. It is not the case for +/// underlying [`f64`] values itself. impl Eq for Float {} impl Hash for Float { @@ -284,9 +308,11 @@ impl Hash for Float { } /// Partial ordering comparison -/// In order to be able to use `Number` as a mapping key, NaN floating values -/// wrapped in `Number` are equals to each other and are less then any other -/// floating value. It is not the case for the underlying `f64` values themselves. +/// In order to be able to use [`Number`] as a mapping key, NaN floating values +/// wrapped in [`Number`] are equal to each other and are less then any other +/// floating value. It is not the case for the underlying [`f64`] values +/// themselves. +/// /// ``` /// use ron::value::Number; /// assert!(Number::new(std::f64::NAN) < Number::new(std::f64::NEG_INFINITY)); @@ -304,10 +330,10 @@ impl PartialOrd for Float { } /// Ordering comparison -/// In order to be able to use `Float` as a mapping key, NaN floating values -/// wrapped in `Float` are equals to each other and are less then any other -/// floating value. It is not the case for underlying `f64` values itself. See -/// the `PartialEq` implementation. +/// In order to be able to use [`Float`] as a mapping key, NaN floating values +/// wrapped in [`Float`] are equal to each other and are less then any other +/// floating value. It is not the case for underlying [`f64`] values itself. +/// See the [`PartialEq`] implementation. impl Ord for Float { fn cmp(&self, other: &Self) -> Ordering { self.partial_cmp(other).expect("Bug: Contract violation") @@ -327,7 +353,7 @@ pub enum Value { } impl Value { - /// Tries to deserialize this `Value` into `T`. + /// Tries to deserialize this [`Value`] into `T`. pub fn into_rust(self) -> Result where T: DeserializeOwned, @@ -336,8 +362,8 @@ impl Value { } } -/// Deserializer implementation for RON `Value`. -/// This does not support enums (because `Value` doesn't store them). +/// Deserializer implementation for RON [`Value`]. +/// This does not support enums (because [`Value`] does not store them). impl<'de> Deserializer<'de> for Value { type Error = Error; @@ -354,18 +380,45 @@ impl<'de> Deserializer<'de> for Value { match self { Value::Bool(b) => visitor.visit_bool(b), Value::Char(c) => visitor.visit_char(c), - Value::Map(m) => visitor.visit_map(MapAccessor { - keys: m.keys().cloned().rev().collect(), - values: m.values().cloned().rev().collect(), - }), + Value::Map(m) => { + let old_len = m.len(); + + let mut items: Vec<(Value, Value)> = m.into_iter().collect(); + items.reverse(); + + let value = visitor.visit_map(MapAccessor { + items: &mut items, + value: None, + })?; + + if items.is_empty() { + Ok(value) + } else { + Err(Error::ExpectedDifferentLength { + expected: format!("a map of length {}", old_len - items.len()), + found: old_len, + }) + } + } Value::Number(Number::Float(ref f)) => visitor.visit_f64(f.get()), Value::Number(Number::Integer(i)) => visitor.visit_i64(i), Value::Option(Some(o)) => visitor.visit_some(*o), Value::Option(None) => visitor.visit_none(), Value::String(s) => visitor.visit_string(s), Value::Seq(mut seq) => { + let old_len = seq.len(); + seq.reverse(); - visitor.visit_seq(Seq { seq }) + let value = visitor.visit_seq(Seq { seq: &mut seq })?; + + if seq.is_empty() { + Ok(value) + } else { + Err(Error::ExpectedDifferentLength { + expected: format!("a sequence of length {}", old_len - seq.len()), + found: old_len, + }) + } } Value::Unit => visitor.visit_unit(), } @@ -434,12 +487,12 @@ impl<'de> Deserializer<'de> for Value { } } -struct MapAccessor { - keys: Vec, - values: Vec, +struct MapAccessor<'a> { + items: &'a mut Vec<(Value, Value)>, + value: Option, } -impl<'de> MapAccess<'de> for MapAccessor { +impl<'a, 'de> MapAccess<'de> for MapAccessor<'a> { type Error = Error; fn next_key_seed(&mut self, seed: K) -> Result> @@ -447,28 +500,35 @@ impl<'de> MapAccess<'de> for MapAccessor { K: DeserializeSeed<'de>, { // The `Vec` is reversed, so we can pop to get the originally first element - self.keys - .pop() - .map_or(Ok(None), |v| seed.deserialize(v).map(Some)) + match self.items.pop() { + Some((key, value)) => { + self.value = Some(value); + seed.deserialize(key).map(Some) + } + None => Ok(None), + } } fn next_value_seed(&mut self, seed: V) -> Result where V: DeserializeSeed<'de>, { - // The `Vec` is reversed, so we can pop to get the originally first element - self.values - .pop() - .map(|v| seed.deserialize(v)) - .expect("Contract violation") + match self.value.take() { + Some(value) => seed.deserialize(value), + None => panic!("Contract violation: value before key"), + } + } + + fn size_hint(&self) -> Option { + Some(self.items.len()) } } -struct Seq { - seq: Vec, +struct Seq<'a> { + seq: &'a mut Vec, } -impl<'de> SeqAccess<'de> for Seq { +impl<'a, 'de> SeqAccess<'de> for Seq<'a> { type Error = Error; fn next_element_seed(&mut self, seed: T) -> Result> @@ -480,14 +540,20 @@ impl<'de> SeqAccess<'de> for Seq { .pop() .map_or(Ok(None), |v| seed.deserialize(v).map(Some)) } + + fn size_hint(&self) -> Option { + Some(self.seq.len()) + } } #[cfg(test)] mod tests { - use super::*; - use serde::Deserialize; use std::{collections::BTreeMap, fmt::Debug}; + use serde::Deserialize; + + use super::*; + fn assert_same<'de, T>(s: &'de str) where T: Debug + Deserialize<'de> + PartialEq, diff --git a/tests/123_enum_representation.rs b/tests/123_enum_representation.rs index 3f82b08cd..414350f69 100644 --- a/tests/123_enum_representation.rs +++ b/tests/123_enum_representation.rs @@ -1,6 +1,7 @@ +use std::{cmp::PartialEq, fmt::Debug}; + use ron::{de::from_str, ser::to_string}; use serde::{Deserialize, Serialize}; -use std::{cmp::PartialEq, fmt::Debug}; #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] enum Inner { @@ -107,14 +108,14 @@ fn test_adjacently_a_ser() { bar: 2, different: Inner::Foo, }; - let e = "(type:\"VariantA\",content:(foo:1,bar:2,different:Foo))"; + let e = "(type:VariantA,content:(foo:1,bar:2,different:Foo))"; test_ser(&v, e); } #[test] fn test_adjacently_b_ser() { let v = EnumStructAdjacently::VariantB { foo: 1, bar: 2 }; - let e = "(type:\"VariantB\",content:(foo:1,bar:2))"; + let e = "(type:VariantB,content:(foo:1,bar:2))"; test_ser(&v, e); } @@ -174,7 +175,7 @@ fn test_internally_b_de() { #[test] fn test_adjacently_a_de() { - let s = "(type:\"VariantA\",content:(foo:1,bar:2,different:Foo))"; + let s = "(type:VariantA,content:(foo:1,bar:2,different:Foo))"; let e = EnumStructAdjacently::VariantA { foo: 1, bar: 2, @@ -185,7 +186,7 @@ fn test_adjacently_a_de() { #[test] fn test_adjacently_b_de() { - let s = "(type:\"VariantB\",content:(foo:1,bar:2))"; + let s = "(type:VariantB,content:(foo:1,bar:2))"; let e = EnumStructAdjacently::VariantB { foo: 1, bar: 2 }; test_de(s, e); } diff --git a/tests/147_empty_sets_serialisation.rs b/tests/147_empty_sets_serialisation.rs index 81cabf64f..d93f9dd11 100644 --- a/tests/147_empty_sets_serialisation.rs +++ b/tests/147_empty_sets_serialisation.rs @@ -1,6 +1,7 @@ -use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use serde::{Deserialize, Serialize}; + #[derive(Debug, PartialEq, Deserialize, Serialize)] struct UnitStruct; diff --git a/tests/152_bitflags.rs b/tests/152_bitflags.rs index b51fd5510..b4085b93d 100644 --- a/tests/152_bitflags.rs +++ b/tests/152_bitflags.rs @@ -1,11 +1,11 @@ -use bitflags::*; +use bitflags::bitflags; use option_set::option_set; -#[macro_use] -extern crate bitflags_serial; - bitflags! { - #[derive(serde::Serialize, serde::Deserialize)] + #[derive( + Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, + serde::Serialize, serde::Deserialize, + )] struct TestGood: u8 { const ONE = 1; const TWO = 1 << 1; @@ -21,14 +21,6 @@ option_set! { } } -bitflags_serial! { - struct TestBadTWO: u8 { - const ONE = 1; - const TWO = 1 << 1; - const THREE = 1 << 2; - } -} - #[test] fn test_bitflags() { // Test case provided by jaynus in @@ -39,8 +31,8 @@ fn test_bitflags() { let json_ser_good = serde_json::ser::to_string(&flag_good).unwrap(); let ron_ser_good = ron::ser::to_string(&flag_good).unwrap(); - assert_eq!(json_ser_good, "{\"bits\":3}"); - assert_eq!(ron_ser_good, "(bits:3)"); + assert_eq!(json_ser_good, "\"ONE | TWO\""); + assert_eq!(ron_ser_good, "(\"ONE | TWO\")"); let json_de_good: TestGood = serde_json::de::from_str(json_ser_good.as_str()).unwrap(); let ron_de_good: TestGood = ron::de::from_str(ron_ser_good.as_str()).unwrap(); @@ -62,19 +54,4 @@ fn test_bitflags() { assert_eq!(json_de_bad, flag_bad); assert_eq!(ron_de_bad, flag_bad); - - // bitflags_serial - let flag_bad_two = TestBadTWO::ONE | TestBadTWO::TWO; - - let json_ser_bad_two = serde_json::ser::to_string(&flag_bad_two).unwrap(); - let ron_ser_bad_two = ron::ser::to_string(&flag_bad_two).unwrap(); - - assert_eq!(json_ser_bad_two, "[\"ONE\",\"TWO\"]"); - assert_eq!(ron_ser_bad_two, "[ONE,TWO]"); - - let json_de_bad_two: TestBadTWO = serde_json::de::from_str(json_ser_bad_two.as_str()).unwrap(); - let ron_de_bad_two: TestBadTWO = ron::de::from_str(ron_ser_bad_two.as_str()).unwrap(); - - assert_eq!(json_de_bad_two, flag_bad_two); - assert_eq!(ron_de_bad_two, flag_bad_two); } diff --git a/tests/203_error_positions.rs b/tests/203_error_positions.rs index 0ef486a17..153e3f5ba 100644 --- a/tests/203_error_positions.rs +++ b/tests/203_error_positions.rs @@ -13,7 +13,7 @@ enum Test { StructVariant { a: bool, b: NonZeroU32, c: i32 }, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq)] // GRCOV_EXCL_LINE struct TypeError; impl<'de> Deserialize<'de> for TypeError { diff --git a/tests/207_adjacently_tagged_enum.rs b/tests/207_adjacently_tagged_enum.rs index 46fda5c64..8a81d753e 100644 --- a/tests/207_adjacently_tagged_enum.rs +++ b/tests/207_adjacently_tagged_enum.rs @@ -14,7 +14,7 @@ fn test_adjacently_tagged() { let ron_string = to_string(&source).unwrap(); - assert_eq!(ron_string, "(type:\"Index\",data:1)"); + assert_eq!(ron_string, "(type:Index,data:1)"); let deserialized = from_str::(&ron_string).unwrap(); diff --git a/tests/238_array.rs b/tests/238_array.rs new file mode 100644 index 000000000..8429f804c --- /dev/null +++ b/tests/238_array.rs @@ -0,0 +1,51 @@ +use ron::{ + error::{Error, Position, SpannedError}, + value::{Number, Value}, +}; + +#[test] +fn test_array() { + let array: [i32; 3] = [1, 2, 3]; + + let ser = ron::to_string(&array).unwrap(); + assert_eq!(ser, "(1,2,3)"); + + let de: [i32; 3] = ron::from_str(&ser).unwrap(); + assert_eq!(de, array); + + let value: Value = ron::from_str(&ser).unwrap(); + assert_eq!( + value, + Value::Seq(vec![ + Value::Number(Number::from(1)), + Value::Number(Number::from(2)), + Value::Number(Number::from(3)), + ]) + ); + + let ser = ron::to_string(&value).unwrap(); + assert_eq!(ser, "[1,2,3]"); + + let de: [i32; 3] = value.into_rust().unwrap(); + assert_eq!(de, array); + + // FIXME: fails and hence arrays do not roundtrip + let de: SpannedError = ron::from_str::<[i32; 3]>(&ser).unwrap_err(); + assert_eq!( + de, + SpannedError { + code: Error::ExpectedStructLike, + position: Position { line: 1, col: 1 }, + } + ); + + let value: Value = ron::from_str(&ser).unwrap(); + assert_eq!( + value, + Value::Seq(vec![ + Value::Number(Number::from(1)), + Value::Number(Number::from(2)), + Value::Number(Number::from(3)), + ]) + ); +} diff --git a/tests/250_variant_newtypes.rs b/tests/250_variant_newtypes.rs index f4c76606c..55673e92b 100644 --- a/tests/250_variant_newtypes.rs +++ b/tests/250_variant_newtypes.rs @@ -1,7 +1,10 @@ use std::collections::HashMap; use ron::{ - de::from_str, error::Error, extensions::Extensions, ser::to_string_pretty, ser::PrettyConfig, + de::from_str, + error::Error, + extensions::Extensions, + ser::{to_string_pretty, PrettyConfig}, }; use serde::{Deserialize, Serialize}; diff --git a/tests/307_stack_overflow.rs b/tests/307_stack_overflow.rs new file mode 100644 index 000000000..7076e6e13 Binary files /dev/null and b/tests/307_stack_overflow.rs differ diff --git a/tests/393_serde_errors.rs b/tests/393_serde_errors.rs index f57532bf6..9246e6633 100644 --- a/tests/393_serde_errors.rs +++ b/tests/393_serde_errors.rs @@ -188,13 +188,13 @@ fn test_adjacently_tagged_enum() { // the enum as a struct assert_eq!( - ron::from_str::("(type: \"StructVariant\", content: (d: 4))"), + ron::from_str::("(type: StructVariant, content: (d: 4))"), Err(SpannedError { code: Error::MissingStructField { field: "a", outer: Some(String::from("TestEnumAdjacent")), }, - position: Position { line: 1, col: 39 }, + position: Position { line: 1, col: 37 }, }) ); } diff --git a/tests/401_raw_identifier.rs b/tests/401_raw_identifier.rs new file mode 100644 index 000000000..23e6ecabd --- /dev/null +++ b/tests/401_raw_identifier.rs @@ -0,0 +1,177 @@ +use ron::error::{Error, Position, SpannedError}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename = "Hello World")] +struct InvalidStruct; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename = "")] +struct EmptyStruct; + +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] +#[serde(rename = "Hello+World")] +#[serde(deny_unknown_fields)] +struct RawStruct { + #[serde(rename = "ab.cd-ef")] + field: bool, +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] +enum RawEnum { + #[serde(rename = "Hello-World")] + RawVariant, +} + +#[test] +fn test_invalid_identifiers() { + let ser = ron::ser::to_string_pretty( + &InvalidStruct, + ron::ser::PrettyConfig::default().struct_names(true), + ); + assert_eq!( + ser, + Err(Error::InvalidIdentifier(String::from("Hello World"))) + ); + + let ser = ron::ser::to_string_pretty( + &EmptyStruct, + ron::ser::PrettyConfig::default().struct_names(true), + ); + assert_eq!(ser, Err(Error::InvalidIdentifier(String::from("")))); + + let de = ron::from_str::("Hello World").unwrap_err(); + assert_eq!( + de, + SpannedError { + code: Error::ExpectedDifferentStructName { + expected: "Hello World", + found: String::from("Hello"), + }, + position: Position { line: 1, col: 6 }, + } + ); + + let de = ron::from_str::("").unwrap_err(); + assert_eq!( + de, + SpannedError { + code: Error::ExpectedUnit, + position: Position { line: 1, col: 1 }, + } + ); + + let de = ron::from_str::("r#").unwrap_err(); + assert_eq!( + format!("{}", de), + "1:1: Expected only opening `(`, no name, for un-nameable struct" + ); + + let de = ron::from_str::("").unwrap_err(); + assert_eq!( + de, + SpannedError { + code: Error::ExpectedNamedStructLike("Hello+World"), + position: Position { line: 1, col: 1 }, + }, + ); + + let de = ron::from_str::("r#").unwrap_err(); + assert_eq!( + de, + SpannedError { + code: Error::ExpectedNamedStructLike("Hello+World"), + position: Position { line: 1, col: 1 }, + }, + ); + + let de = ron::from_str::("Hello+World").unwrap_err(); + assert_eq!( + de, + SpannedError { + code: Error::SuggestRawIdentifier(String::from("Hello+World")), + position: Position { line: 1, col: 1 }, + } + ); + + let de = ron::from_str::( + "r#Hello+World( + ab.cd-ef: true, + )", + ) + .unwrap_err(); + assert_eq!( + de, + SpannedError { + code: Error::SuggestRawIdentifier(String::from("ab.cd-ef")), + position: Position { line: 2, col: 9 }, + } + ); + + let de = ron::from_str::( + "r#Hello+World( + r#ab.cd+ef: true, + )", + ) + .unwrap_err(); + assert_eq!( + de, + SpannedError { + code: Error::NoSuchStructField { + expected: &["ab.cd-ef"], + found: String::from("ab.cd+ef"), + outer: Some(String::from("Hello+World")), + }, + position: Position { line: 2, col: 19 }, + } + ); + + let de = ron::from_str::("Hello-World").unwrap_err(); + assert_eq!( + de, + SpannedError { + code: Error::SuggestRawIdentifier(String::from("Hello-World")), + position: Position { line: 1, col: 1 }, + } + ); + + let de = ron::from_str::("r#Hello+World").unwrap_err(); + assert_eq!( + de, + SpannedError { + code: Error::NoSuchEnumVariant { + expected: &["Hello-World"], + found: String::from("Hello+World"), + outer: Some(String::from("RawEnum")), + }, + position: Position { line: 1, col: 14 }, + } + ); + + let de = ron::from_str::("r#+").unwrap_err(); + assert_eq!( + format!("{}", de), + r#"1:4: Expected struct ""_[invalid identifier] but found `r#+`"#, + ); +} + +#[test] +fn test_raw_identifier_roundtrip() { + let val = RawStruct { field: true }; + + let ser = + ron::ser::to_string_pretty(&val, ron::ser::PrettyConfig::default().struct_names(true)) + .unwrap(); + assert_eq!(ser, "r#Hello+World(\n r#ab.cd-ef: true,\n)"); + + let de: RawStruct = ron::from_str(&ser).unwrap(); + assert_eq!(de, val); + + let val = RawEnum::RawVariant; + + let ser = ron::ser::to_string(&val).unwrap(); + assert_eq!(ser, "r#Hello-World"); + + let de: RawEnum = ron::from_str(&ser).unwrap(); + assert_eq!(de, val); +} diff --git a/tests/410_trailing_comma.rs b/tests/410_trailing_comma.rs new file mode 100644 index 000000000..91018a3ef --- /dev/null +++ b/tests/410_trailing_comma.rs @@ -0,0 +1,95 @@ +use std::collections::HashMap; + +use ron::from_str; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Newtype(i32); + +#[derive(Deserialize)] +struct Tuple(i32, i32); + +#[derive(Deserialize)] +#[allow(dead_code)] +struct Struct { + a: i32, + b: i32, +} + +#[derive(Deserialize)] +#[allow(dead_code)] +enum Enum { + Newtype(i32), + Tuple(i32, i32), + Struct { a: i32, b: i32 }, +} + +#[test] +fn test_trailing_comma_some() { + assert!(from_str::>("Some(1)").is_ok()); + assert!(from_str::>("Some(1,)").is_ok()); + assert!(from_str::>("Some(1,,)").is_err()); +} + +#[test] +fn test_trailing_comma_tuple() { + assert!(from_str::<(i32, i32)>("(1,2)").is_ok()); + assert!(from_str::<(i32, i32)>("(1,2,)").is_ok()); + assert!(from_str::<(i32, i32)>("(1,2,,)").is_err()); +} + +#[test] +fn test_trailing_comma_list() { + assert!(from_str::>("[1,2]").is_ok()); + assert!(from_str::>("[1,2,]").is_ok()); + assert!(from_str::>("[1,2,,]").is_err()); +} + +#[test] +fn test_trailing_comma_map() { + assert!(from_str::>("{1:false,2:true}").is_ok()); + assert!(from_str::>("{1:false,2:true,}").is_ok()); + assert!(from_str::>("{1:false,2:true,,}").is_err()); +} + +#[test] +fn test_trailing_comma_newtype_struct() { + assert!(from_str::("(1)").is_ok()); + assert!(from_str::("(1,)").is_ok()); + assert!(from_str::("(1,,)").is_err()); +} + +#[test] +fn test_trailing_comma_tuple_struct() { + assert!(from_str::("(1,2)").is_ok()); + assert!(from_str::("(1,2,)").is_ok()); + assert!(from_str::("(1,2,,)").is_err()); +} + +#[test] +fn test_trailing_comma_struct() { + assert!(from_str::("(a:1,b:2)").is_ok()); + assert!(from_str::("(a:1,b:2,)").is_ok()); + assert!(from_str::("(a:1,b:2,,)").is_err()); +} + +#[test] +fn test_trailing_comma_enum_newtype_variant() { + assert!(from_str::("Newtype(1)").is_ok()); + assert!(from_str::("Newtype(1,)").is_ok()); + assert!(from_str::("Newtype(1,,)").is_err()); +} + +#[test] +fn test_trailing_comma_enum_tuple_variant() { + assert!(from_str::("Tuple(1,2)").is_ok()); + assert!(from_str::("Tuple(1,2,)").is_ok()); + assert!(from_str::("Tuple(1,2,,)").is_err()); +} + +#[test] +fn test_trailing_comma_enum_struct_variant() { + assert!(from_str::("Struct(a:1,b:2)").is_ok()); + assert!(from_str::("Struct(a:1,b:2,)").is_ok()); + assert!(from_str::("Struct(a:1,b:2,,)").is_err()); +} diff --git a/tests/423_de_borrowed_identifier.rs b/tests/423_de_borrowed_identifier.rs new file mode 100644 index 000000000..d29a0050f --- /dev/null +++ b/tests/423_de_borrowed_identifier.rs @@ -0,0 +1,45 @@ +use std::any::Any; + +use serde::{ + de::{MapAccess, Visitor}, + Deserializer, +}; + +#[test] +fn manually_deserialize_dyn() { + let ron = r#"SerializeDyn( + type: "engine_utils::types::registry::tests::Player", + )"#; + + let mut de = ron::Deserializer::from_bytes(ron.as_bytes()).unwrap(); + + let result = de + .deserialize_struct("SerializeDyn", &["type"], SerializeDynVisitor) + .unwrap(); + + assert_eq!( + *result.downcast::>().unwrap(), + Some(( + String::from("type"), + String::from("engine_utils::types::registry::tests::Player") + )) + ); +} + +struct SerializeDynVisitor; + +impl<'de> Visitor<'de> for SerializeDynVisitor { + type Value = Box; + + // GRCOV_EXCL_START + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "a serialize dyn struct") + } + // GRCOV_EXCL_STOP + + fn visit_map>(self, mut map: A) -> Result { + let entry = map.next_entry::<&str, String>()?; + + Ok(Box::new(entry.map(|(k, v)| (String::from(k), v)))) + } +} diff --git a/tests/462_bytes.rs b/tests/462_bytes.rs new file mode 100644 index 000000000..ac9189039 --- /dev/null +++ b/tests/462_bytes.rs @@ -0,0 +1,22 @@ +#[test] +fn test_deserialise_byte_slice() { + let val: &[u8] = &[0_u8, 1_u8, 2_u8, 3_u8]; + let ron = ron::to_string(val).unwrap(); + assert_eq!(ron, "[0,1,2,3]"); + + // deserialising a byte slice from a byte sequence should fail + // with the error that a borrowed slice was expected but a byte + // buffer was provided + // NOT with an expected string error, since the byte slice + // serialisation never serialises to a string + assert_eq!( + ron::from_str::<&[u8]>(&ron), + Err(ron::error::SpannedError { + code: ron::error::Error::InvalidValueForType { + expected: String::from("a borrowed byte array"), + found: String::from("the bytes \"AAECAw==\""), + }, + position: ron::error::Position { line: 1, col: 10 }, + }) + ); +} diff --git a/tests/depth_limit.rs b/tests/depth_limit.rs index 6d197bde9..ee61a09c8 100644 --- a/tests/depth_limit.rs +++ b/tests/depth_limit.rs @@ -1,6 +1,7 @@ -use serde::Serialize; use std::collections::HashMap; +use serde::Serialize; + #[derive(Serialize)] struct Config { float: (f32, f64), diff --git a/tests/escape.rs b/tests/escape.rs index 48f5fdaa1..2874c3f4f 100644 --- a/tests/escape.rs +++ b/tests/escape.rs @@ -1,6 +1,7 @@ +use std::{char::from_u32, fmt::Debug}; + use ron::{de::from_str, ser::to_string}; use serde::{Deserialize, Serialize}; -use std::{char::from_u32, fmt::Debug}; #[test] fn test_escape_basic() { diff --git a/tests/extensions.rs b/tests/extensions.rs index 6161d90b4..6b3ade87a 100644 --- a/tests/extensions.rs +++ b/tests/extensions.rs @@ -1,6 +1,7 @@ -use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use serde::{Deserialize, Serialize}; + #[derive(Debug, PartialEq, Deserialize, Serialize)] struct UnitStruct; diff --git a/tests/non_identifier_identifier.rs b/tests/non_identifier_identifier.rs new file mode 100644 index 000000000..81b6c2cdc --- /dev/null +++ b/tests/non_identifier_identifier.rs @@ -0,0 +1,94 @@ +macro_rules! test_non_identifier { + ($test_name:ident => $deserialize_method:ident($($deserialize_param:expr),*)) => { + #[test] + fn $test_name() { + use serde::{Deserialize, Deserializer, de::Visitor, de::MapAccess}; + + struct FieldVisitor; + + impl<'de> Visitor<'de> for FieldVisitor { + type Value = FieldName; + + // GRCOV_EXCL_START + fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.write_str("an error") + } + // GRCOV_EXCL_STOP + } + + struct FieldName; + + impl<'de> Deserialize<'de> for FieldName { + fn deserialize>(deserializer: D) + -> Result + { + deserializer.$deserialize_method($($deserialize_param,)* FieldVisitor) + } + } + + struct StructVisitor; + + impl<'de> Visitor<'de> for StructVisitor { + type Value = Struct; + + // GRCOV_EXCL_START + fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.write_str("a struct") + } + // GRCOV_EXCL_STOP + + fn visit_map>(self, mut map: A) + -> Result + { + map.next_key::().map(|_| Struct) + } + } + + #[derive(Debug)] + struct Struct; + + impl<'de> Deserialize<'de> for Struct { + fn deserialize>(deserializer: D) + -> Result + { + deserializer.deserialize_struct("Struct", &[], StructVisitor) + } + } + + assert_eq!( + ron::from_str::("(true: 4)").unwrap_err().code, + ron::Error::ExpectedIdentifier + ) + } + }; +} + +test_non_identifier! { test_bool => deserialize_bool() } +test_non_identifier! { test_i8 => deserialize_i8() } +test_non_identifier! { test_i16 => deserialize_i16() } +test_non_identifier! { test_i32 => deserialize_i32() } +test_non_identifier! { test_i64 => deserialize_i64() } +#[cfg(feature = "integer128")] +test_non_identifier! { test_i128 => deserialize_i128() } +test_non_identifier! { test_u8 => deserialize_u8() } +test_non_identifier! { test_u16 => deserialize_u16() } +test_non_identifier! { test_u32 => deserialize_u32() } +test_non_identifier! { test_u64 => deserialize_u64() } +#[cfg(feature = "integer128")] +test_non_identifier! { test_u128 => deserialize_u128() } +test_non_identifier! { test_f32 => deserialize_f32() } +test_non_identifier! { test_f64 => deserialize_f64() } +test_non_identifier! { test_char => deserialize_char() } +test_non_identifier! { test_string => deserialize_string() } +test_non_identifier! { test_bytes => deserialize_bytes() } +test_non_identifier! { test_byte_buf => deserialize_byte_buf() } +test_non_identifier! { test_option => deserialize_option() } +test_non_identifier! { test_unit => deserialize_unit() } +test_non_identifier! { test_unit_struct => deserialize_unit_struct("") } +test_non_identifier! { test_newtype_struct => deserialize_newtype_struct("") } +test_non_identifier! { test_seq => deserialize_seq() } +test_non_identifier! { test_tuple => deserialize_tuple(0) } +test_non_identifier! { test_tuple_struct => deserialize_tuple_struct("", 0) } +test_non_identifier! { test_map => deserialize_map() } +test_non_identifier! { test_struct => deserialize_struct("", &[]) } +test_non_identifier! { test_enum => deserialize_enum("", &[]) } diff --git a/tests/numbers.rs b/tests/numbers.rs index 4717ddb9f..bdd357478 100644 --- a/tests/numbers.rs +++ b/tests/numbers.rs @@ -1,5 +1,7 @@ -use ron::de::from_str; -use ron::error::{Error, Position, SpannedError}; +use ron::{ + de::from_str, + error::{Error, Position, SpannedError}, +}; #[test] fn test_hex() { diff --git a/tests/options.rs b/tests/options.rs index 89a122af8..0f6092468 100644 --- a/tests/options.rs +++ b/tests/options.rs @@ -1,6 +1,5 @@ -use serde::{Deserialize, Serialize}; - use ron::{extensions::Extensions, ser::PrettyConfig, Options}; +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] struct Newtype(f64); diff --git a/tests/preserve_sequence.rs b/tests/preserve_sequence.rs index 8ffb20d86..bdfe3575e 100644 --- a/tests/preserve_sequence.rs +++ b/tests/preserve_sequence.rs @@ -1,9 +1,10 @@ +use std::collections::BTreeMap; + use ron::{ de::from_str, ser::{to_string_pretty, PrettyConfig}, }; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; #[derive(Debug, Deserialize, Serialize)] struct Config { diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs index 7ca5bd8ad..cc168f024 100644 --- a/tests/roundtrip.rs +++ b/tests/roundtrip.rs @@ -1,7 +1,7 @@ -use serde::{Deserialize, Serialize}; use std::collections::HashMap; use ron::extensions::Extensions; +use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Deserialize, Serialize)] struct UnitStruct; diff --git a/tests/to_string_pretty.rs b/tests/to_string_pretty.rs index 0e1f93d1a..ad5c7ed26 100644 --- a/tests/to_string_pretty.rs +++ b/tests/to_string_pretty.rs @@ -1,5 +1,7 @@ -use ron::ser::{to_string_pretty, PrettyConfig}; -use ron::to_string; +use ron::{ + ser::{to_string_pretty, PrettyConfig}, + to_string, +}; use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Deserialize, Serialize)] diff --git a/tests/value.rs b/tests/value.rs index 8256d3cf9..3919058c0 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -1,7 +1,11 @@ -use ron::value::{Map, Number, Value}; -use serde::Serialize; use std::f64; +use ron::{ + error::Error, + value::{Map, Number, Value}, +}; +use serde::Serialize; + #[test] fn bool() { assert_eq!("true".parse(), Ok(Value::Bool(true))); @@ -67,6 +71,34 @@ fn seq() { Value::Number(Number::new(2f64)), ]; assert_eq!("[1, 2.0]".parse(), Ok(Value::Seq(seq))); + + let err = Value::Seq(vec![Value::Number(Number::new(1))]) + .into_rust::<[i32; 2]>() + .unwrap_err(); + + assert_eq!( + err, + Error::ExpectedDifferentLength { + expected: String::from("an array of length 2"), + found: 1, + } + ); + + let err = Value::Seq(vec![ + Value::Number(Number::new(1)), + Value::Number(Number::new(2)), + Value::Number(Number::new(3)), + ]) + .into_rust::<[i32; 2]>() + .unwrap_err(); + + assert_eq!( + err, + Error::ExpectedDifferentLength { + expected: String::from("a sequence of length 2"), + found: 3, + } + ); } #[test]