Skip to content

Commit 4db71be

Browse files
authored
Prevent infinite loop within serialization methods (#287)
1 parent d17f3fa commit 4db71be

File tree

2 files changed

+61
-0
lines changed

2 files changed

+61
-0
lines changed

testutils/go/predefined.go

+28
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package goutils
22

33
import (
44
"encoding/json"
5+
"errors"
56
"strconv"
67
"strings"
78
)
89

10+
var ErrInfiniteLoop = errors.New("infinite loop detected")
11+
912
/*
1013
Much appreciated to EndlessCheng
1114
Adapted from https://github.com/EndlessCheng/codeforces-go/blob/ae5b312f3f/leetcode/testutil/leetcode.go
@@ -36,14 +39,23 @@ func DeserializeListNode(s string) (*ListNode, error) {
3639
return root, nil
3740
}
3841

42+
// ToString returns a string representation of the linked list.
43+
// It panics with ErrInfiniteLoop if a cycle is detected.
3944
func (l *ListNode) ToString() string {
45+
seen := make(map[*ListNode]bool, 10)
46+
4047
sb := &strings.Builder{}
4148
sb.WriteByte('[')
4249
for ; l != nil; l = l.Next {
4350
if sb.Len() > 1 {
4451
sb.WriteByte(',')
4552
}
4653
sb.WriteString(strconv.Itoa(l.Val))
54+
55+
if seen[l] {
56+
panic(ErrInfiniteLoop)
57+
}
58+
seen[l] = true
4759
}
4860
sb.WriteByte(']')
4961
return sb.String()
@@ -104,13 +116,21 @@ func DeserializeTreeNode(s string) (*TreeNode, error) {
104116
return root, nil
105117
}
106118

119+
// ToString returns a string representation of the binary tree.
120+
// It panics with ErrInfiniteLoop if a cycle is detected.
107121
func (t *TreeNode) ToString() string {
108122
nodes := []*TreeNode{}
109123
queue := []*TreeNode{t}
124+
seen := make(map[*TreeNode]bool, 10)
110125
for len(queue) > 0 {
111126
t, queue = queue[0], queue[1:]
112127
nodes = append(nodes, t)
113128
if t != nil {
129+
if seen[t] {
130+
panic(ErrInfiniteLoop)
131+
}
132+
seen[t] = true
133+
114134
queue = append(queue, t.Left, t.Right)
115135
}
116136
}
@@ -164,16 +184,24 @@ func DeserializeNaryTreeNode(s string) (*NaryTreeNode, error) {
164184
return root.Children[0], nil
165185
}
166186

187+
// ToString returns a string representation of the nary tree.
188+
// It panics with ErrInfiniteLoop if a cycle is detected.
167189
func (t *NaryTreeNode) ToString() string {
168190
nodes := []*NaryTreeNode{}
169191
q := []*NaryTreeNode{{Children: []*NaryTreeNode{t}}}
192+
seen := make(map[*NaryTreeNode]bool, 10)
170193

171194
for len(q) > 0 {
172195
node := q[0]
173196
q = q[1:]
174197
nodes = append(nodes, node)
175198

176199
if node != nil {
200+
if seen[node] {
201+
panic(ErrInfiniteLoop)
202+
}
203+
seen[node] = true
204+
177205
if len(node.Children) > 0 {
178206
q = append(q, node.Children...)
179207
}

testutils/go/serialize_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package goutils
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestInfiniteLoopDetect(t *testing.T) {
10+
11+
linkedList := &ListNode{Val: 1}
12+
linkedList.Next = &ListNode{Val: 2, Next: linkedList}
13+
14+
tree := &TreeNode{Val: 1}
15+
tree.Left = &TreeNode{Val: 2, Right: tree}
16+
17+
naryTree := &NaryTreeNode{Val: 1}
18+
naryTree.Children = []*NaryTreeNode{{Val: 2, Children: []*NaryTreeNode{naryTree}}}
19+
20+
type toStringer interface {
21+
ToString() string
22+
}
23+
24+
tests := []toStringer{
25+
linkedList,
26+
tree,
27+
naryTree,
28+
}
29+
30+
for _, tc := range tests {
31+
assert.PanicsWithValue(t, ErrInfiniteLoop, func() { tc.ToString() })
32+
}
33+
}

0 commit comments

Comments
 (0)