Repository: BurntSushi/toml
Pull request #288 (2021-06-13 12:56:05)

Fix panic on unclosed inline tables
With this:

	x = {key = 1 #

It would trigger the p.bug call:

	if it.typ != itemKeyStart {
		p.bug("Expected key start but instead found %q, around line %d",
			it.val, p.approxLine)
	}

That's not right; an unclodes table is a user error in the TOML file,
not a bug. Just removing that fixes it.

We could also replace this with p.panicf, but the error for that is less
nice:

       have: "Near line 1 (last key parsed 'x.a'): Expected key start but instead found \"\", around line 1"

And it always worked line this for e.g. arrays just fine:

	x = [1 #


Patch:
diff --git a/lex.go b/lex.go
index 47def13..212947a 100644
--- a/lex.go
+++ b/lex.go
@@ -2,6 +2,8 @@ package toml
 
 import (
 	"fmt"
+	"reflect"
+	"runtime"
 	"strings"
 	"unicode"
 	"unicode/utf8"
@@ -514,9 +516,8 @@ func lexArrayValueEnd(lx *lexer) stateFn {
 		return lexArrayEnd
 	}
 	return lx.errorf(
-		"expected a comma or array terminator %q, but got %q instead",
-		arrayEnd, r,
-	)
+		"expected a comma or array terminator %q, but got %s instead",
+		arrayEnd, runeOrEOF(r))
 }
 
 // lexArrayEnd finishes the lexing of an array.
@@ -568,8 +569,16 @@ func lexInlineTableValueEnd(lx *lexer) stateFn {
 	case r == inlineTableEnd:
 		return lexInlineTableEnd
 	}
-	return lx.errorf("expected a comma or an inline table terminator %q, "+
-		"but got %q instead", inlineTableEnd, r)
+	return lx.errorf(
+		"expected a comma or an inline table terminator %q, but got %s instead",
+		inlineTableEnd, runeOrEOF(r))
+}
+
+func runeOrEOF(r rune) string {
+	if r == eof {
+		return "end of file"
+	}
+	return "'" + string(r) + "'"
 }
 
 // lexInlineTableEnd finishes the lexing of an inline table.
@@ -917,6 +926,14 @@ func isBareKeyChar(r rune) bool {
 		r == '-'
 }
 
+func (s stateFn) String() string {
+	name := runtime.FuncForPC(reflect.ValueOf(s).Pointer()).Name()
+	if i := strings.LastIndexByte(name, '.'); i > -1 {
+		name = name[i+1:]
+	}
+	return name + "()"
+}
+
 func (itype itemType) String() string {
 	switch itype {
 	case itemError:
@@ -949,6 +966,10 @@ func (itype itemType) String() string {
 		return "ArrayEnd"
 	case itemCommentStart:
 		return "CommentStart"
+	case itemInlineTableStart:
+		return "InlineTableStart"
+	case itemInlineTableEnd:
+		return "InlineTableEnd"
 	}
 	panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype)))
 }
diff --git a/parse.go b/parse.go
index bbd8687..1505a71 100644
--- a/parse.go
+++ b/parse.go
@@ -311,10 +311,6 @@ func (p *parser) value(it item) (interface{}, tomlType) {
 		p.context = append(p.context, p.currentKey)
 		p.currentKey = ""
 		for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
-			if it.typ != itemKeyStart {
-				p.bug("Expected key start but instead found %q, around line %d",
-					it.val, p.approxLine)
-			}
 			if it.typ == itemCommentStart {
 				p.expect(itemText)
 				continue


Test patch:
diff --git a/decode_test.go b/decode_test.go
index d18bff4..e67bc58 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -998,13 +998,16 @@ func TestDecodeErrors(t *testing.T) {
 		{"x = ", "unexpected EOF; expected value", true},
 		{"x = \n", "expected value but found '\\n' instead", true},
 
-		// Cases found by fuzzing in #155
-		{`""` + "\ufffd", "expected key separator", false}, // used to panic with index out of range
-		{`x="""`, "unexpected EOF", true},                  // used to hang
+		// Cases found by fuzzing in #155 and #239
+		{`""` + "\ufffd", "expected key separator", false},                            // used to panic with index out of range
+		{`x="""`, "unexpected EOF", true},                                             // used to hang
+		{`x = [{ key = 42 #`, "expected a comma or an inline table terminator", true}, // panic
+		{`x = {a = 42 #`, "expected a comma or an inline table terminator '}', but got end of file instead", true},
+		{`x = [42 #`, "expected a comma or array terminator ']', but got end of file instead", false},
 	}
 
 	for _, tt := range tests {
-		t.Run(tt.in, func(t *testing.T) {
+		t.Run("", func(t *testing.T) {
 			var x struct{}
 			_, err := Decode(tt.in, &x)
 			if err == nil {
@@ -1013,7 +1016,7 @@ func TestDecodeErrors(t *testing.T) {
 			if !errorContains(err, tt.wantErr) {
 				t.Errorf("wrong error\nhave: %q\nwant: %q", err, tt.wantErr)
 			}
-			if tt.lastKey && !errorContains(err, "last key parsed 'x'") {
+			if tt.lastKey && !errorContains(err, "last key parsed 'x") {
 				t.Errorf("last key parsed not in error\nhave: %q\nwant: %q", err, "last key parsed 'x'")
 			}
 		})