Skip to content

Commit 4f54037

Browse files
authored
Return errors for empty text fragments (#29)
This fixes two issues: 1. Fragments with only context lines were accepted 2. Fragments with only a "no newline" marker caused a panic The panic was found by go-fuzz. While fixing that issue, I discovered the first problem with other types of empty fragments while comparing behavior against 'git apply'. Also extract some helper functions and modify the loop conditions to clean things up while making changes in this area.
1 parent b6a9011 commit 4f54037

File tree

2 files changed

+48
-18
lines changed

2 files changed

+48
-18
lines changed

gitdiff/text.go

+30-18
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,8 @@ func (p *parser) ParseTextChunk(frag *TextFragment) error {
7979
return p.Errorf(0, "no content following fragment header")
8080
}
8181

82-
isNoNewlineLine := func(s string) bool {
83-
// test for "\ No newline at end of file" by prefix because the text
84-
// changes by locale (git claims all versions are at least 12 chars)
85-
return len(s) >= 12 && s[:2] == "\\ "
86-
}
87-
8882
oldLines, newLines := frag.OldLines, frag.NewLines
89-
for {
83+
for oldLines > 0 || newLines > 0 {
9084
line := p.Line(0)
9185
op, data := line[0], line[1:]
9286

@@ -113,13 +107,14 @@ func (p *parser) ParseTextChunk(frag *TextFragment) error {
113107
frag.LinesAdded++
114108
frag.TrailingContext = 0
115109
frag.Lines = append(frag.Lines, Line{OpAdd, data})
116-
default:
110+
case '\\':
117111
// this may appear in middle of fragment if it's for a deleted line
118-
if isNoNewlineLine(line) {
119-
last := &frag.Lines[len(frag.Lines)-1]
120-
last.Line = strings.TrimSuffix(last.Line, "\n")
112+
if isNoNewlineMarker(line) {
113+
removeLastNewline(frag)
121114
break
122115
}
116+
fallthrough
117+
default:
123118
// TODO(bkeyes): if this is because we hit the next header, it
124119
// would be helpful to return the miscounts line error. We could
125120
// either test for the common headers ("@@ -", "diff --git") or
@@ -128,11 +123,6 @@ func (p *parser) ParseTextChunk(frag *TextFragment) error {
128123
return p.Errorf(0, "invalid line operation: %q", op)
129124
}
130125

131-
next := p.Line(1)
132-
if oldLines <= 0 && newLines <= 0 && !isNoNewlineLine(next) {
133-
break
134-
}
135-
136126
if err := p.Next(); err != nil {
137127
if err == io.EOF {
138128
break
@@ -145,13 +135,35 @@ func (p *parser) ParseTextChunk(frag *TextFragment) error {
145135
hdr := max(frag.OldLines-oldLines, frag.NewLines-newLines) + 1
146136
return p.Errorf(-hdr, "fragment header miscounts lines: %+d old, %+d new", -oldLines, -newLines)
147137
}
138+
if frag.LinesAdded == 0 && frag.LinesDeleted == 0 {
139+
return p.Errorf(0, "fragment contains no changes")
140+
}
148141

149-
if err := p.Next(); err != nil && err != io.EOF {
150-
return err
142+
// check for a final "no newline" marker since it is not included in the
143+
// counters used to stop the loop above
144+
if isNoNewlineMarker(p.Line(0)) {
145+
removeLastNewline(frag)
146+
if err := p.Next(); err != nil && err != io.EOF {
147+
return err
148+
}
151149
}
150+
152151
return nil
153152
}
154153

154+
func isNoNewlineMarker(s string) bool {
155+
// test for "\ No newline at end of file" by prefix because the text
156+
// changes by locale (git claims all versions are at least 12 chars)
157+
return len(s) >= 12 && s[:2] == "\\ "
158+
}
159+
160+
func removeLastNewline(frag *TextFragment) {
161+
if len(frag.Lines) > 0 {
162+
last := &frag.Lines[len(frag.Lines)-1]
163+
last.Line = strings.TrimSuffix(last.Line, "\n")
164+
}
165+
}
166+
155167
func parseRange(s string) (start int64, end int64, err error) {
156168
parts := strings.SplitN(s, ",", 2)
157169

gitdiff/text_test.go

+18
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,24 @@ func TestParseTextChunk(t *testing.T) {
317317
},
318318
Err: true,
319319
},
320+
"onlyContext": {
321+
Input: ` context line
322+
context line
323+
`,
324+
Fragment: TextFragment{
325+
OldLines: 2,
326+
NewLines: 2,
327+
},
328+
Err: true,
329+
},
330+
"unexpectedNoNewlineMarker": {
331+
Input: `\ No newline at end of file`,
332+
Fragment: TextFragment{
333+
OldLines: 1,
334+
NewLines: 1,
335+
},
336+
Err: true,
337+
},
320338
}
321339

322340
for name, test := range tests {

0 commit comments

Comments
 (0)