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
26 changes: 21 additions & 5 deletions tasks/taskHelpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,16 +434,29 @@ func (v ValidateBlob) InsertKey(insertKey string, value interface{}) ValidateBlo
}

func (v ValidateBlob) insertChildKey(insertKey string, value interface{}) (output []ValidateBlob) {
// walk the tree
if insertKey == "" {
log.Debug("insertKey is empty, returning children unchanged")
return v.Children
}

// walk the tree
depth := strings.Count(v.PathAndKey(), "/")

//split up the key to have the actual key
split := strings.Split(insertKey, "/") //Skip the first / here to ignore the preceding / when calculating
if len(split) == 0 {
log.Debug("split resulted in empty slice, returning children unchanged")
return v.Children
}
key := split[len(split)-1]
if split[0] == "" {
split = split[1:] // drop first element to ignore the preceding /
}
// Additional safety check after dropping first element
if len(split) == 0 {
log.Debug("split is empty after dropping leading slash, returning children unchanged")
return v.Children
}
newSplit := split[:len(split)-1] // drop last element of the slice to build the path
path := ""
for _, pathItem := range newSplit {
Expand All @@ -468,7 +481,9 @@ func (v ValidateBlob) insertChildKey(insertKey string, value interface{}) (outpu
}
if matches == 0 {
log.Debug("failed to find path to node, adding partial node to existing", v)
if len(split)+1 == depth {
// Ensure depth-1 is a valid index before accessing split[depth-1]
if depth > len(split) {
log.Debug("depth exceeds split length", "depth:", depth, "split length:", len(split))
return //depth of node reached, stop searching to avoid panics
}
node := ValidateBlob{Key: split[depth-1], Path: v.PathAndKey()}
Expand Down Expand Up @@ -914,11 +929,12 @@ func BytesToPrettyJSONBytes(bytes []byte) ([]byte, error) {

firstByte := string(bytes[0])

if firstByte == "[" {
switch firstByte {
case "[":
unmarshaled = []interface{}{}
} else if firstByte == "{" {
case "{":
unmarshaled = make(map[string]interface{})
} else {
default:
return bytes, errors.New("invalid JSON: First character is " + string(firstByte))
}

Expand Down
60 changes: 60 additions & 0 deletions tasks/taskHelpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,66 @@ func TestValidateBlob_InsertKey(t *testing.T) {
}
}

// TestValidateBlob_InsertKey_PanicRegression tests that insertChildKey doesn't panic
// when the depth exceeds the split array length. This is a regression test for an
// index out of bounds panic that occurred in the original implementation.
func TestValidateBlob_InsertKey_PanicRegression(t *testing.T) {
t.Run("deeply nested insert should not panic", func(t *testing.T) {
// Create a ValidateBlob with a starting structure
v := ValidateBlob{
Key: "root",
Path: "/",
}

// Insert a deeply nested key path - this would have caused a panic before the fix
// The issue occurred when the recursive depth counter exceeded the length of the split path
defer func() {
if r := recover(); r != nil {
t.Errorf("InsertKey() panicked with: %v", r)
}
}()

result := v.InsertKey("/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o", "test_value")

// Verify that we got a result back (not a panic)
if result.Key != "root" {
t.Errorf("Expected root key, got %v", result.Key)
}

// Verify the result has children (something was inserted)
if len(result.Children) == 0 {
t.Errorf("Expected children to be populated, got empty")
}
})

t.Run("UpdateOrInsertKey with excessive depth should not panic", func(t *testing.T) {
// Test the same scenario through UpdateOrInsertKey, which is what the
// Java config validation code actually calls (based on the stack trace)
v := ValidateBlob{
Key: "common",
Path: "/",
Children: []ValidateBlob{
{Key: "license_key", Path: "/common", RawValue: "existing"},
},
}

defer func() {
if r := recover(); r != nil {
t.Errorf("UpdateOrInsertKey() panicked with: %v", r)
}
}()

// This simulates what happens in Java config validation when system properties
// create deeply nested paths
result := v.UpdateOrInsertKey("log_file_path/nested/very/deep/path/that/exceeds/normal/depth", "log.txt")

// Verify we got a result
if result.Key != "common" {
t.Errorf("Expected common key, got %v", result.Key)
}
})
}

func TestValidateBlob_UpdateOrInsertKey(t *testing.T) {
type args struct {
key string
Expand Down
Loading