Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions unmarshaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,15 +416,39 @@ func (d *decoder) handleArrayTableCollection(key unstable.Iterator, v reflect.Va

return v, nil
case reflect.Slice:
elem := v.Index(v.Len() - 1)
// Create a new element when the slice is empty; otherwise operate on
// the last element.
var (
elem reflect.Value
created bool
)
if v.Len() == 0 {
created = true
elemType := v.Type().Elem()
if elemType.Kind() == reflect.Interface {
elem = makeMapStringInterface()
} else {
elem = reflect.New(elemType).Elem()
}
} else {
elem = v.Index(v.Len() - 1)
}

x, err := d.handleArrayTable(key, elem)
if err != nil || d.skipUntilTable {
return reflect.Value{}, err
}
if x.IsValid() {
elem.Set(x)
if created {
elem = x
} else {
elem.Set(x)
}
}

if created {
return reflect.Append(v, elem), nil
}
return v, err
case reflect.Array:
idx := d.arrayIndex(false, v)
Expand Down
207 changes: 203 additions & 4 deletions unmarshaler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ foo = "bar"`,
assert: func(t *testing.T, test test) {
// Despite the documentation:
// Pointer variable equality is determined based on the equality of the
// referenced values (as opposed to the memory addresses).
// referenced values (as opposed to the memory addresses).
// assert.Equal does not work properly with maps with pointer keys
// https://github.com/stretchr/testify/issues/1143
expected := make(map[unmarshalTextKey]string)
Expand Down Expand Up @@ -3884,9 +3884,9 @@ func TestUnmarshal_Nil(t *testing.T) {
{
desc: "simplest",
input: `
[foo]
[foo.foo]
`,
[foo]
[foo.foo]
`,
expected: "[foo]\n[foo.foo]\n",
},
}
Expand Down Expand Up @@ -4040,3 +4040,202 @@ func TestIssue994_OK(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "bar from unmarshaler", d.S)
}

func TestIssue995(t *testing.T) {
type AllowList struct {
Description string
Condition string
Commits []string
Paths []string
RegexTarget string
Regexes []string
StopWords []string
}

type Rule struct {
ID string
Description string
Regex string
SecretGroup int
Entropy interface{}
Keywords []string
Path string
Tags []string
AllowList *AllowList
Allowlists []AllowList
}

type GitleaksConfig struct {
Description string
Rules []Rule
Allowlist struct {
Commits []string
Paths []string
RegexTarget string
Regexes []string
StopWords []string
}
}

doc := `
[[allowlists]]
description = "Exception for File "
files = [ '''app/src''']

[[rules.allowlists]]
description = "policies"
regexes = [
'''abc'''
]
`

var cfg GitleaksConfig
err := toml.Unmarshal([]byte(doc), &cfg)
assert.NoError(t, err)

// Ensure no panic and that nested array table was created.
if len(cfg.Rules) == 0 {
t.Fatalf("expected Rules to contain at least one element after unmarshaling nested array table")
}
if len(cfg.Rules[0].Allowlists) != 1 {
t.Fatalf("expected first Rule to have exactly one allowlists entry, got %d", len(cfg.Rules[0].Allowlists))
}
assert.Equal(t, "policies", cfg.Rules[0].Allowlists[0].Description)
assert.Equal(t, []string{"abc"}, cfg.Rules[0].Allowlists[0].Regexes)
}

func TestIssue995_InterfaceSlice_MultiNested(t *testing.T) {
type Root struct {
Rules []interface{}
}

doc := `
[[rules.allowlists]]
description = "a"

[[rules.allowlists]]
description = "b"
`

var r Root
err := toml.Unmarshal([]byte(doc), &r)
assert.NoError(t, err)

if len(r.Rules) != 1 {
t.Fatalf("expected one element in Rules, got %d", len(r.Rules))
}

m, ok := r.Rules[0].(map[string]interface{})
if !ok {
t.Fatalf("expected Rules[0] to be a map[string]any, got %T", r.Rules[0])
}

als, ok := m["allowlists"].([]interface{})
if !ok {
t.Fatalf("expected allowlists to be []any, got %T", m["allowlists"])
}
if len(als) != 2 {
t.Fatalf("expected 2 allowlists entries, got %d", len(als))
}

a0, ok := als[0].(map[string]interface{})
if !ok {
t.Fatalf("expected allowlists[0] to be map[string]any, got %T", als[0])
}
a1, ok := als[1].(map[string]interface{})
if !ok {
t.Fatalf("expected allowlists[1] to be map[string]any, got %T", als[1])
}
assert.Equal(t, "a", a0["description"])
assert.Equal(t, "b", a1["description"])
}

func TestIssue995_MultiNestedConcrete(t *testing.T) {
type AllowList struct {
Description string
}
type Rule struct {
Allowlists []AllowList
}
type Root struct {
Rules []Rule
}

doc := `
[[rules.allowlists]]
description = "a"

[[rules.allowlists]]
description = "b"
`

var r Root
err := toml.Unmarshal([]byte(doc), &r)
assert.NoError(t, err)

if len(r.Rules) != 1 {
t.Fatalf("expected one element in Rules, got %d", len(r.Rules))
}
assert.Equal(t, 2, len(r.Rules[0].Allowlists))
assert.Equal(t, "a", r.Rules[0].Allowlists[0].Description)
assert.Equal(t, "b", r.Rules[0].Allowlists[1].Description)
}

func TestIssue995_PointerToSlice_Rules(t *testing.T) {
type AllowList struct{ Description string }
type Rule struct{ Allowlists []AllowList }
type Root struct{ Rules *[]Rule }

doc := `
[[rules.allowlists]]
description = "a"

[[rules.allowlists]]
description = "b"
`

var r Root
err := toml.Unmarshal([]byte(doc), &r)
assert.NoError(t, err)
if r.Rules == nil {
t.Fatalf("expected Rules pointer to be initialized")
}
if len(*r.Rules) != 1 {
t.Fatalf("expected one element in Rules, got %d", len(*r.Rules))
}
rule := (*r.Rules)[0]
assert.Equal(t, 2, len(rule.Allowlists))
assert.Equal(t, "a", rule.Allowlists[0].Description)
assert.Equal(t, "b", rule.Allowlists[1].Description)
}

func TestIssue995_SliceNonEmpty_UsesLastElement(t *testing.T) {
type AllowList struct{ Description string }
type Rule struct{ Allowlists []AllowList }
type Root struct{ Rules []Rule }

// Pre-initialize with one Rule; nested array table should populate
// the last element, not create a new one at this level.
var r Root
r.Rules = []Rule{{}}

doc := `
[[rules.allowlists]]
description = "a"

[[rules.allowlists]]
description = "b"
`

err := toml.Unmarshal([]byte(doc), &r)
assert.NoError(t, err)
if len(r.Rules) != 1 {
t.Fatalf("expected one element in Rules, got %d", len(r.Rules))
}
assert.Equal(t, 2, len(r.Rules[0].Allowlists))
// Values presence check
got := []string{r.Rules[0].Allowlists[0].Description, r.Rules[0].Allowlists[1].Description}
if !(got[0] == "a" && got[1] == "b") && !(got[0] == "b" && got[1] == "a") {
t.Fatalf("unexpected values in allowlists: %v", got)
}
}
Loading