Fix incorrect error positions for non-suffix subslices in unstable.Parser.Range (#1047)
The unsafe removal (#1021) replaced danger.SubsliceOffset (pointer arithmetic) with len(p.data)-len(b), which only works for suffix slices. Parser.Range is called with arbitrary interior subslices (e.g. ParserError.Highlight), so the offset was wrong whenever the error occurred after previously scanned content like comments. Fix by using reflect.ValueOf().Pointer() to recover the actual data pointer, matching the approach already used in errors.go. https://claude.ai/code/session_01EXYfFXc3DDGpQ27sWdXTKq
This commit is contained in:
@@ -301,6 +301,75 @@ OtherMissing = 1
|
||||
assert.Equal(t, 2, len(strictErr.Unwrap()))
|
||||
}
|
||||
|
||||
func TestDecodeError_PositionAfterComment(t *testing.T) {
|
||||
// Regression test for https://github.com/pelletier/go-toml/issues/1047
|
||||
// Error positions must be correct when the error occurs after comments or
|
||||
// other content that was already scanned past.
|
||||
examples := []struct {
|
||||
desc string
|
||||
doc string
|
||||
expectedRow int
|
||||
expectedCol int
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
desc: "invalid key after comment",
|
||||
doc: "# comment\n= \"value\"",
|
||||
expectedRow: 2,
|
||||
expectedCol: 1,
|
||||
errContains: "invalid character",
|
||||
},
|
||||
{
|
||||
desc: "invalid key after two comments",
|
||||
doc: "# one\n# two\n= \"value\"",
|
||||
expectedRow: 3,
|
||||
expectedCol: 1,
|
||||
errContains: "invalid character",
|
||||
},
|
||||
{
|
||||
desc: "invalid key after key-value pair",
|
||||
doc: "a = 1\n= 2",
|
||||
expectedRow: 2,
|
||||
expectedCol: 1,
|
||||
errContains: "invalid character",
|
||||
},
|
||||
{
|
||||
desc: "invalid key after blank line",
|
||||
doc: "a = 1\n\n= 2",
|
||||
expectedRow: 3,
|
||||
expectedCol: 1,
|
||||
errContains: "invalid character",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range examples {
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
var v interface{}
|
||||
err := Unmarshal([]byte(e.doc), &v)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
|
||||
var derr *DecodeError
|
||||
if !errors.As(err, &derr) {
|
||||
t.Fatalf("error not a *DecodeError: %T: %v", err, err)
|
||||
}
|
||||
|
||||
row, col := derr.Position()
|
||||
if row != e.expectedRow {
|
||||
t.Errorf("row: got %d, want %d (error: %s)", row, e.expectedRow, derr.String())
|
||||
}
|
||||
if col != e.expectedCol {
|
||||
t.Errorf("col: got %d, want %d (error: %s)", col, e.expectedCol, derr.String())
|
||||
}
|
||||
|
||||
if !strings.Contains(derr.Error(), e.errContains) {
|
||||
t.Errorf("error %q does not contain %q", derr.Error(), e.errContains)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleDecodeError() {
|
||||
doc := `name = 123__456`
|
||||
|
||||
|
||||
+8
-3
@@ -3,6 +3,7 @@ package unstable
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unicode"
|
||||
|
||||
"github.com/pelletier/go-toml/v2/internal/characters"
|
||||
@@ -83,10 +84,14 @@ func (p *Parser) rangeOfToken(token, rest []byte) Range {
|
||||
}
|
||||
|
||||
// subsliceOffset returns the byte offset of subslice b within p.data.
|
||||
// b must be a suffix (tail) of p.data.
|
||||
// b must be a subslice of p.data (sharing the same backing array).
|
||||
func (p *Parser) subsliceOffset(b []byte) int {
|
||||
// b is a suffix of p.data, so its offset is len(p.data) - len(b)
|
||||
return len(p.data) - len(b)
|
||||
if len(b) == 0 {
|
||||
return 0
|
||||
}
|
||||
dataPtr := reflect.ValueOf(p.data).Pointer()
|
||||
subPtr := reflect.ValueOf(b).Pointer()
|
||||
return int(subPtr - dataPtr)
|
||||
}
|
||||
|
||||
// Raw returns the slice corresponding to the bytes in the given range.
|
||||
|
||||
@@ -695,3 +695,81 @@ func ExampleParser() {
|
||||
// Expression: KeyValue
|
||||
// value -> (Integer) 42
|
||||
}
|
||||
|
||||
func TestParserError_RangeOffset(t *testing.T) {
|
||||
// Regression test for https://github.com/pelletier/go-toml/issues/1047
|
||||
// Parser.Range must return the correct byte offset for error highlights,
|
||||
// not just for suffix slices.
|
||||
examples := []struct {
|
||||
desc string
|
||||
input string
|
||||
wantOffset int
|
||||
wantLine int
|
||||
wantColumn int
|
||||
wantMessage string
|
||||
}{
|
||||
{
|
||||
desc: "invalid key start after comment",
|
||||
input: "# comment\n= \"value\"",
|
||||
wantOffset: 10,
|
||||
wantLine: 2,
|
||||
wantColumn: 1,
|
||||
wantMessage: "invalid character at start of key: =",
|
||||
},
|
||||
{
|
||||
desc: "invalid key start after two comments",
|
||||
input: "# one\n# two\n= \"value\"",
|
||||
wantOffset: 12,
|
||||
wantLine: 3,
|
||||
wantColumn: 1,
|
||||
wantMessage: "invalid character at start of key: =",
|
||||
},
|
||||
{
|
||||
desc: "invalid key start after blank line",
|
||||
input: "a = 1\n\n= 2",
|
||||
wantOffset: 7,
|
||||
wantLine: 3,
|
||||
wantColumn: 1,
|
||||
wantMessage: "invalid character at start of key: =",
|
||||
},
|
||||
{
|
||||
desc: "invalid key start after valid key-value",
|
||||
input: "a = 1\n= 2",
|
||||
wantOffset: 6,
|
||||
wantLine: 2,
|
||||
wantColumn: 1,
|
||||
wantMessage: "invalid character at start of key: =",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range examples {
|
||||
t.Run(e.desc, func(t *testing.T) {
|
||||
p := Parser{}
|
||||
p.Reset([]byte(e.input))
|
||||
for p.NextExpression() {
|
||||
}
|
||||
err := p.Error()
|
||||
if err == nil {
|
||||
t.Fatal("expected an error")
|
||||
}
|
||||
perr, ok := err.(*ParserError)
|
||||
if !ok {
|
||||
t.Fatalf("expected *ParserError, got %T", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, e.wantMessage, perr.Message)
|
||||
|
||||
r := p.Range(perr.Highlight)
|
||||
if int(r.Offset) != e.wantOffset {
|
||||
t.Errorf("Range offset: got %d, want %d", r.Offset, e.wantOffset)
|
||||
}
|
||||
|
||||
shape := p.Shape(r)
|
||||
if shape.Start.Line != e.wantLine || shape.Start.Column != e.wantColumn {
|
||||
t.Errorf("position: got %d:%d, want %d:%d",
|
||||
shape.Start.Line, shape.Start.Column,
|
||||
e.wantLine, e.wantColumn)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user