Skip to content

Commit

Permalink
Merge pull request #168 from aembke/master
Browse files Browse the repository at this point in the history
Fix #167
  • Loading branch information
maciejhirsz authored Jan 14, 2020
2 parents d69cbb4 + 18bcdca commit bbca97e
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 7 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "json"
version = "0.12.0"
version = "0.12.1"
authors = ["Maciej Hirsz <hello@maciej.codes>"]
description = "JSON implementation in Rust"
repository = "https://github.com/maciejhirsz/json-rust"
Expand Down
39 changes: 36 additions & 3 deletions src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,20 @@ pub trait Generator {

#[inline(never)]
fn write_string_complex(&mut self, string: &str, mut start: usize) -> io::Result<()> {
self.write(string[ .. start].as_bytes())?;
self.write(&string.as_bytes()[ .. start])?;

for (index, ch) in string.bytes().enumerate().skip(start) {
let escape = ESCAPED[ch as usize];
if escape > 0 {
self.write(string[start .. index].as_bytes())?;
self.write(&string.as_bytes()[start .. index])?;
self.write(&[b'\\', escape])?;
start = index + 1;
}
if escape == b'u' {
write!(self.get_writer(), "{:04x}", ch)?;
}
}
self.write(string[start ..].as_bytes())?;
self.write(&string.as_bytes()[start ..])?;

self.write_char(b'"')
}
Expand Down Expand Up @@ -382,3 +382,36 @@ fn extend_from_slice(dst: &mut Vec<u8>, src: &[u8]) {
src_len);
}
}

#[cfg(test)]
mod tests {
use super::*;
use std::borrow::Borrow;
use crate::JsonValue;
use crate::parse;

// found while fuzzing the DumpGenerator

#[test]
fn should_not_panic_on_bad_bytes() {
let data = [0, 12, 128, 88, 64, 99].to_vec();
let s = unsafe {
String::from_utf8_unchecked(data)
};

let mut generator = DumpGenerator::new();
generator.write_string(&s);
}

#[test]
fn should_not_panic_on_bad_bytes_2() {
let data = b"\x48\x48\x48\x57\x03\xE8\x48\x48\xE8\x03\x8F\x48\x29\x48\x48";
let s = unsafe {
String::from_utf8_unchecked(data.to_vec())
};

let mut generator = DumpGenerator::new();
generator.write_string(&s);
}

}
150 changes: 147 additions & 3 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,11 +483,20 @@ impl<'a> Parser<'a> {
// having to be read from source to a buffer and then from a buffer to
// our target string. Nothing to be done about this, really.
fn read_complex_string<'b>(&mut self, start: usize) -> Result<&'b str> {
self.buffer.clear();
// Since string slices are returned by this function that are created via pointers into `self.buffer`
// we shouldn't be clearing or modifying the buffer in consecutive calls to this function. Instead
// we continuously append bytes to `self.buffer` and keep track of the starting offset of the buffer on each
// call to this function. Later when creating string slices that point to the contents of this buffer
// we use this starting offset to make sure that newly created slices point only to the bytes that were
// appended in the most recent call to this function.
//
// Failing to do this can result in the StackBlock `key` values being modified in place later.
let len = self.buffer.len();
//self.buffer.clear();
let mut ch = b'\\';

// TODO: Use fastwrite here as well
self.buffer.extend_from_slice(self.source[start .. self.index - 1].as_bytes());
self.buffer.extend_from_slice(&self.source.as_bytes()[start .. self.index - 1]);

loop {
if ALLOWED[ch as usize] {
Expand Down Expand Up @@ -533,7 +542,7 @@ impl<'a> Parser<'a> {
// issues here, we construct a new slice from raw parts, which
// then has lifetime bound to the outer function scope instead
// of the parser itself.
slice::from_raw_parts(self.buffer.as_ptr(), self.buffer.len())
slice::from_raw_parts(self.buffer[len .. ].as_ptr(), self.buffer.len() - len)
)
})
}
Expand Down Expand Up @@ -752,3 +761,138 @@ struct StackBlock(JsonValue, usize);
pub fn parse(source: &str) -> Result<JsonValue> {
Parser::new(source).parse()
}


#[cfg(test)]
mod tests {
use super::*;
use crate::stringify;
use crate::JsonValue;

#[macro_use]
use crate::object;
use crate::array;

use std::fs::File;
use std::io::prelude::*;

#[test]
fn it_should_parse_escaped_forward_slashes_with_quotes() {
// used to get around the fact that rust strings don't escape forward slashes
let mut file = File::open("tests/test_json_slashes_quotes").unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();

let actual = parse(&contents).unwrap();
let serialized = stringify(actual.clone());

assert_eq!(serialized, contents);
}

#[test]
fn it_should_parse_escaped_quotes() {
let contents = String::from("{\"ab\":\"c\\\"d\\\"e\"}");

let actual = parse(&contents).unwrap();
let serialized = stringify(actual.clone());

assert_eq!(serialized, contents);
}

#[test]
fn it_should_parse_basic_json_values() {
let s = "{\"a\":1,\"b\":true,\"c\":false,\"d\":null,\"e\":2}";
let actual = parse(s).unwrap();
let mut expected = object! {
"a" => 1,
"b" => true,
"c" => false,
"e" => 2
};
expected["d"] = JsonValue::Null;

assert_eq!(actual, expected);
}

#[test]
fn it_should_parse_json_arrays() {
let s = "{\"a\":1,\"b\":true,\"c\":false,\"d\":null,\"e\":2,\"f\":[1,2,3,false,true,[],{}]}";
let actual = parse(s).unwrap();
let mut expected = object! {
"a" => 1,
"b" => true,
"c" => false,
"e" => 2
};
expected["d"] = JsonValue::Null;
expected["f"] = array![
1,2,3,
false,
true,
array![],
object!{}
];

assert_eq!(actual, expected);
}

#[test]
fn it_should_parse_json_nested_object() {
let s = "{\"a\":1,\"b\":{\"c\":2,\"d\":{\"e\":{\"f\":{\"g\":3,\"h\":[]}}},\"i\":4,\"j\":[],\"k\":{\"l\":5,\"m\":{}}}}";
let actual = parse(s).unwrap();
let mut expected = object! {
"a" => 1,
"b" => object!{
"c" => 2,
"d" => object!{
"e" => object! {
"f" => object!{
"g" => 3,
"h" => array![]
}
}
},
"i" => 4,
"j" => array![],
"k" => object!{
"l" => 5,
"m" => object!{}
}
}
};

assert_eq!(actual, expected);
}

#[test]
fn it_should_parse_json_complex_object() {
let s = "{\"a\":1,\"b\":{\"c\":2,\"d\":{\"e\":{\"f\":{\"g\":3,\"h\":[{\"z\":1},{\"y\":2,\"x\":[{},{}]}]}}},\"i\":4,\"j\":[],\"k\":{\"l\":5,\"m\":{}}}}";
let actual = parse(s).unwrap();
let mut expected = object! {
"a" => 1,
"b" => object!{
"c" => 2,
"d" => object!{
"e" => object! {
"f" => object!{
"g" => 3,
"h" => array![
object!{"z" => 1},
object!{"y" => 2, "x" => array![object!{}, object!{}]}
]
}
}
},
"i" => 4,
"j" => array![],
"k" => object!{
"l" => 5,
"m" => object!{}
}
}
};

assert_eq!(actual, expected);
}

}
1 change: 1 addition & 0 deletions tests/test_json_slashes_quotes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"a\\/b":"c\"d\"e"}

0 comments on commit bbca97e

Please sign in to comment.