diff options
| author | Rose Hogenson <rosehogenson@posteo.net> | 2025-11-18 19:20:29 -0800 |
|---|---|---|
| committer | Rose Hogenson <rosehogenson@posteo.net> | 2025-11-18 19:20:29 -0800 |
| commit | 933c9c9721bf20bc00dab85a75546a7573c31747 (patch) | |
| tree | 62282c41bec50a844148e09b5741283aa5bc3a61 | |
| parent | b25fc1dbacf79d795f80ae07a5785877c2b0fdb0 (diff) | |
| download | ccl-933c9c9721bf20bc00dab85a75546a7573c31747.tar.zst | |
Holy shit fuzzing is so cool
| -rw-r--r-- | ccl.go | 2 | ||||
| -rw-r--r-- | ccl_test.go | 170 | ||||
| -rw-r--r-- | lexer.go | 18 |
3 files changed, 184 insertions, 6 deletions
@@ -306,7 +306,7 @@ func checkNum(b []byte) bool { return true } b = b[1:] - if len(b) > 0 && b[0] == '-' || b[0] == '+' { + if len(b) > 0 && (b[0] == '-' || b[0] == '+') { b = b[1:] } if len(b) == 0 { diff --git a/ccl_test.go b/ccl_test.go index c83e8ab..6e3ef3f 100644 --- a/ccl_test.go +++ b/ccl_test.go @@ -486,6 +486,12 @@ func TestUnmarshal_Invalid(t *testing.T) { }, { desc: "NestedRepeatedNestedType", msg: `nested_repeated: [[1]]`, + }, { + desc: "FloatMissingExponent", + msg: `float:1e`, + }, { + desc: "UnterminatedComment", + msg: `/*`, }} { t.Run(tc.desc, func(t *testing.T) { t.Parallel() @@ -734,3 +740,167 @@ func BenchmarkParse(b *testing.B) { } } } + +func FuzzUnmarshal(f *testing.F) { + for _, tc := range []string{ + ` + # This is a comment + string: 'asdf\n' # comment end of line + string2: "asdf\n" + int: 10 + float: 10.5e13 + bool: true + bool2: false + message { field: 10 } + repeated: [1, 2, 3] + repeated: 4 + repeated: [5, 6] + `, + `string: "strings +can just span multiple lines"`, + `int: 0`, + `int: 0xff`, + `int: 0XfF`, + `int: 0x0f`, + `int: -0x0f`, + `int: +0x0f`, + `float: 1.5e10`, + `float: 1.5E10`, + `float: -1.5e-10`, + `float: +1.5e+10`, + `int: 10`, + `int: -10`, + `int: +10`, + `int8:1`, + `int16:1`, + `int32:1`, + `int64:1`, + `uint:1`, + `uint8:1`, + `uint16:1`, + `uint32:1`, + `uint64:1`, + `int:on`, + `int:no`, + `uint:on`, + `uint:no`, + `float:-1`, + `float:1`, + `string: 'asdf'`, + `string: "asdf"`, + `string: 'ain\'t'`, + `string: "won\'t"`, + `string: '\"'`, + `string: "\""`, + `string: "\?"`, + `string: '\\'`, + `string: '\a'`, + `string: '\b'`, + `string: '\f'`, + `string: '\n'`, + `string: '\r'`, + `string: '\t'`, + `string: '\v'`, + `string: '\x0a'`, + `string: "\xe4\xb8\x96"`, + `string: '\u2014'`, + `string: '\U0001f600'`, + `string: '\033'`, + `message { field: 10 }`, + `message {}`, + ` + repeated: 1 + repeated: 2`, + `repeated: [1, 2]`, + `repeated: []`, + `repeated: 1`, + `repeated: [ + 1, + 2, + ]`, + `repeated_message: [{}]`, + `message: /** inline comment **/ {}`, + `message: {} // line comment`, + `string: 'that'"'"'s cool'`, + `string: 'remove newline \ +from string'`, + "string: 'remove newline \\\r\nfrom string'", + `bytes:"dGVzdA=="`, + `bytes_wrapper: [1, 2, 3]`, + `time:"2025-10-28T07:41:47Z"`, + `time_pointer:"2025-10-28T07:41:47Z"`, + `int_pointer: 5`, + `repeated_pointer: [1, 2, 3]`, + `int: .`, + `float:1e+`, + `int:0xgg`, + `string: '\g'`, + `string: "\g"`, + "string:'\\\r'", + `string:"\x1"`, + `string:"\xgg"`, + `string:"\u001"`, + `string:"\ugggg"`, + `string: '`, + `string: "`, + `10`, + `msg {10}`, + `repeated []`, + `repeated: [1 2]`, + `repeated: [asdf]`, + `repeated_msg: [{asdf}]`, + `int: 0644`, + `string: "\777"`, + `string: "\x80"`, + `string`, + `string "abc"`, + `int:5 int:6`, + `int8:512`, + `int8:-512`, + `bytes:"dGVzdAo"`, + `bytes:[1,2,3]`, + `asdfasdfasdf:"asdf"`, + `repeated: [[1]]`, + `nested_repeated: [[1]]`, + `float:1e`, + `/*`, + } { + f.Add([]byte(tc)) + } + f.Fuzz(func(t *testing.T, input []byte) { + type byteSliceWrapper []byte + type nestedMessage struct { + Field int64 `ccl:"field"` + } + var message struct { + String string `ccl:"string"` + String2 string `ccl:"string2"` + Int int `ccl:"int"` + Int8 int8 `ccl:"int8"` + Int16 int16 `ccl:"int16"` + Int32 int32 `ccl:"int32"` + Int64 int64 `ccl:"int64"` + Uint uint `ccl:"uint"` + Uint8 uint8 `ccl:"uint8"` + Uint16 uint16 `ccl:"uint16"` + Uint32 uint32 `ccl:"uint32"` + Uint64 uint64 `ccl:"uint64"` + Float float64 `ccl:"float"` + Bool bool `ccl:"bool"` + Bool2 bool `ccl:"bool2"` + Message *nestedMessage `ccl:"message"` + Repeated []int64 `ccl:"repeated"` + RepeatedMessage []*nestedMessage `ccl:"repeated_message"` + Bytes []byte `ccl:"bytes"` + BytesWrapper byteSliceWrapper `ccl:"bytes_wrapper"` + Time time.Time `ccl:"time"` + TimePointer *time.Time `ccl:"time_pointer"` + IntPointer *int `ccl:"int_pointer"` + RepeatedPointer []*int `ccl:"repeated_pointer"` + + Ignore map[int]int `ccl:"-,"` // unlike JSON this also means ignore + unexported int64 + } + Unmarshal(input, &message) + }) +} @@ -2,6 +2,7 @@ package ccl import ( "bytes" + "fmt" "unicode" "unicode/utf8" ) @@ -26,7 +27,8 @@ func (l *lexer) yield(n int) token { return t } -func (l *lexer) skipSpace() { +func (l *lexer) skipSpace() error { +Space: for l.i < len(l.data) { if bytes.HasPrefix(l.data[l.i:], []byte("#")) || bytes.HasPrefix(l.data[l.i:], []byte("//")) { for ; l.i < len(l.data) && l.data[l.i] != '\n'; l.i++ { @@ -34,10 +36,13 @@ func (l *lexer) skipSpace() { continue } if bytes.HasPrefix(l.data[l.i:], []byte("/*")) { - for ; l.i < len(l.data) && !bytes.HasPrefix(l.data[l.i:], []byte("*/")); l.i++ { + for ; l.i < len(l.data); l.i++ { + if bytes.HasPrefix(l.data[l.i:], []byte("*/")) { + l.i += 2 + continue Space + } } - l.i += 2 - continue + return fmt.Errorf("unterminated comment") } if r, n := utf8.DecodeRune(l.data[l.i:]); unicode.IsSpace(r) { l.i += n @@ -45,6 +50,7 @@ func (l *lexer) skipSpace() { } break } + return nil } func numFirstByte(b byte) bool { @@ -72,7 +78,9 @@ func fieldTailByte(b byte) bool { } func (l *lexer) next() (token, error) { - l.skipSpace() + if err := l.skipSpace(); err != nil { + return token{}, err + } if l.i == len(l.data) { return token{}, errEOF } |
