Skip to content

Commit

Permalink
Fix bug and possible panic when decoding nested structs
Browse files Browse the repository at this point in the history
  • Loading branch information
ianlopshire committed Feb 8, 2024
1 parent 45d0d87 commit 549210b
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 12 deletions.
29 changes: 21 additions & 8 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ func (d *Decoder) readLine(v reflect.Value) (err error, ok bool) {
return valueSetter(v, rawValue), true
}

func rawValueFromLine(value rawValue, startPos, endPos int, format format) rawValue {
func rawValueFromLine(value rawValue, startPos, endPos int, format format) (rawValue, error) {
var trimFunc func(string) string

switch format.alignment {
Expand All @@ -218,7 +218,7 @@ func rawValueFromLine(value rawValue, startPos, endPos int, format format) rawVa

if value.codepointIndices != nil {
if len(value.codepointIndices) == 0 || startPos > len(value.codepointIndices) {
return rawValue{data: ""}
return rawValue{data: ""}, nil
}
var relevantIndices []int
var lineData string
Expand All @@ -229,20 +229,30 @@ func rawValueFromLine(value rawValue, startPos, endPos int, format format) rawVa
relevantIndices = value.codepointIndices[startPos-1 : endPos]
lineData = value.data[relevantIndices[0]:value.codepointIndices[endPos]]
}

trimmed := trimFunc(lineData)

// If the value was trimmed or didn't start at the first codepoint, the indices
// will be incorrect. Create a new rawValue to get the correct indices.
if len(trimmed) != len(lineData) || len(relevantIndices) == 0 {
return newRawValue(trimmed, true)
}

return rawValue{
data: trimFunc(lineData),
data: lineData,
codepointIndices: relevantIndices,
}
}, nil

} else {
if len(value.data) == 0 || startPos > len(value.data) {
return rawValue{data: ""}
return rawValue{data: ""}, nil
}
if endPos > len(value.data) {
endPos = len(value.data)
}
return rawValue{
data: trimFunc(value.data[startPos-1 : endPos]),
}
}, nil
}
}

Expand Down Expand Up @@ -288,9 +298,12 @@ func structSetter(t reflect.Type) valueSetter {
if !fieldSpec.ok {
continue
}
rawValue := rawValueFromLine(raw, fieldSpec.startPos, fieldSpec.endPos, fieldSpec.format)
err := fieldSpec.setter(v.Field(i), rawValue)

rawValue, err := rawValueFromLine(raw, fieldSpec.startPos, fieldSpec.endPos, fieldSpec.format)
if err != nil {
return err
}
if err := fieldSpec.setter(v.Field(i), rawValue); err != nil {
sf := t.Field(i)
return &UnmarshalTypeError{raw.data, sf.Type, t.Name(), sf.Name, err}
}
Expand Down
80 changes: 76 additions & 4 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ func ExampleUnmarshal() {
fmt.Printf("%+v\n", people[2])
fmt.Printf("%+v\n", people[3])
// Output:
//{ID:1 FirstName:Ian LastName:Lopshire Grade:99.5 Age:20 Alive:false Github:false}
//{ID:2 FirstName:John LastName:Doe Grade:89.5 Age:21 Alive:true Github:true}
//{ID:3 FirstName:Jane LastName:Doe Grade:79.5 Age:22 Alive:false Github:false}
//{ID:4 FirstName:Ann LastName:Carraway Grade:79.59 Age:23 Alive:false Github:true}
// {ID:1 FirstName:Ian LastName:Lopshire Grade:99.5 Age:20 Alive:false Github:false}
// {ID:2 FirstName:John LastName:Doe Grade:89.5 Age:21 Alive:true Github:true}
// {ID:3 FirstName:Jane LastName:Doe Grade:79.5 Age:22 Alive:false Github:false}
// {ID:4 FirstName:Ann LastName:Carraway Grade:79.59 Age:23 Alive:false Github:true}
}

func TestUnmarshal(t *testing.T) {
Expand Down Expand Up @@ -539,3 +539,75 @@ func TestLineSeparator(t *testing.T) {
})
}
}

func TestDecode_Nested(t *testing.T) {
type Nested struct {
First string `fixed:"1,3"`
Second string `fixed:"4,6"`
}

type Test struct {
Nested Nested `fixed:"1,6,none"`
}

for _, tt := range []struct {
name string
raw []byte

expected Test
}{
{
name: "ascii no trimming needed",
raw: []byte("123ABC\n"),
expected: Test{
Nested: Nested{
First: "123",
Second: "ABC",
},
},
},
{
name: "ascii with trimmed values",
raw: []byte(" 12BC\n"),
expected: Test{
Nested: Nested{
First: "12",
Second: "BC",
},
},
},
{
name: "Multi-byte no trimming needed",
raw: []byte("12☃☃BC\n"),
expected: Test{
Nested: Nested{
First: "12☃",
Second: "☃BC",
},
},
},
{
name: "Multi-byte with trimmed values",
raw: []byte(" ☃2B☃\n"),
expected: Test{
Nested: Nested{
First: "☃2",
Second: "B☃",
},
},
},
} {
t.Run(tt.name, func(t *testing.T) {
d := NewDecoder(bytes.NewReader(tt.raw))
d.SetUseCodepointIndices(true)
var s Test
err := d.Decode(&s)
if err != nil {
t.Errorf("Unexpected err: %v", err)
}
if !reflect.DeepEqual(tt.expected, s) {
t.Errorf("Decode(%v) want %v, have %v", tt.raw, tt.expected, s)
}
})
}
}

0 comments on commit 549210b

Please sign in to comment.