Skip to content

Commit 0c7b0d3

Browse files
lujjjhmvdan
authored andcommitted
encoding/json: detect cyclic maps and slices
Now reports an error if cyclic maps and slices are to be encoded instead of an infinite recursion. This case wasn't handled in CL 187920. Fixes #40745. Change-Id: Ia34b014ecbb71fd2663bb065ba5355a307dbcc15 GitHub-Last-Rev: 6f874944f4065b5237babbb0fdce14c1c74a3c97 GitHub-Pull-Request: golang/go#40756 Reviewed-on: https://go-review.googlesource.com/c/go/+/248358 Reviewed-by: Daniel Martí <[email protected]> Trust: Daniel Martí <[email protected]> Trust: Bryan C. Mills <[email protected]> Run-TryBot: Daniel Martí <[email protected]> TryBot-Result: Go Bot <[email protected]>
1 parent 41f7f67 commit 0c7b0d3

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-1
lines changed

json/encode.go

+27
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,16 @@ func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
779779
e.WriteString("null")
780780
return
781781
}
782+
if e.ptrLevel++; e.ptrLevel > startDetectingCyclesAfter {
783+
// We're a large number of nested ptrEncoder.encode calls deep;
784+
// start checking if we've run into a pointer cycle.
785+
ptr := v.Pointer()
786+
if _, ok := e.ptrSeen[ptr]; ok {
787+
e.error(&UnsupportedValueError{v, fmt.Sprintf("encountered a cycle via %s", v.Type())})
788+
}
789+
e.ptrSeen[ptr] = struct{}{}
790+
defer delete(e.ptrSeen, ptr)
791+
}
782792
e.WriteByte('{')
783793

784794
// Extract and sort the keys.
@@ -801,6 +811,7 @@ func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
801811
me.elemEnc(e, v.MapIndex(kv.v), opts)
802812
}
803813
e.WriteByte('}')
814+
e.ptrLevel--
804815
}
805816

806817
func newMapEncoder(t reflect.Type) encoderFunc {
@@ -857,7 +868,23 @@ func (se sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
857868
e.WriteString("null")
858869
return
859870
}
871+
if e.ptrLevel++; e.ptrLevel > startDetectingCyclesAfter {
872+
// We're a large number of nested ptrEncoder.encode calls deep;
873+
// start checking if we've run into a pointer cycle.
874+
// Here we use a struct to memorize the pointer to the first element of the slice
875+
// and its length.
876+
ptr := struct {
877+
ptr uintptr
878+
len int
879+
}{v.Pointer(), v.Len()}
880+
if _, ok := e.ptrSeen[ptr]; ok {
881+
e.error(&UnsupportedValueError{v, fmt.Sprintf("encountered a cycle via %s", v.Type())})
882+
}
883+
e.ptrSeen[ptr] = struct{}{}
884+
defer delete(e.ptrSeen, ptr)
885+
}
860886
se.arrayEnc(e, v, opts)
887+
e.ptrLevel--
861888
}
862889

863890
func newSliceEncoder(t reflect.Type) encoderFunc {

json/encode_test.go

+26-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,15 @@ type PointerCycleIndirect struct {
183183
Ptrs []interface{}
184184
}
185185

186-
var pointerCycleIndirect = &PointerCycleIndirect{}
186+
type RecursiveSlice []RecursiveSlice
187+
188+
var (
189+
pointerCycleIndirect = &PointerCycleIndirect{}
190+
mapCycle = make(map[string]interface{})
191+
sliceCycle = []interface{}{nil}
192+
sliceNoCycle = []interface{}{nil, nil}
193+
recursiveSliceCycle = []RecursiveSlice{nil}
194+
)
187195

188196
func init() {
189197
ptr := &SamePointerNoCycle{}
@@ -192,6 +200,14 @@ func init() {
192200

193201
pointerCycle.Ptr = pointerCycle
194202
pointerCycleIndirect.Ptrs = []interface{}{pointerCycleIndirect}
203+
204+
mapCycle["x"] = mapCycle
205+
sliceCycle[0] = sliceCycle
206+
sliceNoCycle[1] = sliceNoCycle[:1]
207+
for i := startDetectingCyclesAfter; i > 0; i-- {
208+
sliceNoCycle = []interface{}{sliceNoCycle}
209+
}
210+
recursiveSliceCycle[0] = recursiveSliceCycle
195211
}
196212

197213
func TestSamePointerNoCycle(t *testing.T) {
@@ -200,12 +216,21 @@ func TestSamePointerNoCycle(t *testing.T) {
200216
}
201217
}
202218

219+
func TestSliceNoCycle(t *testing.T) {
220+
if _, err := Marshal(sliceNoCycle); err != nil {
221+
t.Fatalf("unexpected error: %v", err)
222+
}
223+
}
224+
203225
var unsupportedValues = []interface{}{
204226
math.NaN(),
205227
math.Inf(-1),
206228
math.Inf(1),
207229
pointerCycle,
208230
pointerCycleIndirect,
231+
mapCycle,
232+
sliceCycle,
233+
recursiveSliceCycle,
209234
}
210235

211236
func TestUnsupportedValues(t *testing.T) {

0 commit comments

Comments
 (0)