@@ -812,22 +812,23 @@ func (e encoder) encodeEmbeddedStructPointer(b []byte, p unsafe.Pointer, t refle
812812}
813813
814814func (e encoder ) encodePointer (b []byte , p unsafe.Pointer , t reflect.Type , encode encodeFunc ) ([]byte , error ) {
815- if p = * (* unsafe .Pointer )(p ); p != nil {
816- if e .ptrDepth ++ ; e .ptrDepth >= startDetectingCyclesAfter {
817- if _ , seen := e .ptrSeen [p ]; seen {
818- // TODO: reconstruct the reflect.Value from p + t so we can set
819- // the erorr's Value field?
820- return b , & UnsupportedValueError {Str : fmt .Sprintf ("encountered a cycle via %s" , t )}
821- }
822- if e .ptrSeen == nil {
823- e .ptrSeen = make (map [unsafe.Pointer ]struct {})
824- }
825- e .ptrSeen [p ] = struct {}{}
826- defer delete (e .ptrSeen , p )
815+ // p was a pointer to the actual user data pointer:
816+ // dereference it to operate on the user data pointer.
817+ p = * (* unsafe .Pointer )(p )
818+ if p == nil {
819+ return e .encodeNull (b , nil )
820+ }
821+
822+ if shouldCheckForRefCycle (& e ) {
823+ key := cycleKey {ptr : p }
824+ if hasRefCycle (& e , key ) {
825+ return b , refCycleError (t , p )
827826 }
828- return encode (e , b , p )
827+
828+ defer freeRefCycleInfo (& e , key )
829829 }
830- return e .encodeNull (b , nil )
830+
831+ return encode (e , b , p )
831832}
832833
833834func (e encoder ) encodeInterface (b []byte , p unsafe.Pointer ) ([]byte , error ) {
@@ -986,3 +987,64 @@ func appendCompactEscapeHTML(dst []byte, src []byte) []byte {
986987
987988 return dst
988989}
990+
991+ // shouldCheckForRefCycle determines whether checking for reference cycles
992+ // is reasonable to do at this time.
993+ //
994+ // When true, checkRefCycle should be called and any error handled,
995+ // and then a deferred call to freeRefCycleInfo should be made.
996+ //
997+ // This should only be called from encoder methods that are possible points
998+ // that could directly contribute to a reference cycle.
999+ func shouldCheckForRefCycle (e * encoder ) bool {
1000+ // Note: do not combine this with checkRefCycle,
1001+ // because checkRefCycle is too large to be inlined,
1002+ // and a non-inlined depth check leads to ~5%+ benchmark degradation.
1003+ e .refDepth ++
1004+ return e .refDepth >= startDetectingCyclesAfter
1005+ }
1006+
1007+ // refCycleError constructs an [UnsupportedValueError].
1008+ func refCycleError (t reflect.Type , p unsafe.Pointer ) error {
1009+ v := reflect .NewAt (t , p )
1010+ return & UnsupportedValueError {
1011+ Value : v ,
1012+ Str : fmt .Sprintf ("encountered a cycle via %s" , t ),
1013+ }
1014+ }
1015+
1016+ // hasRefCycle returns an error if a reference cycle was detected.
1017+ // The data pointer passed in should be equivalent to one of:
1018+ //
1019+ // - A normal Go pointer, e.g. `unsafe.Pointer(&T)`
1020+ // - The pointer to a map header, e.g. `*(*unsafe.Pointer)(&map[K]V)`
1021+ //
1022+ // Many [encoder] methods accept a pointer-to-a-pointer,
1023+ // and so those may need to be derenced in order to safely pass them here.
1024+ func hasRefCycle (e * encoder , key cycleKey ) bool {
1025+ _ , seen := e .refSeen [key ]
1026+ if seen {
1027+ return true
1028+ }
1029+
1030+ if e .refSeen == nil {
1031+ e .refSeen = cycleMapPool .Get ().(cycleMap )
1032+ }
1033+
1034+ e .refSeen [key ] = struct {}{}
1035+
1036+ return false
1037+ }
1038+
1039+ // freeRefCycle performs the cleanup operation for [checkRefCycle].
1040+ // p must be the same value passed into a prior call to checkRefCycle.
1041+ func freeRefCycleInfo (e * encoder , key cycleKey ) {
1042+ delete (e .refSeen , key )
1043+ if len (e .refSeen ) == 0 {
1044+ // There are no remaining elements,
1045+ // so we can release this map for later reuse.
1046+ m := e .refSeen
1047+ e .refSeen = nil
1048+ cycleMapPool .Put (m )
1049+ }
1050+ }
0 commit comments