Skip to content

Commit afc04b7

Browse files
Handle nil pointers. (#125)
* Handle nil pointers. * Fix golint warnings.
1 parent a303c88 commit afc04b7

File tree

2 files changed

+139
-15
lines changed

2 files changed

+139
-15
lines changed

pkg/docker/dockerimage/topobjects.go

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import (
44
"container/heap"
55
)
66

7+
// TopObjects is a slice of ObjectMetadata pointers that implements the heap.Interface
8+
// for maintaining a priority queue of objects, typically sorted by size.
79
type TopObjects []*ObjectMetadata
810

11+
// NewTopObjects creates and returns a new, empty TopObjects slice with a specified capacity.
912
func NewTopObjects(n int) TopObjects {
1013
if n < 1 {
1114
n = 1
@@ -14,34 +17,51 @@ func NewTopObjects(n int) TopObjects {
1417
return make(TopObjects, 0, n)
1518
}
1619

20+
// Len returns the number of elements in the TopObjects slice.
1721
func (to TopObjects) Len() int { return len(to) }
1822

23+
// Less compares two elements in the slice for sorting.
24+
// Nil elements are considered smaller than non-nil elements.
25+
// Two nil elements are considered equal.
1926
func (to TopObjects) Less(i, j int) bool {
20-
if to[i] == nil && to[j] != nil {
21-
return true
27+
// Handle cases where either element is nil
28+
if to[i] == nil && to[j] == nil {
29+
return false // Equal, so not less than
2230
}
23-
if to[i] != nil && to[j] == nil {
24-
return false
31+
if to[i] == nil {
32+
return true // nil is considered smaller than non-nil
2533
}
26-
if to[i] == nil && to[j] == nil {
27-
return false
34+
if to[j] == nil {
35+
return false // Non-nil is considered larger than nil
2836
}
29-
37+
// Both elements are non-nil, compare their sizes
3038
return to[i].Size < to[j].Size
3139
}
3240

41+
// Swap swaps the elements with indexes i and j.
42+
// It performs bounds checking to prevent panics.
3343
func (to TopObjects) Swap(i, j int) {
44+
if i < 0 || i >= len(to) || j < 0 || j >= len(to) {
45+
return // Out of bounds, no-op
46+
}
3447
to[i], to[j] = to[j], to[i]
3548
}
3649

50+
// Push adds an element to the heap. It handles nil values safely and only adds
51+
// valid *ObjectMetadata pointers to the slice.
3752
func (to *TopObjects) Push(x interface{}) {
38-
item := x.(*ObjectMetadata)
39-
if item == nil {
53+
if x == nil {
54+
return
55+
}
56+
item, ok := x.(*ObjectMetadata)
57+
if !ok || item == nil {
4058
return
4159
}
4260
*to = append(*to, item)
4361
}
4462

63+
// Pop removes and returns the smallest element from the heap.
64+
// The result is the element that would be returned by Pop() from the heap package.
4565
func (to *TopObjects) Pop() interface{} {
4666
old := *to
4767
n := len(old)
@@ -51,14 +71,24 @@ func (to *TopObjects) Pop() interface{} {
5171
return item
5272
}
5373

74+
// List returns a sorted slice of ObjectMetadata, with the largest elements first.
75+
// It creates a copy of the underlying data to preserve the original heap.
76+
// Returns nil if the receiver is nil.
5477
func (to TopObjects) List() []*ObjectMetadata {
55-
list := []*ObjectMetadata{}
56-
for len(to) > 0 {
57-
item := heap.Pop(&to).(*ObjectMetadata)
58-
if item == nil {
59-
continue
78+
if to == nil {
79+
return nil
80+
}
81+
82+
tmp := make(TopObjects, len(to))
83+
copy(tmp, to)
84+
heap.Init(&tmp)
85+
86+
list := make([]*ObjectMetadata, 0, len(to))
87+
for tmp.Len() > 0 {
88+
item := heap.Pop(&tmp).(*ObjectMetadata)
89+
if item != nil {
90+
list = append([]*ObjectMetadata{item}, list...) // prepend to maintain order
6091
}
61-
list = append([]*ObjectMetadata{item}, list...)
6292
}
6393

6494
return list
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package dockerimage
2+
3+
import (
4+
"container/heap"
5+
"testing"
6+
)
7+
8+
func TestTopObjects_List_NilSafety(t *testing.T) {
9+
tests := []struct {
10+
name string
11+
setup func(t *testing.T) TopObjects
12+
wantLen int
13+
}{
14+
{
15+
name: "nil TopObjects",
16+
setup: func(t *testing.T) TopObjects {
17+
return nil
18+
},
19+
wantLen: 0,
20+
},
21+
{
22+
name: "empty TopObjects",
23+
setup: func(t *testing.T) TopObjects {
24+
return NewTopObjects(0)
25+
},
26+
wantLen: 0,
27+
},
28+
{
29+
name: "TopObjects with nil elements",
30+
setup: func(t *testing.T) TopObjects {
31+
to := NewTopObjects(3)
32+
heap.Push(&to, &ObjectMetadata{Name: "file1", Size: 100})
33+
// Test both interface{} nil and typed nil
34+
heap.Push(&to, nil)
35+
heap.Push(&to, (*ObjectMetadata)(nil))
36+
heap.Push(&to, &ObjectMetadata{Name: "file2", Size: 200})
37+
return to
38+
},
39+
wantLen: 2, // Should only contain non-nil elements
40+
},
41+
{
42+
name: "TopObjects with multiple elements",
43+
setup: func(t *testing.T) TopObjects {
44+
to := NewTopObjects(3)
45+
heap.Push(&to, &ObjectMetadata{Name: "file1", Size: 100})
46+
heap.Push(&to, &ObjectMetadata{Name: "file2", Size: 200})
47+
heap.Push(&to, &ObjectMetadata{Name: "file3", Size: 50})
48+
return to
49+
},
50+
wantLen: 3,
51+
},
52+
}
53+
54+
for _, tt := range tests {
55+
t.Run(tt.name, func(t *testing.T) {
56+
to := tt.setup(t)
57+
result := to.List()
58+
59+
if len(result) != tt.wantLen {
60+
t.Errorf("List() returned %d items, want %d", len(result), tt.wantLen)
61+
}
62+
63+
// Verify the order is correct (descending by size)
64+
for i := 1; i < len(result); i++ {
65+
if result[i-1] == nil || result[i] == nil {
66+
t.Fatal("List() contains nil elements")
67+
}
68+
if result[i-1].Size < result[i].Size {
69+
t.Errorf("List() is not sorted in descending order: %d < %d at index %d", result[i-1].Size, result[i].Size, i)
70+
}
71+
}
72+
})
73+
}
74+
}
75+
76+
func TestTopObjects_List_ModificationSafety(t *testing.T) {
77+
// Test that the original TopObjects is not modified by List()
78+
to := NewTopObjects(3)
79+
heap.Push(&to, &ObjectMetadata{Name: "file1", Size: 100})
80+
heap.Push(&to, &ObjectMetadata{Name: "file2", Size: 200})
81+
82+
originalLen := to.Len()
83+
result := to.List()
84+
85+
// Modify the result slice
86+
if len(result) > 0 {
87+
result[0] = &ObjectMetadata{Name: "modified", Size: 999}
88+
}
89+
90+
// The original TopObjects should not be affected
91+
if to.Len() != originalLen {
92+
t.Errorf("Original TopObjects length changed from %d to %d", originalLen, to.Len())
93+
}
94+
}

0 commit comments

Comments
 (0)