Skip to content

Commit da05a52

Browse files
committed
Fix missingnl behavior on normal-diff
1 parent a4f7642 commit da05a52

4 files changed

Lines changed: 225 additions & 12 deletions

File tree

lib/normal-diff/fuzz/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
target
3+
corpus
4+
artifacts

lib/normal-diff/fuzz/Cargo.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
2+
[package]
3+
name = "normal-diff-fuzz"
4+
version = "0.0.0"
5+
authors = ["Automatically generated"]
6+
publish = false
7+
edition = "2018"
8+
9+
[package.metadata]
10+
cargo-fuzz = true
11+
12+
[dependencies]
13+
libfuzzer-sys = "0.3"
14+
15+
[dependencies.normal-diff]
16+
path = ".."
17+
18+
# Prevent this from interfering with workspaces
19+
[workspace]
20+
members = ["."]
21+
22+
[[bin]]
23+
name = "fuzz_patch"
24+
path = "fuzz_targets/fuzz_patch.rs"
25+
test = false
26+
doc = false
27+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#![no_main]
2+
#[macro_use] extern crate libfuzzer_sys;
3+
extern crate normal_diff;
4+
5+
use std::fs::{self, File};
6+
use std::io::Write;
7+
use std::process::Command;
8+
9+
fuzz_target!(|x: (Vec<u8>, Vec<u8>)| {
10+
let (from, to) = x;
11+
/*if let Ok(s) = String::from_utf8(from.clone()) {
12+
if !s.is_ascii() { return }
13+
if s.find(|x| x < ' ' && x != '\n').is_some() { return }
14+
} else {
15+
return
16+
}
17+
if let Ok(s) = String::from_utf8(to.clone()) {
18+
if !s.is_ascii() { return }
19+
if s.find(|x| x < ' ' && x != '\n').is_some() { return }
20+
} else {
21+
return
22+
}*/
23+
let diff = normal_diff::diff(&from, &to);
24+
File::create("target/fuzz.file.original")
25+
.unwrap()
26+
.write_all(&from)
27+
.unwrap();
28+
File::create("target/fuzz.file.expected")
29+
.unwrap()
30+
.write_all(&to)
31+
.unwrap();
32+
File::create("target/fuzz.file")
33+
.unwrap()
34+
.write_all(&from)
35+
.unwrap();
36+
File::create("target/fuzz.diff")
37+
.unwrap()
38+
.write_all(&diff)
39+
.unwrap();
40+
let output = Command::new("patch")
41+
.arg("-p0")
42+
.arg("--binary")
43+
.arg("--fuzz=0")
44+
.arg("--normal")
45+
.arg("target/fuzz.file")
46+
.stdin(File::open("target/fuzz.diff").unwrap())
47+
.output()
48+
.unwrap();
49+
if !output.status.success() {
50+
panic!("STDOUT:\n{}\nSTDERR:\n{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr));
51+
}
52+
let result = fs::read("target/fuzz.file").unwrap();
53+
if result != to {
54+
panic!("STDOUT:\n{}\nSTDERR:\n{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr));
55+
}
56+
});
57+

lib/normal-diff/src/lib.rs

Lines changed: 137 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ struct Mismatch {
66
pub line_number_actual: usize,
77
pub expected: Vec<Vec<u8>>,
88
pub actual: Vec<Vec<u8>>,
9+
pub expected_missing_nl: bool,
10+
pub actual_missing_nl: bool,
911
}
1012

1113
impl Mismatch {
@@ -15,6 +17,8 @@ impl Mismatch {
1517
line_number_actual,
1618
expected: Vec::new(),
1719
actual: Vec::new(),
20+
expected_missing_nl: false,
21+
actual_missing_nl: false,
1822
}
1923
}
2024
}
@@ -31,8 +35,8 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Vec<Mismatch> {
3135

3236
debug_assert_eq!(b"".split(|&c| c == b'\n').count(), 1);
3337
// ^ means that underflow here is impossible
34-
let _expected_lines_count = expected_lines.len() - 1;
35-
let _actual_lines_count = actual_lines.len() - 1;
38+
let expected_lines_count = expected_lines.len() - 1;
39+
let actual_lines_count = actual_lines.len() - 1;
3640

3741
if expected_lines.last() == Some(&&b""[..]) {
3842
expected_lines.pop();
@@ -45,26 +49,46 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Vec<Mismatch> {
4549
for result in diff::slice(&expected_lines, &actual_lines) {
4650
match result {
4751
diff::Result::Left(str) => {
48-
if mismatch.actual.len() != 0 {
52+
if mismatch.actual.len() != 0 && !mismatch.actual_missing_nl {
4953
results.push(mismatch);
5054
mismatch = Mismatch::new(line_number_expected, line_number_actual);
5155
}
5256
mismatch.expected.push(str.to_vec());
57+
mismatch.expected_missing_nl = line_number_expected > expected_lines_count;
5358
line_number_expected += 1;
5459
}
5560
diff::Result::Right(str) => {
5661
mismatch.actual.push(str.to_vec());
62+
mismatch.actual_missing_nl = line_number_actual > actual_lines_count;
5763
line_number_actual += 1;
5864
}
59-
diff::Result::Both(_str, _) => {
60-
line_number_expected += 1;
61-
line_number_actual += 1;
62-
if mismatch.actual.len() != 0 || mismatch.expected.len() != 0 {
63-
results.push(mismatch);
64-
mismatch = Mismatch::new(line_number_expected, line_number_actual);
65-
} else {
66-
mismatch.line_number_expected = line_number_expected;
67-
mismatch.line_number_actual = line_number_actual;
65+
diff::Result::Both(str, _) => {
66+
match (line_number_expected > expected_lines_count, line_number_actual > actual_lines_count) {
67+
(true, false) => {
68+
line_number_expected += 1;
69+
line_number_actual += 1;
70+
mismatch.expected.push(str.to_vec());
71+
mismatch.expected_missing_nl = true;
72+
mismatch.actual.push(str.to_vec());
73+
}
74+
(false, true) => {
75+
line_number_expected += 1;
76+
line_number_actual += 1;
77+
mismatch.actual.push(str.to_vec());
78+
mismatch.actual_missing_nl = true;
79+
mismatch.expected.push(str.to_vec());
80+
}
81+
(true, true) | (false, false) => {
82+
line_number_expected += 1;
83+
line_number_actual += 1;
84+
if mismatch.actual.len() != 0 || mismatch.expected.len() != 0 {
85+
results.push(mismatch);
86+
mismatch = Mismatch::new(line_number_expected, line_number_actual);
87+
} else {
88+
mismatch.line_number_expected = line_number_expected;
89+
mismatch.line_number_actual = line_number_actual;
90+
}
91+
},
6892
}
6993
}
7094
}
@@ -118,6 +142,9 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Vec<u8> {
118142
output.write_all(expected).unwrap();
119143
writeln!(&mut output, "").unwrap();
120144
}
145+
if result.expected_missing_nl {
146+
writeln!(&mut output, r"\ No newline at end of file").unwrap();
147+
}
121148
if expected_count != 0 && actual_count != 0 {
122149
writeln!(&mut output, "---").unwrap();
123150
}
@@ -126,6 +153,9 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Vec<u8> {
126153
output.write_all(actual).unwrap();
127154
writeln!(&mut output, "").unwrap();
128155
}
156+
if result.actual_missing_nl {
157+
writeln!(&mut output, r"\ No newline at end of file").unwrap();
158+
}
129159
}
130160
output
131161
}
@@ -209,6 +239,101 @@ fn test_permutations() {
209239
}
210240
}
211241

242+
#[test]
243+
fn test_permutations_missing_line_ending() {
244+
// test all possible six-line files with missing newlines.
245+
let _ = std::fs::create_dir("target");
246+
for &a in &[0, 1, 2] {
247+
for &b in &[0, 1, 2] {
248+
for &c in &[0, 1, 2] {
249+
for &d in &[0, 1, 2] {
250+
for &e in &[0, 1, 2] {
251+
for &f in &[0, 1, 2] {
252+
for &g in &[0, 1, 2] {
253+
use std::fs::{self, File};
254+
use std::io::Write;
255+
use std::process::Command;
256+
let mut alef = Vec::new();
257+
let mut bet = Vec::new();
258+
alef.write_all(if a == 0 { b"a\n" } else { b"b\n" })
259+
.unwrap();
260+
if a != 2 {
261+
bet.write_all(b"b\n").unwrap();
262+
}
263+
alef.write_all(if b == 0 { b"c\n" } else { b"d\n" })
264+
.unwrap();
265+
if b != 2 {
266+
bet.write_all(b"d\n").unwrap();
267+
}
268+
alef.write_all(if c == 0 { b"e\n" } else { b"f\n" })
269+
.unwrap();
270+
if c != 2 {
271+
bet.write_all(b"f\n").unwrap();
272+
}
273+
alef.write_all(if d == 0 { b"g\n" } else { b"h\n" })
274+
.unwrap();
275+
if d != 2 {
276+
bet.write_all(b"h\n").unwrap();
277+
}
278+
alef.write_all(if e == 0 { b"i\n" } else { b"j\n" })
279+
.unwrap();
280+
if e != 2 {
281+
bet.write_all(b"j\n").unwrap();
282+
}
283+
alef.write_all(if f == 0 { b"k\n" } else { b"l\n" })
284+
.unwrap();
285+
if f != 2 {
286+
bet.write_all(b"l\n").unwrap();
287+
}
288+
match g {
289+
0 => {
290+
alef.pop();
291+
}
292+
1 => {
293+
bet.pop();
294+
}
295+
2 => {
296+
alef.pop();
297+
bet.pop();
298+
}
299+
_ => unreachable!(),
300+
}
301+
// This test diff is intentionally reversed.
302+
// We want it to turn the alef into bet.
303+
let diff = diff(&alef, &bet);
304+
File::create("target/abn.diff")
305+
.unwrap()
306+
.write_all(&diff)
307+
.unwrap();
308+
let mut fa = File::create("target/alefn").unwrap();
309+
fa.write_all(&alef[..]).unwrap();
310+
let mut fb = File::create("target/betn").unwrap();
311+
fb.write_all(&bet[..]).unwrap();
312+
let _ = fa;
313+
let _ = fb;
314+
let output = Command::new("patch")
315+
.arg("-p0")
316+
.arg("--normal")
317+
.arg("target/alefn")
318+
.stdin(File::open("target/abn.diff").unwrap())
319+
.output()
320+
.unwrap();
321+
if !output.status.success() {
322+
panic!("{:?}", output);
323+
}
324+
//println!("{}", String::from_utf8_lossy(&output.stdout));
325+
//println!("{}", String::from_utf8_lossy(&output.stderr));
326+
let alef = fs::read("target/alefn").unwrap();
327+
assert_eq!(alef, bet);
328+
}
329+
}
330+
}
331+
}
332+
}
333+
}
334+
}
335+
}
336+
212337
#[test]
213338
fn test_permutations_empty_lines() {
214339
// test all possible six-line files with missing newlines.

0 commit comments

Comments
 (0)