Skip to content

Commit

Permalink
add fuzzing test (#548)
Browse files Browse the repository at this point in the history
  • Loading branch information
goccy authored Nov 27, 2024
1 parent 639f493 commit 23c9234
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 4 deletions.
18 changes: 16 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
- name: test
run: |
make test
i386-test:
name: Test in i386
strategy:
Expand All @@ -56,7 +56,21 @@ jobs:
- name: test
run: |
make simple-test
fuzz:
name: Fuzzing Test
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup Go
uses: actions/setup-go@v4
with:
go-version: "1.23"
- name: run
run: |
make fuzz
ycat:
name: ycat
runs-on: ubuntu-latest
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ simple-test: testmod
go test -v ./...
go test -v ./testdata -modfile=$(TESTMOD)

.PHONY: fuzz
fuzz:
go test -fuzz=Fuzz -fuzztime 60s

.PHONY: cover
cover: testmod
go test -coverpkg=.,./ast,./lexer,./parser,./printer,./scanner,./token -coverprofile=cover.out -modfile=$(TESTMOD) ./... ./testdata
Expand Down
6 changes: 6 additions & 0 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -1674,6 +1674,12 @@ func (d *Decoder) decodeMap(ctx context.Context, dst reflect.Value, src ast.Node
mapValue.SetMapIndex(d.createDecodableValue(keyType), d.castToAssignableValue(dstValue, valueType))
continue
}
if keyType.Kind() != k.Kind() {
return errors.ErrSyntax(
fmt.Sprintf("cannot convert %q type to %q type", k.Kind(), keyType.Kind()),
key.GetToken(),
)
}
mapValue.SetMapIndex(k, d.castToAssignableValue(dstValue, valueType))
}
dst.Set(mapValue)
Expand Down
52 changes: 52 additions & 0 deletions fuzz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package yaml_test

import (
"strings"
"testing"

"github.com/goccy/go-yaml"
)

func FuzzUnmarshalToMap(f *testing.F) {
const validYAML = `
id: 1
message: Hello, World
verified: true
`

invalidYAML := []string{
"0::",
"{0",
"*-0",
">\n>",
"&{0",
"0_",
"0\n:",
"0\n-",
"0\n0",
"0\n0\n",
"0\n0\n0",
"0\n0\n0\n",
"0\n0\n0\n0",
"0\n0\n0\n0\n",
"0\n0\n0\n0\n0",
"0\n0\n0\n0\n0\n",
"0\n0\n0\n0\n0\n0",
"0\n0\n0\n0\n0\n0\n",
"",
}

f.Add([]byte(validYAML))
for _, s := range invalidYAML {
f.Add([]byte(s))
f.Add([]byte(validYAML + s))
f.Add([]byte(s + validYAML))
f.Add([]byte(s + validYAML + s))
f.Add([]byte(strings.Repeat(s, 3)))
}

f.Fuzz(func(t *testing.T, src []byte) {
v := map[string]any{}
_ = yaml.Unmarshal(src, &v)
})
}
4 changes: 4 additions & 0 deletions parser/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ func (c *context) nextNotCommentToken() *Token {
return nil
}

func (c *context) isTokenNotFound() bool {
return c.currentToken() == nil
}

