Skip to content

Commit 37e513f

Browse files
author
Achille
authored
add test for issue 107 (#108)
* add test for issue 107 * fix issue 107
1 parent 47bdac2 commit 37e513f

File tree

4 files changed

+58
-4
lines changed

4 files changed

+58
-4
lines changed

json/codec.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,32 @@ import (
1616
"github.com/segmentio/asm/keyset"
1717
)
1818

19+
const (
20+
// 1000 is the value used by the standard encoding/json package.
21+
//
22+
// https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/encoding/json/encode.go;drc=refs%2Ftags%2Fgo1.17.3;l=300
23+
startDetectingCyclesAfter = 1000
24+
)
25+
1926
type codec struct {
2027
encode encodeFunc
2128
decode decodeFunc
2229
}
2330

24-
type encoder struct{ flags AppendFlags }
25-
type decoder struct{ flags ParseFlags }
31+
type encoder struct {
32+
flags AppendFlags
33+
// ptrDepth tracks the depth of pointer cycles, when it reaches the value
34+
// of startDetectingCyclesAfter, the ptrSeen map is allocated and the
35+
// encoder starts tracking pointers it has seen as an attempt to detect
36+
// whether it has entered a pointer cycle and needs to error before the
37+
// goroutine runs out of stack space.
38+
ptrDepth uint32
39+
ptrSeen map[unsafe.Pointer]struct{}
40+
}
41+
42+
type decoder struct {
43+
flags ParseFlags
44+
}
2645

2746
type encodeFunc func(encoder, []byte, unsafe.Pointer) ([]byte, error)
2847
type decodeFunc func(decoder, []byte, unsafe.Pointer) ([]byte, error)

json/encode.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package json
22

33
import (
44
"encoding"
5+
"fmt"
56
"math"
67
"reflect"
78
"sort"
@@ -815,6 +816,18 @@ func (e encoder) encodeEmbeddedStructPointer(b []byte, p unsafe.Pointer, t refle
815816

816817
func (e encoder) encodePointer(b []byte, p unsafe.Pointer, t reflect.Type, encode encodeFunc) ([]byte, error) {
817818
if p = *(*unsafe.Pointer)(p); p != nil {
819+
if e.ptrDepth++; e.ptrDepth >= startDetectingCyclesAfter {
820+
if _, seen := e.ptrSeen[p]; seen {
821+
// TODO: reconstruct the reflect.Value from p + t so we can set
822+
// the erorr's Value field?
823+
return b, &UnsupportedValueError{Str: fmt.Sprintf("encountered a cycle via %s", t)}
824+
}
825+
if e.ptrSeen == nil {
826+
e.ptrSeen = make(map[unsafe.Pointer]struct{})
827+
}
828+
e.ptrSeen[p] = struct{}{}
829+
defer delete(e.ptrSeen, p)
830+
}
818831
return encode(e, b, p)
819832
}
820833
return e.encodeNull(b, nil)

json/json.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ type UnsupportedValueError = json.UnsupportedValueError
5555

5656
// AppendFlags is a type used to represent configuration options that can be
5757
// applied when formatting json output.
58-
type AppendFlags uint
58+
type AppendFlags uint32
5959

6060
const (
6161
// EscapeHTML is a formatting flag used to to escape HTML in json strings.
@@ -74,7 +74,7 @@ const (
7474

7575
// ParseFlags is a type used to represent configuration options that can be
7676
// applied when parsing json input.
77-
type ParseFlags uint
77+
type ParseFlags uint32
7878

7979
func (flags ParseFlags) has(f ParseFlags) bool {
8080
return (flags & f) != 0

json/json_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1591,6 +1591,28 @@ func TestGithubIssue44(t *testing.T) {
15911591
}
15921592
}
15931593

1594+
type issue107Foo struct {
1595+
Bar *issue107Bar
1596+
}
1597+
1598+
type issue107Bar struct {
1599+
Foo *issue107Foo
1600+
}
1601+
1602+
func TestGithubIssue107(t *testing.T) {
1603+
f := &issue107Foo{}
1604+
b := &issue107Bar{}
1605+
f.Bar = b
1606+
b.Foo = f
1607+
1608+
_, err := Marshal(f) // must not crash
1609+
switch err.(type) {
1610+
case *UnsupportedValueError:
1611+
default:
1612+
t.Errorf("marshaling a cycling data structure was expected to return an unsupported value error but got %T", err)
1613+
}
1614+
}
1615+
15941616
type rawJsonString string
15951617

15961618
func (r *rawJsonString) UnmarshalJSON(b []byte) error {

0 commit comments

Comments
 (0)