Skip to content

Commit f30034d

Browse files
committed
Allow calling Delete() inside Walk() and WalkPrefix()
In recursiveWalk called by Walk and WalkPrefix, we were iterating over the edges of a node. The edges as well as the current node object itself (when mergeChild is called) can be modified by calls to Delete. This commit modifies the loop to handle modifications correctly and prevent a nil pointer dereference panic in the case detailed in TestWalkDelete. Signed-off-by: Tibor Vass <[email protected]>
1 parent f1d6689 commit f30034d

2 files changed

Lines changed: 52 additions & 1 deletion

File tree

radix.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,10 +525,27 @@ func recursiveWalk(n *node, fn WalkFn) bool {
525525
}
526526

527527
// Recurse on the children
528-
for _, e := range n.edges {
528+
i := 0
529+
k := len(n.edges) // keeps track of number of edges in previous iteration
530+
for i < k {
531+
e := n.edges[i]
529532
if recursiveWalk(e.node, fn) {
530533
return true
531534
}
535+
// It is a possibility that the WalkFn modified the node we are
536+
// iterating on. If there are no more edges, mergeChild happened,
537+
// so the last edge became the current node n, on which we'll
538+
// iterate one last time.
539+
if len(n.edges) == 0 {
540+
return recursiveWalk(n, fn)
541+
}
542+
// If there are now less edges than in the previous iteration,
543+
// then do not increment the loop index, since the current index
544+
// points to a new edge. Otherwise, get to the next index.
545+
if len(n.edges) >= k {
546+
i++
547+
}
548+
k = len(n.edges)
532549
}
533550
return false
534551
}

radix_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,40 @@ func TestWalkPath(t *testing.T) {
344344
}
345345
}
346346

347+
func TestWalkDelete(t *testing.T) {
348+
r := New()
349+
r.Insert("init0/0", nil)
350+
r.Insert("init0/1", nil)
351+
r.Insert("init0/2", nil)
352+
r.Insert("init0/3", nil)
353+
r.Insert("init1/0", nil)
354+
r.Insert("init1/1", nil)
355+
r.Insert("init1/2", nil)
356+
r.Insert("init1/3", nil)
357+
r.Insert("init2", nil)
358+
359+
deleteFn := func(s string, v interface{}) bool {
360+
r.Delete(s)
361+
return false
362+
}
363+
364+
r.WalkPrefix("init1", deleteFn)
365+
366+
for _, s := range []string{"init0/0", "init0/1", "init0/2", "init0/3", "init2"} {
367+
if _, ok := r.Get(s); !ok {
368+
t.Fatalf("expecting to still find %q", s)
369+
}
370+
}
371+
if n := r.Len(); n != 5 {
372+
t.Fatalf("expected to find exactly 5 nodes, instead found %d: %v", n, r.ToMap())
373+
}
374+
375+
r.Walk(deleteFn)
376+
if n := r.Len(); n != 0 {
377+
t.Fatalf("expected to find exactly 0 nodes, instead found %d: %v", n, r.ToMap())
378+
}
379+
}
380+
347381
// generateUUID is used to generate a random UUID
348382
func generateUUID() string {
349383
buf := make([]byte, 16)

0 commit comments

Comments
 (0)