func (c *context) withGroup(g *TokenGroup) *context {
ctx := *c
ctx.tokenRef = &tokenRef{
Expand Down
35 changes: 34 additions & 1 deletion parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ func (p *parser) parseToken(ctx *context, tk *Token) (ast.Node, error) {
return nil, err
}
ctx.goNext()
if ctx.isTokenNotFound() {
return nil, errors.ErrSyntax("could not find anchor value", tk.RawToken())
}
value, err := p.parseToken(ctx, ctx.currentToken())
if err != nil {
return nil, err
Expand Down Expand Up @@ -244,6 +247,9 @@ func (p *parser) parseScalarValue(ctx *context, tk *Token) (ast.ScalarNode, erro
return nil, err
}
ctx.goNext()
if ctx.isTokenNotFound() {
return nil, errors.ErrSyntax("could not find anchor value", tk.RawToken())
}
value, err := p.parseToken(ctx, ctx.currentToken())
if err != nil {
return nil, err
Expand Down Expand Up @@ -338,6 +344,9 @@ func (p *parser) parseFlowMap(ctx *context) (*ast.MappingNode, error) {
ctx.goNext()
} else {
ctx.goNext()
if ctx.isTokenNotFound() {
return nil, errors.ErrSyntax("could not find map value", colonTk.RawToken())
}
value, err := p.parseToken(ctx, ctx.currentToken())
if err != nil {
return nil, err
Expand Down Expand Up @@ -472,6 +481,9 @@ func (p *parser) parseMap(ctx *context) (*ast.MappingNode, error) {
}

func (p *parser) validateMapKeyValueNextToken(ctx *context, keyTk, tk *Token) error {
if tk == nil {
return nil
}
if tk.Column() <= keyTk.Column() {
return nil
}
Expand Down Expand Up @@ -519,12 +531,18 @@ func (p *parser) parseMapKey(ctx *context, g *TokenGroup) (ast.MapKeyNode, error
}
if g.First().Type() == token.MappingKeyType {
mapKeyTk := g.First()
if mapKeyTk.Group == nil {
return nil, errors.ErrSyntax("could not find value for mapping key", mapKeyTk.RawToken())
}
ctx := ctx.withGroup(mapKeyTk.Group)
key, err := newMappingKeyNode(ctx, mapKeyTk)
if err != nil {
return nil, err
}
ctx.goNext() // skip mapping key token
if ctx.isTokenNotFound() {
return nil, errors.ErrSyntax("could not find value for mapping key", mapKeyTk.RawToken())
}

scalar, err := p.parseScalarValue(ctx, ctx.currentToken())
if err != nil {
Expand Down Expand Up @@ -689,6 +707,10 @@ func (p *parser) parseAnchor(ctx *context, g *TokenGroup) (*ast.AnchorNode, erro
return nil, err
}
ctx.goNext()
if ctx.isTokenNotFound() {
return nil, errors.ErrSyntax("could not find anchor value", anchor.GetToken())
}

value, err := p.parseToken(ctx, ctx.currentToken())
if err != nil {
return nil, err
Expand All @@ -703,6 +725,10 @@ func (p *parser) parseAnchorName(ctx *context) (*ast.AnchorNode, error) {
return nil, err
}
ctx.goNext()
if ctx.isTokenNotFound() {
return nil, errors.ErrSyntax("could not find anchor value", anchor.GetToken())
}

anchorName, err := p.parseScalarValue(ctx, ctx.currentToken())
if err != nil {
return nil, err
Expand All @@ -720,6 +746,9 @@ func (p *parser) parseAlias(ctx *context) (*ast.AliasNode, error) {
return nil, err
}
ctx.goNext()
if ctx.isTokenNotFound() {
return nil, errors.ErrSyntax("could not find alias value", alias.GetToken())
}

aliasName, err := p.parseScalarValue(ctx, ctx.currentToken())
if err != nil {
Expand Down Expand Up @@ -834,13 +863,17 @@ func (p *parser) parseFlowSequence(ctx *context) (*ast.SequenceNode, error) {
return nil, errors.ErrSyntax("',' or ']' must be specified", tk.RawToken())
}

if tk := ctx.currentToken(); tk != nil && tk.Type() == token.SequenceEndType {
if tk := ctx.currentToken(); tk.Type() == token.SequenceEndType {
// this case is here: "[ elem, ]".
// In this case, ignore the last element and break sequence parsing.
node.End = tk.RawToken()
break
}

if ctx.isTokenNotFound() {
break
}

value, err := p.parseToken(ctx.withIndex(uint(len(node.Values))).withFlow(true), ctx.currentToken())
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion scanner/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func (c *Context) updateDocumentNewLineInFolded(column int) {
return
}
if c.docLineIndentColumn == c.docPrevLineIndentColumn {
if c.buf[len(c.buf)-1] == '\n' {
if len(c.buf) != 0 && c.buf[len(c.buf)-1] == '\n' {
c.buf[len(c.buf)-1] = ' '
}
}
Expand Down
10 changes: 10 additions & 0 deletions scanner/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,11 @@ func (s *Scanner) scanDocumentHeaderOption(ctx *Context) error {
if hasComment {
commentLen := orgOptLen - len(opt)
headerPos := strings.Index(string(ctx.obuf), "|")
if len(ctx.obuf) < commentLen+headerPos {
invalidTk := token.Invalid("found invalid literal header option", string(ctx.obuf), s.pos())
s.progressColumn(ctx, progress)
return ErrInvalidToken(invalidTk)
}
litBuf := ctx.obuf[:len(ctx.obuf)-commentLen-headerPos]
commentBuf := ctx.obuf[len(litBuf):]
ctx.addToken(token.Literal("|"+opt, string(litBuf), s.pos()))
Expand All @@ -1017,6 +1022,11 @@ func (s *Scanner) scanDocumentHeaderOption(ctx *Context) error {
if hasComment {
commentLen := orgOptLen - len(opt)
headerPos := strings.Index(string(ctx.obuf), ">")
if len(ctx.obuf) < commentLen+headerPos {
invalidTk := token.Invalid("found invalid folded header option", string(ctx.obuf), s.pos())
s.progressColumn(ctx, progress)
return ErrInvalidToken(invalidTk)
}
foldedBuf := ctx.obuf[:len(ctx.obuf)-commentLen-headerPos]
commentBuf := ctx.obuf[len(foldedBuf):]
ctx.addToken(token.Folded(">"+opt, string(foldedBuf), s.pos()))
Expand Down

0 comments on commit 23c9234

Please sign in to comment.