Skip to content

Commit 5ea40da

Browse files
committed
encoding/json: detect cyclic maps and slices
The documentation says: JSON cannot represent cyclic data structures and Marshal does not handle them. Passing cyclic structures to Marshal will result in an error. However, passing cyclic maps or slices still results in an infinite recursion. Cycle detection is now added into mapEncoder and sliceEncoder. name old time/op new time/op delta CodeEncoder-16 933µs ± 0% 1324µs ± 0% ~ (p=1.000 n=1+1) CodeMarshal-16 940µs ± 0% 1436µs ± 0% ~ (p=1.000 n=1+1) name old speed new speed delta CodeEncoder-16 2.08GB/s ± 0% 1.47GB/s ± 0% ~ (p=1.000 n=1+1) CodeMarshal-16 2.06GB/s ± 0% 1.35GB/s ± 0% ~ (p=1.000 n=1+1) name old alloc/op new alloc/op delta CodeEncoder-16 7.00B ± 0% 94933.00B ± 0% ~ (p=1.000 n=1+1) CodeMarshal-16 1.98MB ± 0% 2.04MB ± 0% ~ (p=1.000 n=1+1) name old allocs/op new allocs/op delta CodeEncoder-16 0.00 11816.00 ± 0% ~ (p=1.000 n=1+1) CodeMarshal-16 1.00 ± 0% 11816.00 ± 0% ~ (p=1.000 n=1+1) Fixes #40745.
1 parent c2e73fb commit 5ea40da

File tree

2 files changed

+32
-1
lines changed

2 files changed

+32
-1
lines changed

src/encoding/json/encode.go

+22
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,18 @@ 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+
ptr := v.Pointer()
875+
if _, ok := e.ptrSeen[ptr]; ok {
876+
e.error(&UnsupportedValueError{v, fmt.Sprintf("encountered a cycle via %s", v.Type())})
877+
}
878+
e.ptrSeen[ptr] = struct{}{}
879+
defer delete(e.ptrSeen, ptr)
880+
}
860881
se.arrayEnc(e, v, opts)
882+
e.ptrLevel--
861883
}
862884

863885
func newSliceEncoder(t reflect.Type) encoderFunc {

src/encoding/json/encode_test.go

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

186-
var pointerCycleIndirect = &PointerCycleIndirect{}
186+
var (
187+
pointerCycleIndirect = &PointerCycleIndirect{}
188+
mapCycle = make(map[string]interface{})
189+
sliceCycle = []interface{}{nil}
190+
)
187191

188192
func init() {
189193
ptr := &SamePointerNoCycle{}
@@ -192,6 +196,9 @@ func init() {
192196

193197
pointerCycle.Ptr = pointerCycle
194198
pointerCycleIndirect.Ptrs = []interface{}{pointerCycleIndirect}
199+
200+
mapCycle["x"] = mapCycle
201+
sliceCycle[0] = sliceCycle
195202
}
196203

197204
func TestSamePointerNoCycle(t *testing.T) {
@@ -206,6 +213,8 @@ var unsupportedValues = []interface{}{
206213
math.Inf(1),
207214
pointerCycle,
208215
pointerCycleIndirect,
216+
mapCycle,
217+
sliceCycle,
209218
}
210219

211220
func TestUnsupportedValues(t *testing.T) {

0 commit comments

Comments
 (0)