]> Cypherpunks repositories - gostls13.git/commitdiff
encoding/json/v2: avoid escaping jsonopts.Struct
authorJoe Tsai <joetsai@digital-static.net>
Mon, 30 Jun 2025 22:40:20 +0000 (15:40 -0700)
committerGopher Robot <gobot@golang.org>
Mon, 30 Jun 2025 23:52:49 +0000 (16:52 -0700)
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 <joetsai@digital-static.net>
Reviewed-by: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
src/encoding/json/internal/jsonopts/options.go
src/encoding/json/v2/options.go

index 2226830b6bcd9955b759bb31e390b5693d9c09b5..e4c3f47d36adc8f686c79dccef897d05d5999465 100644 (file)
@@ -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)
                }
        }
 }
index 12bbdb5d86bcf32336470b54177b06823f24a78e..0942d2d30784f96a738967ccebeb53ff1cc2f521 100644 (file)
@@ -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
        }
 }