Handle array table into an empty slice (#997)

Fix #995
This commit is contained in:
Thomas Pelletier
2025-08-21 12:05:41 +02:00
committed by GitHub
parent bc9958322f
commit 18a2148713
2 changed files with 229 additions and 6 deletions
+26 -2
View File
@@ -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)
+203 -4
View File
@@ -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)
@@ -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",
},
}
@@ -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)
}
}