v0.1.1 de06cbd

fuzz_test.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package git
4
5import (
6	"bytes"
7	"errors"
8	"strings"
9	"testing"
10)
11
12func okOrCorrupt(t *testing.T, err error) {
13	t.Helper()
14	if err != nil && !errors.Is(err, ErrCorrupt) && !errors.Is(err, ErrTooLarge) && !errors.Is(err, ErrUnsupported) {
15		t.Fatalf("error is not a typed one: %v", err)
16	}
17}
18
19func FuzzPackEntryHeader(f *testing.F) {
20	pack := func(body ...byte) []byte {
21		d := append([]byte("PACK\x00\x00\x00\x02\x00\x00\x00\x01"), body...)
22		return append(d, make([]byte, 20)...)
23	}
24	f.Add(pack(0x35, 0x78, 0x9c, 0x01), uint64(12))
25	f.Add(pack(0xb4, 0x23, 0x78), uint64(12))
26	f.Add(pack(0x65, 0x05, 0x78), uint64(12))
27	f.Add(pack(0x65, 0xff, 0xff, 0xff, 0x7f, 0x78), uint64(12))
28	f.Add(append(pack(0x75), bytes.Repeat([]byte{7}, 25)...), uint64(12))
29	f.Add(pack(bytes.Repeat([]byte{0xff}, 30)...), uint64(12))
30	f.Add([]byte{}, uint64(0))
31	f.Fuzz(func(t *testing.T, data []byte, off uint64) {
32		h, err := parseEntryHeader(data, off)
33		okOrCorrupt(t, err)
34		if err == nil {
35			if h.data > uint64(len(data)) || (h.typ == objOfsDelta && h.baseOff >= off) {
36				t.Fatalf("inconsistent header %+v for %d bytes at %d", h, len(data), off)
37			}
38		}
39	})
40}
41
42func FuzzDeltaApply(f *testing.F) {
43	base := []byte("hello, delta world")
44	good := append(varint(uint64(len(base))), varint(12)...)
45	good = append(good, copyOp(0, 5)...)
46	good = append(good, 3, ' ', 'G', 'o')
47	good = append(good, copyOp(11, 4)...)
48	f.Add(base, good)
49	f.Add(base, []byte{})
50	f.Add(base, []byte{0x12, 0x05, 0x80})
51	f.Add([]byte{}, []byte{0x00, 0x00})
52	f.Add(base, append(varint(uint64(len(base))), 0xff, 0xff, 0xff, 0xff, 0x0f, 0x90, 0x01))
53	f.Add(base, append(append(varint(uint64(len(base))), varint(1<<20)...), 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff))
54	f.Fuzz(func(t *testing.T, base, delta []byte) {
55		out, err := patch(base, delta, 1<<20)
56		okOrCorrupt(t, err)
57		if err == nil {
58			_, rs, _, herr := deltaHeader(delta)
59			if herr != nil || uint64(len(out)) != rs {
60				t.Fatalf("result of %d bytes, header says %d (%v)", len(out), rs, herr)
61			}
62		}
63	})
64}
65
66func FuzzParseTree(f *testing.F) {
67	f.Add(append([]byte("100644 a\x00"), bytes.Repeat([]byte{1}, 20)...))
68	f.Add(append(append([]byte("40000 dir\x00"), bytes.Repeat([]byte{2}, 20)...), append([]byte("160000 sub\x00"), bytes.Repeat([]byte{3}, 20)...)...))
69	f.Add([]byte("100644 \x00"))
70	f.Add([]byte("100644 name"))
71	f.Add([]byte{})
72	f.Add(bytes.Repeat([]byte("1 \x00"), 30))
73	f.Fuzz(func(t *testing.T, data []byte) {
74		n := 0
75		err := ParseTree(data, func(e Entry) bool {
76			n++
77			if len(e.Name) == 0 {
78				t.Fatal("empty name accepted")
79			}
80			return true
81		})
82		okOrCorrupt(t, err)
83		if err == nil && n == 0 && len(data) != 0 {
84			t.Fatal("non-empty tree without entries")
85		}
86	})
87}
88
89func FuzzParseCommit(f *testing.F) {
90	tree, par := strings.Repeat("a", 40), strings.Repeat("b", 40)
91	f.Add([]byte("tree " + tree + "\nparent " + par + "\nauthor A <a@x> 1700000000 +0100\ncommitter B <b@x> 1700000001 -0830\n\nmessage\n"))
92	f.Add([]byte("tree " + tree + "\ngpgsig -----BEGIN-----\n \n abc\n -----END-----\nencoding latin1\n\n\xe9\xff"))
93	f.Add([]byte("tree " + tree))
94	f.Add([]byte("tree " + tree + "\nauthor <> 99999999999999999999999 +9999\n\n"))
95	f.Add([]byte("tree\n\n"))
96	f.Add([]byte("tree " + tree + "\ntree " + par + "\nparent " + par + "\n\n"))
97	f.Add([]byte("tree " + tree + "\nparent " + par + "\nauthor A <a@x> 1 +0000\nparent " + tree + "\n\nm"))
98	f.Add([]byte("parent " + par + "\ntree " + tree + "\n\n"))
99	f.Add([]byte{})
100	f.Add([]byte("\n\n\n"))
101	var c Commit
102	f.Fuzz(func(t *testing.T, data []byte) {
103		err := ParseCommit(data, &c)
104		okOrCorrupt(t, err)
105		if err == nil && len(c.Message) > len(data) {
106			t.Fatal("message longer than input")
107		}
108		if err == nil && !bytes.HasPrefix(data, []byte("tree ")) {
109			t.Fatal("accepted a commit whose first header is not the tree")
110		}
111		if err == nil && len(c.Parents) > 0 {
112			// The parents are the lines that follow the tree directly.
113			rest := data[bytes.IndexByte(data, '\n')+1:]
114			for _, p := range c.Parents {
115				want := append([]byte("parent "), []byte(p.String())...)
116				if len(rest) < len(want) || !bytes.EqualFold(rest[:len(want)], want) {
117					t.Fatalf("parent %s is not on the line after the tree", p)
118				}
119				rest = rest[bytes.IndexByte(rest, '\n')+1:]
120			}
121		}
122		var tg Tag
123		okOrCorrupt(t, ParseTag(data, &tg))
124		_ = parseSignature(data)
125	})
126}
127
128func FuzzIdxLookup(f *testing.F) {
129	var offs []uint64
130	b1, b2 := []byte("one"), []byte("two")
131	_, idx := buildPack([][]byte{entryBytes(3, 3, nil, b1), entryBytes(3, 3, nil, b2)},
132		[]ObjectID{objectIDOf("blob", b1), objectIDOf("blob", b2)}, &offs)
133	id := objectIDOf("blob", b1)
134	f.Add(idx, id[:])
135	f.Add(idx[:len(idx)-1], id[:])
136	f.Add(idx[:1100], id[:])
137	f.Add([]byte{}, id[:])
138	big := bytes.Clone(idx)
139	big[len(big)-41] |= 0x80
140	f.Add(big, id[:])
141	f.Fuzz(func(t *testing.T, data, key []byte) {
142		x, err := parseIdx(data)
143		if err != nil {
144			okOrCorrupt(t, err)
145			return
146		}
147		var want ObjectID
148		copy(want[:], key)
149		if pos, ok := x.find(&want); ok {
150			if pos >= x.n || !bytes.Equal(x.id(pos), want[:]) {
151				t.Fatalf("find returned position %d for %s", pos, want)
152			}
153			_, err := x.offset(pos)
154			okOrCorrupt(t, err)
155		}
156		if len(key) > 0 {
157			nib := min(len(key)*2, 40)
158			x.prefixMatches(key, nib, func(id ObjectID) bool {
159				if !hasNibblePrefix(id[:], key, nib) {
160					t.Fatalf("prefixMatches yielded %s for %x/%d", id, key, nib)
161				}
162				return true
163			})
164		}
165		for i := uint32(0); i < min(x.n, 8); i++ {
166			_, err := x.offset(i)
167			okOrCorrupt(t, err)
168		}
169	})
170}
171
172func FuzzPackedRefsAndConfig(f *testing.F) {
173	a := strings.Repeat("a", 40)
174	f.Add("# pack-refs with: peeled fully-peeled sorted \n" + a + " refs/heads/main\n" + a + " refs/tags/v1\n^" + a + "\n")
175	f.Add("^" + a)
176	f.Add("[core]\n\trepositoryformatversion = 1\n[extensions]\n\tobjectformat = sha1\n")
177	f.Add("[")
178	f.Add("ref: refs/heads/main\n")
179	f.Fuzz(func(t *testing.T, s string) {
180		_, err := parsePackedRefs(s)
181		okOrCorrupt(t, err)
182		okOrCorrupt(t, checkConfigData([]byte(s)))
183		_, err = parseRefContent([]byte(s))
184		okOrCorrupt(t, err)
185		_ = validRefName(s)
186	})
187}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 2

↑↓ moveenter openesc close