From: Joe Tsai Date: Mon, 30 Jun 2025 22:40:20 +0000 (-0700) Subject: encoding/json/v2: avoid escaping jsonopts.Struct X-Git-Tag: go1.25rc2~2^2~6 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=6bd9944c9a9fb6b1da6288358b0620218d621441;p=gostls13.git encoding/json/v2: avoid escaping jsonopts.Struct The jsonopts.Struct.join method unfortunately escapes the receiver because it is passed to JoinUnknownOption, which is a dynamically implemented function. This affects jsontext.Encoder.reset and jsontext.Decoder.reset, which relied on a local jsonopts.Struct to temporarily store prior options such that it would have to be heap allocated. Adjust the signature of JoinUnknownOption to avoid pointers so that nothing escape. This is a regression from https://github.com/go-json-experiment/json/pull/163 Performance: name old time/op new time/op delta Marshal/Bool-32 72.1ns ± 2% 51.3ns ± 1% -28.77% (p=0.000 n=10+9) name old allocs/op new allocs/op delta Marshal/Bool-32 2.00 ± 0% 1.00 ± 0% -50.00% (p=0.000 n=10+10) Updates #71845 Change-Id: Ife500d82d3d2beb13652553a4ffdf882c136f5a0 Reviewed-on: https://go-review.googlesource.com/c/go/+/685135 Auto-Submit: Joseph Tsai Reviewed-by: Damien Neil LUCI-TryBot-Result: Go LUCI Reviewed-by: Dmitri Shuralyov --- diff --git a/src/encoding/json/internal/jsonopts/options.go b/src/encoding/json/internal/jsonopts/options.go index 2226830b6b..e4c3f47d36 100644 --- a/src/encoding/json/internal/jsonopts/options.go +++ b/src/encoding/json/internal/jsonopts/options.go @@ -65,7 +65,7 @@ func (*Struct) JSONOptions(internal.NotForPublicUse) {} // GetUnknownOption is injected by the "json" package to handle Options // declared in that package so that "jsonopts" can handle them. -var GetUnknownOption = func(*Struct, Options) (any, bool) { panic("unknown option") } +var GetUnknownOption = func(Struct, Options) (any, bool) { panic("unknown option") } func GetOption[T any](opts Options, setter func(T) Options) (T, bool) { // Collapse the options to *Struct to simplify lookup. @@ -104,14 +104,14 @@ func GetOption[T any](opts Options, setter func(T) Options) (T, bool) { } return any(structOpts.DepthLimit).(T), true default: - v, ok := GetUnknownOption(structOpts, opt) + v, ok := GetUnknownOption(*structOpts, opt) return v.(T), ok } } // JoinUnknownOption is injected by the "json" package to handle Options // declared in that package so that "jsonopts" can handle them. -var JoinUnknownOption = func(*Struct, Options) { panic("unknown option") } +var JoinUnknownOption = func(Struct, Options) Struct { panic("unknown option") } func (dst *Struct) Join(srcs ...Options) { dst.join(false, srcs...) @@ -182,7 +182,7 @@ func (dst *Struct) join(excludeCoderOptions bool, srcs ...Options) { } } default: - JoinUnknownOption(dst, src) + *dst = JoinUnknownOption(*dst, src) } } } diff --git a/src/encoding/json/v2/options.go b/src/encoding/json/v2/options.go index 12bbdb5d86..0942d2d307 100644 --- a/src/encoding/json/v2/options.go +++ b/src/encoding/json/v2/options.go @@ -257,7 +257,7 @@ func (*unmarshalersOption) JSONOptions(internal.NotForPublicUse) {} // Inject support into "jsonopts" to handle these types. func init() { - jsonopts.GetUnknownOption = func(src *jsonopts.Struct, zero jsonopts.Options) (any, bool) { + jsonopts.GetUnknownOption = func(src jsonopts.Struct, zero jsonopts.Options) (any, bool) { switch zero.(type) { case *marshalersOption: if !src.Flags.Has(jsonflags.Marshalers) { @@ -273,7 +273,7 @@ func init() { panic(fmt.Sprintf("unknown option %T", zero)) } } - jsonopts.JoinUnknownOption = func(dst *jsonopts.Struct, src jsonopts.Options) { + jsonopts.JoinUnknownOption = func(dst jsonopts.Struct, src jsonopts.Options) jsonopts.Struct { switch src := src.(type) { case *marshalersOption: dst.Flags.Set(jsonflags.Marshalers | 1) @@ -284,5 +284,6 @@ func init() { default: panic(fmt.Sprintf("unknown option %T", src)) } + return dst } }