Skip to content

Commit bbca97e

Browse files
authored
Merge pull request #168 from aembke/master
Fix #167
2 parents d69cbb4 + 18bcdca commit bbca97e

File tree

4 files changed

+185
-7
lines changed

4 files changed

+185
-7
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "json"
3-
version = "0.12.0"
3+
version = "0.12.1"
44
authors = ["Maciej Hirsz <[email protected]>"]
55
description = "JSON implementation in Rust"
66
repository = "https://github.com/maciejhirsz/json-rust"

src/codegen.rs

+36-3
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,20 @@ pub trait Generator {
6666

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

7171
for (index, ch) in string.bytes().enumerate().skip(start) {
7272
let escape = ESCAPED[ch as usize];
7373
if escape > 0 {
74-
self.write(string[start .. index].as_bytes())?;
74+
self.write(&string.as_bytes()[start .. index])?;
7575
self.write(&[b'\\', escape])?;
7676
start = index + 1;
7777
}
7878
if escape == b'u' {
7979
write!(self.get_writer(), "{:04x}", ch)?;
8080
}
8181
}
82-
self.write(string[start ..].as_bytes())?;
82+
self.write(&string.as_bytes()[start ..])?;
8383

8484
self.write_char(b'"')
8585
}
@@ -382,3 +382,36 @@ fn extend_from_slice(dst: &mut Vec<u8>, src: &[u8]) {
382382
src_len);
383383
}
384384
}
385+
386+
#[cfg(test)]
387+
mod tests {
388+
use super::*;
389+
use std::borrow::Borrow;
390+
use crate::JsonValue;
391+
use crate::parse;
392+
393+
// found while fuzzing the DumpGenerator
394+
395+
#[test]
396+
fn should_not_panic_on_bad_bytes() {
397+
let data = [0, 12, 128, 88, 64, 99].to_vec();
398+
let s = unsafe {
399+
String::from_utf8_unchecked(data)
400+
};
401+
402+
let mut generator = DumpGenerator::new();
403+
generator.write_string(&s);
404+
}
405+
406+
#[test]
407+
fn should_not_panic_on_bad_bytes_2() {
408+
let data = b"\x48\x48\x48\x57\x03\xE8\x48\x48\xE8\x03\x8F\x48\x29\x48\x48";
409+
let s = unsafe {
410+
String::from_utf8_unchecked(data.to_vec())
411+
};
412+
413+
let mut generator = DumpGenerator::new();
414+
generator.write_string(&s);
415+
}
416+
417+
}

src/parser.rs

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

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

492501
loop {
493502
if ALLOWED[ch as usize] {
@@ -533,7 +542,7 @@ impl<'a> Parser<'a> {
533542
// issues here, we construct a new slice from raw parts, which
534543
// then has lifetime bound to the outer function scope instead
535544
// of the parser itself.
536-
slice::from_raw_parts(self.buffer.as_ptr(), self.buffer.len())
545+
slice::from_raw_parts(self.buffer[len .. ].as_ptr(), self.buffer.len() - len)
537546
)
538547
})
539548
}
@@ -752,3 +761,138 @@ struct StackBlock(JsonValue, usize);
752761
pub fn parse(source: &str) -> Result<JsonValue> {
753762
Parser::new(source).parse()
754763
}
764+
765+
766+
#[cfg(test)]
767+
mod tests {
768+
use super::*;
769+
use crate::stringify;
770+
use crate::JsonValue;
771+
772+
#[macro_use]
773+
use crate::object;
774+
use crate::array;
775+
776+
use std::fs::File;
777+
use std::io::prelude::*;
778+
779+
#[test]
780+
fn it_should_parse_escaped_forward_slashes_with_quotes() {
781+
// used to get around the fact that rust strings don't escape forward slashes
782+
let mut file = File::open("tests/test_json_slashes_quotes").unwrap();
783+
let mut contents = String::new();
784+
file.read_to_string(&mut contents).unwrap();
785+
786+
let actual = parse(&contents).unwrap();
787+
let serialized = stringify(actual.clone());
788+
789+
assert_eq!(serialized, contents);
790+
}
791+
792+
#[test]
793+
fn it_should_parse_escaped_quotes() {
794+
let contents = String::from("{\"ab\":\"c\\\"d\\\"e\"}");
795+
796+
let actual = parse(&contents).unwrap();
797+
let serialized = stringify(actual.clone());
798+
799+
assert_eq!(serialized, contents);
800+
}
801+
802+
#[test]
803+
fn it_should_parse_basic_json_values() {
804+
let s = "{\"a\":1,\"b\":true,\"c\":false,\"d\":null,\"e\":2}";
805+
let actual = parse(s).unwrap();
806+
let mut expected = object! {
807+
"a" => 1,
808+
"b" => true,
809+
"c" => false,
810+
"e" => 2
811+
};
812+
expected["d"] = JsonValue::Null;
813+
814+
assert_eq!(actual, expected);
815+
}
816+
817+
#[test]
818+
fn it_should_parse_json_arrays() {
819+
let s = "{\"a\":1,\"b\":true,\"c\":false,\"d\":null,\"e\":2,\"f\":[1,2,3,false,true,[],{}]}";
820+
let actual = parse(s).unwrap();
821+
let mut expected = object! {
822+
"a" => 1,
823+
"b" => true,
824+
"c" => false,
825+
"e" => 2
826+
};
827+
expected["d"] = JsonValue::Null;
828+
expected["f"] = array![
829+
1,2,3,
830+
false,
831+
true,
832+
array![],
833+
object!{}
834+
];
835+
836+
assert_eq!(actual, expected);
837+
}
838+
839+
#[test]
840+
fn it_should_parse_json_nested_object() {
841+
let s = "{\"a\":1,\"b\":{\"c\":2,\"d\":{\"e\":{\"f\":{\"g\":3,\"h\":[]}}},\"i\":4,\"j\":[],\"k\":{\"l\":5,\"m\":{}}}}";
842+
let actual = parse(s).unwrap();
843+
let mut expected = object! {
844+
"a" => 1,
845+
"b" => object!{
846+
"c" => 2,
847+
"d" => object!{
848+
"e" => object! {
849+
"f" => object!{
850+
"g" => 3,
851+
"h" => array![]
852+
}
853+
}
854+
},
855+
"i" => 4,
856+
"j" => array![],
857+
"k" => object!{
858+
"l" => 5,
859+
"m" => object!{}
860+
}
861+
}
862+
};
863+
864+
assert_eq!(actual, expected);
865+
}
866+
867+
#[test]
868+
fn it_should_parse_json_complex_object() {
869+
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\":{}}}}";
870+
let actual = parse(s).unwrap();
871+
let mut expected = object! {
872+
"a" => 1,
873+
"b" => object!{
874+
"c" => 2,
875+
"d" => object!{
876+
"e" => object! {
877+
"f" => object!{
878+
"g" => 3,
879+
"h" => array![
880+
object!{"z" => 1},
881+
object!{"y" => 2, "x" => array![object!{}, object!{}]}
882+
]
883+
}
884+
}
885+
},
886+
"i" => 4,
887+
"j" => array![],
888+
"k" => object!{
889+
"l" => 5,
890+
"m" => object!{}
891+
}
892+
}
893+
};
894+
895+
assert_eq!(actual, expected);
896+
}
897+
898+
}

tests/test_json_slashes_quotes

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"a\\/b":"c\"d\"e"}

0 commit comments

Comments
 (0)