From 542d7d549ffdd5c43c1d3bdc68bde840e9f71f19 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Tue, 12 May 2026 12:52:03 -0400 Subject: [PATCH 01/32] crypto/tls: let Config.CurvePreferences override GODEBUG options tlsmlkem=0 and tlssecpmlkem=0 were never meant to forcibly disable PQ KEMs, they were only meant to restore the Go 1.24 and Go 1.26 defaults when Config.CurvePreferences is nil. I noticed this while struggling to add a non-default key exchange. While at it, make our behavior on unimplemented Config.CurvePreferences entries more consistent by ignoring them regardless of role. Udpates #69985 Updates #71206 Change-Id: I7d977282153b1d95fdb549efa92353e86a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/777220 Auto-Submit: Filippo Valsorda Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: David Chase --- .../6-stdlib/99-minor/crypto/tls/tlsmlkem.md | 4 ++ src/crypto/tls/common.go | 39 +++++++++++-------- src/crypto/tls/defaults.go | 36 ++++++++++------- src/crypto/tls/fips140_test.go | 4 +- src/crypto/tls/handshake_client.go | 2 +- src/crypto/tls/handshake_client_tls13.go | 2 +- src/crypto/tls/handshake_server_tls13.go | 2 +- src/crypto/tls/key_agreement.go | 2 +- src/crypto/tls/tls_test.go | 15 +++++++ 9 files changed, 70 insertions(+), 36 deletions(-) create mode 100644 doc/next/6-stdlib/99-minor/crypto/tls/tlsmlkem.md diff --git a/doc/next/6-stdlib/99-minor/crypto/tls/tlsmlkem.md b/doc/next/6-stdlib/99-minor/crypto/tls/tlsmlkem.md new file mode 100644 index 00000000000000..834978e1ec39a4 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/tls/tlsmlkem.md @@ -0,0 +1,4 @@ +Post-quantum hybrid key exchanges can now be explicitly enabled in +[Config.CurvePreferences] even if the `tlsmlkem=0` or `tlssecpmlkem=0` GODEBUG +options are used. Those options were always meant to only apply to the default +set used when [Config.CurvePreferences] is nil. diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index a4343c34f394cd..be4921effa702e 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -1276,25 +1276,32 @@ func supportedVersionsFromMax(maxVersion uint16) []uint16 { } func (c *Config) curvePreferences(version uint16) []CurveID { - curvePreferences := defaultCurvePreferences() - if fips140tls.Required() { - curvePreferences = slices.DeleteFunc(curvePreferences, func(x CurveID) bool { - return !slices.Contains(allowedCurvePreferencesFIPS, x) - }) - } + return slices.DeleteFunc(curvePreferenceOrder(), func(x CurveID) bool { + return !c.supportsCurve(version, x) + }) +} + +func (c *Config) supportsCurve(version uint16, x CurveID) bool { if c != nil && len(c.CurvePreferences) != 0 { - curvePreferences = slices.DeleteFunc(curvePreferences, func(x CurveID) bool { - return !slices.Contains(c.CurvePreferences, x) - }) + if !slices.Contains(c.CurvePreferences, x) { + return false + } + // Ignore unimplemented entries in c.CurvePreferences. + if !slices.Contains(curvePreferenceOrder(), x) { + return false + } + } else { + if !defaultCurveEnabled(x) { + return false + } + } + if fips140tls.Required() && !slices.Contains(allowedCurvePreferencesFIPS, x) { + return false } - if version < VersionTLS13 { - curvePreferences = slices.DeleteFunc(curvePreferences, isTLS13OnlyKeyExchange) + if version < VersionTLS13 && isTLS13OnlyKeyExchange(x) { + return false } - return curvePreferences -} - -func (c *Config) supportsCurve(version uint16, curve CurveID) bool { - return slices.Contains(c.curvePreferences(version), curve) + return true } // mutualVersion returns the protocol version to use given the advertised diff --git a/src/crypto/tls/defaults.go b/src/crypto/tls/defaults.go index 8bacaed29f60d2..0eff24d576924f 100644 --- a/src/crypto/tls/defaults.go +++ b/src/crypto/tls/defaults.go @@ -13,24 +13,32 @@ import ( // Defaults are collected in this file to allow distributions to more easily patch // them to apply local policies. +// tlsmlkem=0 restores the pre-Go 1.24 default key exchanges. var tlsmlkem = godebug.New("tlsmlkem") + +// tlssecpmlkem=0 restores the pre-Go 1.26 default key exchanges. var tlssecpmlkem = godebug.New("tlssecpmlkem") -// defaultCurvePreferences is the default set of supported key exchanges, as -// well as the preference order. -func defaultCurvePreferences() []CurveID { - switch { - // tlsmlkem=0 restores the pre-Go 1.24 default. - case tlsmlkem.Value() == "0": - return []CurveID{X25519, CurveP256, CurveP384, CurveP521} - // tlssecpmlkem=0 restores the pre-Go 1.26 default. - case tlssecpmlkem.Value() == "0": - return []CurveID{X25519MLKEM768, X25519, CurveP256, CurveP384, CurveP521} +// defaultCurveEnabled returns whether the key exchange c is enabled by default. +func defaultCurveEnabled(c CurveID) bool { + switch c { + case X25519, CurveP256, CurveP384, CurveP521: + return true + case X25519MLKEM768: + return tlsmlkem.Value() != "0" + case SecP256r1MLKEM768, SecP384r1MLKEM1024: + return tlsmlkem.Value() != "0" && tlssecpmlkem.Value() != "0" default: - return []CurveID{ - X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, - X25519, CurveP256, CurveP384, CurveP521, - } + return false + } +} + +// curvePreferenceOrder is the fixed preference order of key exchanges. It must +// include every supported key exchange. +func curvePreferenceOrder() []CurveID { + return []CurveID{ + X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, + X25519, CurveP256, CurveP384, CurveP521, } } diff --git a/src/crypto/tls/fips140_test.go b/src/crypto/tls/fips140_test.go index 1f0702b6574642..232a8a219cebd1 100644 --- a/src/crypto/tls/fips140_test.go +++ b/src/crypto/tls/fips140_test.go @@ -233,7 +233,7 @@ func TestFIPSServerCipherSuites(t *testing.T) { } func TestFIPSServerCurves(t *testing.T) { - for _, curveid := range defaultCurvePreferences() { + for _, curveid := range curvePreferenceOrder() { t.Run(fmt.Sprintf("curve=%v", curveid), func(t *testing.T) { testConfig := testConfigFIPS140.Clone() testConfig.CurvePreferences = []CurveID{curveid} @@ -342,7 +342,7 @@ func testFIPSClientHello(t *testing.T) { clientConfig.MinVersion = VersionSSL30 clientConfig.MaxVersion = VersionTLS13 clientConfig.CipherSuites = allCipherSuitesIncludingTLS13() - clientConfig.CurvePreferences = defaultCurvePreferences() + clientConfig.CurvePreferences = curvePreferenceOrder() go Client(c, clientConfig).Handshake() srv := Server(s, testConfigFIPS140) diff --git a/src/crypto/tls/handshake_client.go b/src/crypto/tls/handshake_client.go index 9c07a3e2833484..04f6ff62d5d552 100644 --- a/src/crypto/tls/handshake_client.go +++ b/src/crypto/tls/handshake_client.go @@ -148,7 +148,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli curveID := hello.supportedCurves[0] ke, err := keyExchangeForCurveID(curveID) if err != nil { - return nil, nil, nil, errors.New("tls: CurvePreferences includes unsupported curve") + return nil, nil, nil, errors.New("tls: internal error: supportsCurve accepted unimplemented curve") } keyShareKeys, hello.keyShares, err = ke.keyShares(config.rand()) if err != nil { diff --git a/src/crypto/tls/handshake_client_tls13.go b/src/crypto/tls/handshake_client_tls13.go index 4ddd05284c15b0..15f07ed1a23bd6 100644 --- a/src/crypto/tls/handshake_client_tls13.go +++ b/src/crypto/tls/handshake_client_tls13.go @@ -322,7 +322,7 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { ke, err := keyExchangeForCurveID(curveID) if err != nil { c.sendAlert(alertInternalError) - return errors.New("tls: CurvePreferences includes unsupported curve") + return errors.New("tls: internal error: supportsCurve accepted unimplemented curve") } hs.keyShareKeys, hello.keyShares, err = ke.keyShares(c.config.rand()) if err != nil { diff --git a/src/crypto/tls/handshake_server_tls13.go b/src/crypto/tls/handshake_server_tls13.go index 48e0cf50a02542..74b379e766c9ba 100644 --- a/src/crypto/tls/handshake_server_tls13.go +++ b/src/crypto/tls/handshake_server_tls13.go @@ -249,7 +249,7 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { ke, err := keyExchangeForCurveID(selectedGroup) if err != nil { c.sendAlert(alertInternalError) - return errors.New("tls: CurvePreferences includes unsupported curve") + return errors.New("tls: internal error: supportsCurve accepted unimplemented curve") } hs.sharedKey, hs.hello.serverShare, err = ke.serverSharedSecret(c.config.rand(), clientKeyShare.data) if err != nil { diff --git a/src/crypto/tls/key_agreement.go b/src/crypto/tls/key_agreement.go index 126a29ffa6807a..eef729d53d753d 100644 --- a/src/crypto/tls/key_agreement.go +++ b/src/crypto/tls/key_agreement.go @@ -167,7 +167,7 @@ func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Cer return nil, errors.New("tls: no supported elliptic curves offered") } if _, ok := curveForCurveID(ka.curveID); !ok { - return nil, errors.New("tls: CurvePreferences includes unsupported curve") + return nil, errors.New("tls: internal error: supportsCurve accepted unimplemented curve") } key, err := generateECDHEKey(config.rand(), ka.curveID) diff --git a/src/crypto/tls/tls_test.go b/src/crypto/tls/tls_test.go index 01e398db837726..51d53f9aaa0061 100644 --- a/src/crypto/tls/tls_test.go +++ b/src/crypto/tls/tls_test.go @@ -2156,6 +2156,21 @@ func TestHandshakeMLKEM(t *testing.T) { expectClient: []CurveID{X25519MLKEM768, X25519, CurveP256, CurveP384, CurveP521}, expectSelected: X25519MLKEM768, }, + { + name: "CurvePreferences override GODEBUG", + preparation: func(t *testing.T) { + testenv.SetGODEBUG(t, "tlsmlkem=0") + testenv.SetGODEBUG(t, "tlssecpmlkem=0") + }, + clientConfig: func(config *Config) { + config.CurvePreferences = []CurveID{CurveP256, SecP256r1MLKEM768} + }, + serverConfig: func(config *Config) { + config.CurvePreferences = []CurveID{CurveP256, SecP256r1MLKEM768} + }, + expectClient: []CurveID{SecP256r1MLKEM768, CurveP256}, + expectSelected: SecP256r1MLKEM768, + }, } baseServerConfig := testConfigServer.Clone() From 27532dc35c2db60715622dcd2ad0a762108cd3dd Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Sun, 12 Apr 2026 17:05:35 +0200 Subject: [PATCH 02/32] crypto/tls: deprecate Config.Rand We now use SetGlobalRandom in recorded tests. It was already partially ineffective: ML-KEM encapsulation, used as part of X25519MLKEM768, doesn't take a source of randomness, and instead always uses the global random source. Fixes #79367 Change-Id: I1cc07ebec21bee32ece685efde188c0d6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/765926 Auto-Submit: Filippo Valsorda LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Roland Shoemaker Reviewed-by: David Chase --- api/next/79367.txt | 1 + .../6-stdlib/99-minor/crypto/tls/79367.md | 2 ++ src/crypto/tls/common.go | 9 ++++-- src/crypto/tls/example_test.go | 24 ++++----------- src/crypto/tls/handshake_client_test.go | 30 ++++++++++--------- src/crypto/tls/handshake_test.go | 8 ----- 6 files changed, 31 insertions(+), 43 deletions(-) create mode 100644 api/next/79367.txt create mode 100644 doc/next/6-stdlib/99-minor/crypto/tls/79367.md diff --git a/api/next/79367.txt b/api/next/79367.txt new file mode 100644 index 00000000000000..3213d155052b98 --- /dev/null +++ b/api/next/79367.txt @@ -0,0 +1 @@ +pkg crypto/tls, type Config struct, Rand //deprecated #79367 diff --git a/doc/next/6-stdlib/99-minor/crypto/tls/79367.md b/doc/next/6-stdlib/99-minor/crypto/tls/79367.md new file mode 100644 index 00000000000000..f5a37f8ee9b008 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/tls/79367.md @@ -0,0 +1,2 @@ +[Config.Rand] is now deprecated. +For deterministic testing, use [testing/cryptotest.SetGlobalRandom]. diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index be4921effa702e..9d9b814a00ff27 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -572,10 +572,13 @@ const ( // modified. A Config may be reused; the tls package will also not // modify it. type Config struct { - // Rand provides the source of entropy for nonces and RSA blinding. + // Rand provides the source of entropy for the connection. // If Rand is nil, TLS uses the cryptographic random reader in package - // crypto/rand. - // The Reader must be safe for use by multiple goroutines. + // crypto/rand. The Reader must be safe for use by multiple goroutines. + // + // Deprecated: this should be left nil in production. Not all TLS + // configurations are guaranteed to use Rand. Test code can use + // [testing/cryptotest.SetGlobalRandom] instead. Rand io.Reader // Time returns the current time as the number of seconds since the epoch. diff --git a/src/crypto/tls/example_test.go b/src/crypto/tls/example_test.go index 95e4953fb2e0c3..aa9419a5955a53 100644 --- a/src/crypto/tls/example_test.go +++ b/src/crypto/tls/example_test.go @@ -14,14 +14,6 @@ import ( "time" ) -// zeroSource is an io.Reader that returns an unlimited number of zero bytes. -type zeroSource struct{} - -func (zeroSource) Read(b []byte) (n int, err error) { - clear(b) - return len(b), nil -} - func ExampleDial() { // Connecting with a custom root-certificate set. @@ -70,19 +62,17 @@ TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== func ExampleConfig_keyLogWriter() { // Debugging TLS applications by decrypting a network traffic capture. - // WARNING: Use of KeyLogWriter compromises security and should only be // used for debugging. - // Dummy test HTTP server for the example with insecure random so output is - // reproducible. - server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) - server.TLS = &tls.Config{ - Rand: zeroSource{}, // for example only; don't do this. - } + server := httptest.NewUnstartedServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) {})) server.StartTLS() defer server.Close() + pool := x509.NewCertPool() + pool.AddCert(server.Certificate()) + // Typically the log would go to an open file: // w, err := os.OpenFile("tls-secrets.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) w := os.Stdout @@ -91,9 +81,7 @@ func ExampleConfig_keyLogWriter() { Transport: &http.Transport{ TLSClientConfig: &tls.Config{ KeyLogWriter: w, - - Rand: zeroSource{}, // for reproducible output; don't do this. - InsecureSkipVerify: true, // test server certificate is not trusted. + RootCAs: pool, }, }, } diff --git a/src/crypto/tls/handshake_client_test.go b/src/crypto/tls/handshake_client_test.go index 7cf40ee5bcdd14..64a668eae9b512 100644 --- a/src/crypto/tls/handshake_client_test.go +++ b/src/crypto/tls/handshake_client_test.go @@ -1211,12 +1211,10 @@ func TestKeyLogTLS12(t *testing.T) { clientConfig := testConfigClient.Clone() clientConfig.KeyLogWriter = &clientBuf clientConfig.MaxVersion = VersionTLS12 - clientConfig.Rand = zeroSource{} serverConfig := testConfigServer.Clone() serverConfig.KeyLogWriter = &serverBuf serverConfig.MaxVersion = VersionTLS12 - serverConfig.Rand = zeroSource{} c, s := localPipe(t) done := make(chan bool) @@ -1242,17 +1240,23 @@ func TestKeyLogTLS12(t *testing.T) { if len(loggedLine) == 0 { t.Fatalf("%s: no keylog line was produced", side) } - const expectedLen = 13 /* "CLIENT_RANDOM" */ + - 1 /* space */ + - 32*2 /* hex client nonce */ + - 1 /* space */ + - 48*2 /* hex master secret */ + - 1 /* new line */ - if len(loggedLine) != expectedLen { - t.Fatalf("%s: keylog line has incorrect length (want %d, got %d): %q", side, expectedLen, len(loggedLine), loggedLine) + rest, ok := strings.CutSuffix(loggedLine, "\n") + if !ok { + t.Fatalf("%s: keylog line is missing trailing newline: %q", side, loggedLine) + } + label, rest, ok := strings.Cut(rest, " ") + if !ok || label != "CLIENT_RANDOM" { + t.Fatalf("%s: keylog line has incorrect label: %q", side, loggedLine) + } + clientRandom, masterSecret, ok := strings.Cut(rest, " ") + if !ok { + t.Fatalf("%s: keylog line is missing master secret: %q", side, loggedLine) + } + if b, err := hex.DecodeString(clientRandom); err != nil || len(b) != 32 { + t.Fatalf("%s: keylog line has invalid client random: %q", side, loggedLine) } - if !strings.HasPrefix(loggedLine, "CLIENT_RANDOM "+strings.Repeat("0", 64)+" ") { - t.Fatalf("%s: keylog line has incorrect structure or nonce: %q", side, loggedLine) + if b, err := hex.DecodeString(masterSecret); err != nil || len(b) != 48 { + t.Fatalf("%s: keylog line has invalid master secret: %q", side, loggedLine) } } @@ -1265,11 +1269,9 @@ func TestKeyLogTLS13(t *testing.T) { clientConfig := testConfigClient.Clone() clientConfig.KeyLogWriter = &clientBuf - clientConfig.Rand = zeroSource{} serverConfig := testConfigServer.Clone() serverConfig.KeyLogWriter = &serverBuf - serverConfig.Rand = zeroSource{} c, s := localPipe(t) done := make(chan bool) diff --git a/src/crypto/tls/handshake_test.go b/src/crypto/tls/handshake_test.go index 9b72b1f9c7c319..3205dd97d68f40 100644 --- a/src/crypto/tls/handshake_test.go +++ b/src/crypto/tls/handshake_test.go @@ -394,14 +394,6 @@ Dialing: panic("unreachable") } -// zeroSource is an io.Reader that returns an unlimited number of zero bytes. -type zeroSource struct{} - -func (zeroSource) Read(b []byte) (n int, err error) { - clear(b) - return len(b), nil -} - func TestMain(m *testing.M) { flag.Usage = func() { fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", os.Args) From 97a57b481fa02501d833b24f2eab21bea98d2b2c Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Tue, 12 May 2026 14:03:58 -0400 Subject: [PATCH 03/32] crypto/tls: use mlkem.GenerateKey for ML-KEM hybrids We were using mlkem.NewDecapsulationKey on a random slice to support Config.Rand, but Encapsulate was already bypassing Config.Rand anyway. Config.Rand is deprecated anyway in favor of cryptotest.SetDefaultRandom, so switch to mlkem.GenerateKey which has better FIPS 140-3 compliance. Updates #79367 Change-Id: I62a5099bd69a1ee2941d5ae1c9c2bf4b6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/777320 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Auto-Submit: Filippo Valsorda Reviewed-by: Roland Shoemaker Reviewed-by: David Chase --- src/crypto/tls/key_schedule.go | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/crypto/tls/key_schedule.go b/src/crypto/tls/key_schedule.go index 97f4993a179939..c3d041afdfe287 100644 --- a/src/crypto/tls/key_schedule.go +++ b/src/crypto/tls/key_schedule.go @@ -74,16 +74,16 @@ type keyExchange interface { } func keyExchangeForCurveID(id CurveID) (keyExchange, error) { - newMLKEMPrivateKey768 := func(b []byte) (crypto.Decapsulator, error) { - return mlkem.NewDecapsulationKey768(b) + mlkemGenerateKey768 := func() (crypto.Decapsulator, error) { + return mlkem.GenerateKey768() } - newMLKEMPrivateKey1024 := func(b []byte) (crypto.Decapsulator, error) { - return mlkem.NewDecapsulationKey1024(b) + mlkemGenerateKey1024 := func() (crypto.Decapsulator, error) { + return mlkem.GenerateKey1024() } - newMLKEMPublicKey768 := func(b []byte) (crypto.Encapsulator, error) { + mlkemNewPublicKey768 := func(b []byte) (crypto.Encapsulator, error) { return mlkem.NewEncapsulationKey768(b) } - newMLKEMPublicKey1024 := func(b []byte) (crypto.Encapsulator, error) { + mlkemNewPublicKey1024 := func(b []byte) (crypto.Encapsulator, error) { return mlkem.NewEncapsulationKey1024(b) } switch id { @@ -98,15 +98,15 @@ func keyExchangeForCurveID(id CurveID) (keyExchange, error) { case X25519MLKEM768: return &hybridKeyExchange{id, ecdhKeyExchange{X25519, ecdh.X25519()}, 32, mlkem.EncapsulationKeySize768, mlkem.CiphertextSize768, - newMLKEMPrivateKey768, newMLKEMPublicKey768}, nil + mlkemGenerateKey768, mlkemNewPublicKey768}, nil case SecP256r1MLKEM768: return &hybridKeyExchange{id, ecdhKeyExchange{CurveP256, ecdh.P256()}, 65, mlkem.EncapsulationKeySize768, mlkem.CiphertextSize768, - newMLKEMPrivateKey768, newMLKEMPublicKey768}, nil + mlkemGenerateKey768, mlkemNewPublicKey768}, nil case SecP384r1MLKEM1024: return &hybridKeyExchange{id, ecdhKeyExchange{CurveP384, ecdh.P384()}, 97, mlkem.EncapsulationKeySize1024, mlkem.CiphertextSize1024, - newMLKEMPrivateKey1024, newMLKEMPublicKey1024}, nil + mlkemGenerateKey1024, mlkemNewPublicKey1024}, nil default: return nil, errors.New("tls: unsupported key exchange") } @@ -161,8 +161,8 @@ type hybridKeyExchange struct { mlkemPublicKeySize int mlkemCiphertextSize int - newMLKEMPrivateKey func([]byte) (crypto.Decapsulator, error) - newMLKEMPublicKey func([]byte) (crypto.Encapsulator, error) + mlkemGenerateKey func() (crypto.Decapsulator, error) + mlkemNewPublicKey func([]byte) (crypto.Encapsulator, error) } func (ke *hybridKeyExchange) keyShares(rand io.Reader) (*keySharePrivateKeys, []keyShare, error) { @@ -177,11 +177,7 @@ func (ke *hybridKeyExchange) keyShares(rand io.Reader) (*keySharePrivateKeys, [] if err != nil { return nil, nil, err } - seed := make([]byte, mlkem.SeedSize) - if _, err := io.ReadFull(rand, seed); err != nil { - return nil, nil, err - } - priv.mlkem, err = ke.newMLKEMPrivateKey(seed) + priv.mlkem, err = ke.mlkemGenerateKey() if err != nil { return nil, nil, err } @@ -220,7 +216,7 @@ func (ke *hybridKeyExchange) serverSharedSecret(rand io.Reader, clientKeyShare [ if err != nil { return nil, keyShare{}, err } - mlkemPeerKey, err := ke.newMLKEMPublicKey(mlkemShareData) + mlkemPeerKey, err := ke.mlkemNewPublicKey(mlkemShareData) if err != nil { return nil, keyShare{}, err } From 6b0243ccf6e83afbd79889a7862599cb5992660c Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Tue, 12 May 2026 12:53:42 -0400 Subject: [PATCH 04/32] crypto/tls: implement MLKEM1024 key exchange Fixes #78543 Change-Id: I26a70a64665c75e5116b83f73a75093f6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/777221 Reviewed-by: David Chase LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Auto-Submit: Filippo Valsorda Reviewed-by: Roland Shoemaker --- api/next/78543.txt | 2 + .../6-stdlib/99-minor/crypto/tls/78543.md | 2 + src/crypto/tls/bogo_config.json | 20 ++---- src/crypto/tls/bogo_shim_test.go | 2 + src/crypto/tls/common.go | 5 +- src/crypto/tls/common_string.go | 10 ++- src/crypto/tls/defaults.go | 2 +- src/crypto/tls/defaults_fips140.go | 1 + src/crypto/tls/fips140_test.go | 2 +- src/crypto/tls/handshake_client.go | 2 +- src/crypto/tls/handshake_client_tls13.go | 3 +- src/crypto/tls/key_schedule.go | 29 +++++++++ src/crypto/tls/tls_test.go | 64 ++++++++++++++++++- 13 files changed, 117 insertions(+), 27 deletions(-) create mode 100644 api/next/78543.txt create mode 100644 doc/next/6-stdlib/99-minor/crypto/tls/78543.md diff --git a/api/next/78543.txt b/api/next/78543.txt new file mode 100644 index 00000000000000..994f86ff72da5d --- /dev/null +++ b/api/next/78543.txt @@ -0,0 +1,2 @@ +pkg crypto/tls, const MLKEM1024 = 514 #78543 +pkg crypto/tls, const MLKEM1024 CurveID #78543 diff --git a/doc/next/6-stdlib/99-minor/crypto/tls/78543.md b/doc/next/6-stdlib/99-minor/crypto/tls/78543.md new file mode 100644 index 00000000000000..4dd30688547d2b --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/tls/78543.md @@ -0,0 +1,2 @@ +The [MLKEM1024] key exchange is now supported. It can be enabled by adding it to +[Config.CurvePreferences]. diff --git a/src/crypto/tls/bogo_config.json b/src/crypto/tls/bogo_config.json index d57b72aaf8fc06..719d78857c1962 100644 --- a/src/crypto/tls/bogo_config.json +++ b/src/crypto/tls/bogo_config.json @@ -34,7 +34,12 @@ "TLS-ECH-Server-EarlyDataRejected": "Go does not support early (0-RTT) data", "MLKEMKeyShareIncludedSecond": "BoGo wants us to order the key shares based on its preference, but we don't support that", + "MLKEMKeyShareIncludedSecond-*": "BoGo wants us to order the key shares based on its preference, but we don't support that", "MLKEMKeyShareIncludedThird": "BoGo wants us to order the key shares based on its preference, but we don't support that", + "MLKEMKeyShareIncludedThird-*": "BoGo wants us to order the key shares based on its preference, but we don't support that", + "TwoMLKEMs": "BoGo wants us to order the key shares based on its preference, but we don't support that", + "NotJustMLKEMKeyShare-MLKEM1024": "BoringSSL sends an ECC key share for fallback when the main key share is MLKEM1024, we currently don't", + "PostQuantumNotEnabledByDefaultInClients": "We do enable it by default!", "*-Kyber-TLS13": "We don't support Kyber, only ML-KEM (BoGo bug ignoring AllCurves?)", @@ -76,21 +81,6 @@ "PAKE-Extension-*": "We don't support PAKE", "*TicketFlags": "We don't support draft-ietf-tls-tlsflags", - "BothMLKEMAndKyber-MLKEM1024": "We don't support ML-KEM 1024 KEX", - "CurveTest-Client-MLKEM1024-TLS13": "We don't support ML-KEM 1024 KEX", - "CurveTest-Invalid-MLKEMEncapKeyNotReduced-Server-MLKEM1024-TLS13": "We don't support ML-KEM 1024 KEX", - "CurveTest-Invalid-PadKeyShare-Client-MLKEM1024-TLS13": "We don't support ML-KEM 1024 KEX", - "CurveTest-Invalid-PadKeyShare-Server-MLKEM1024-TLS13": "We don't support ML-KEM 1024 KEX", - "CurveTest-Invalid-TruncateKeyShare-Client-MLKEM1024-TLS13": "We don't support ML-KEM 1024 KEX", - "CurveTest-Invalid-TruncateKeyShare-Server-MLKEM1024-TLS13": "We don't support ML-KEM 1024 KEX", - "CurveTest-Server-MLKEM1024-TLS13": "We don't support ML-KEM 1024 KEX", - "JustConfiguringMLKEMWorks-MLKEM1024": "We don't support ML-KEM 1024 KEX", - "NotJustMLKEMKeyShare-MLKEM1024": "We don't support ML-KEM 1024 KEX", - "TwoMLKEMs": "We don't support ML-KEM 1024 KEX", - "MLKEMKeyShareIncludedSecond-MLKEM1024": "We don't support ML-KEM 1024 KEX", - "MLKEMKeyShareIncludedSecond-X25519MLKEM768": "We don't support ML-KEM 1024 KEX", - "MLKEMKeyShareIncludedThird-MLKEM1024": "We don't support ML-KEM 1024 KEX", - "MLKEMKeyShareIncludedThird-X25519MLKEM768": "We don't return key shares in client preference order", "ECDSAKeyUsage-*": "We don't enforce ECDSA KU", diff --git a/src/crypto/tls/bogo_shim_test.go b/src/crypto/tls/bogo_shim_test.go index 5e6110a8daf9bf..a0e7c57b31c443 100644 --- a/src/crypto/tls/bogo_shim_test.go +++ b/src/crypto/tls/bogo_shim_test.go @@ -625,6 +625,8 @@ func TestBogoSuite(t *testing.T) { assertResults := map[string]string{ "CurveTest-Client-X25519MLKEM768-TLS13": "PASS", "CurveTest-Server-X25519MLKEM768-TLS13": "PASS", + "CurveTest-Client-MLKEM1024-TLS13": "PASS", + "CurveTest-Server-MLKEM1024-TLS13": "PASS", // Various signature algorithm tests checking that we enforce our // preferences on the peer. diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index 9d9b814a00ff27..26904c4ae5ca24 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -155,11 +155,12 @@ const ( X25519MLKEM768 CurveID = 4588 SecP256r1MLKEM768 CurveID = 4587 SecP384r1MLKEM1024 CurveID = 4589 + MLKEM1024 CurveID = 514 ) func isTLS13OnlyKeyExchange(curve CurveID) bool { switch curve { - case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024: + case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, MLKEM1024: return true default: return false @@ -168,7 +169,7 @@ func isTLS13OnlyKeyExchange(curve CurveID) bool { func isPQKeyExchange(curve CurveID) bool { switch curve { - case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024: + case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, MLKEM1024: return true default: return false diff --git a/src/crypto/tls/common_string.go b/src/crypto/tls/common_string.go index 04c7283dd97576..6bbaf1597f59d0 100644 --- a/src/crypto/tls/common_string.go +++ b/src/crypto/tls/common_string.go @@ -82,17 +82,19 @@ func _() { _ = x[X25519MLKEM768-4588] _ = x[SecP256r1MLKEM768-4587] _ = x[SecP384r1MLKEM1024-4589] + _ = x[MLKEM1024-514] } const ( _CurveID_name_0 = "CurveP256CurveP384CurveP521" _CurveID_name_1 = "X25519" - _CurveID_name_2 = "SecP256r1MLKEM768X25519MLKEM768SecP384r1MLKEM1024" + _CurveID_name_2 = "MLKEM1024" + _CurveID_name_3 = "SecP256r1MLKEM768X25519MLKEM768SecP384r1MLKEM1024" ) var ( _CurveID_index_0 = [...]uint8{0, 9, 18, 27} - _CurveID_index_2 = [...]uint8{0, 17, 31, 49} + _CurveID_index_3 = [...]uint8{0, 17, 31, 49} ) func (i CurveID) String() string { @@ -102,9 +104,11 @@ func (i CurveID) String() string { return _CurveID_name_0[_CurveID_index_0[i]:_CurveID_index_0[i+1]] case i == 29: return _CurveID_name_1 + case i == 514: + return _CurveID_name_2 case 4587 <= i && i <= 4589: i -= 4587 - return _CurveID_name_2[_CurveID_index_2[i]:_CurveID_index_2[i+1]] + return _CurveID_name_3[_CurveID_index_3[i]:_CurveID_index_3[i+1]] default: return "CurveID(" + strconv.FormatInt(int64(i), 10) + ")" } diff --git a/src/crypto/tls/defaults.go b/src/crypto/tls/defaults.go index 0eff24d576924f..b853d1c7d6423f 100644 --- a/src/crypto/tls/defaults.go +++ b/src/crypto/tls/defaults.go @@ -37,7 +37,7 @@ func defaultCurveEnabled(c CurveID) bool { // include every supported key exchange. func curvePreferenceOrder() []CurveID { return []CurveID{ - X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, + X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, MLKEM1024, X25519, CurveP256, CurveP384, CurveP521, } } diff --git a/src/crypto/tls/defaults_fips140.go b/src/crypto/tls/defaults_fips140.go index e1c935df2f26dd..1c713898ed1832 100644 --- a/src/crypto/tls/defaults_fips140.go +++ b/src/crypto/tls/defaults_fips140.go @@ -36,6 +36,7 @@ var ( X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, + MLKEM1024, CurveP256, CurveP384, CurveP521, diff --git a/src/crypto/tls/fips140_test.go b/src/crypto/tls/fips140_test.go index 232a8a219cebd1..c3f6ec917b5632 100644 --- a/src/crypto/tls/fips140_test.go +++ b/src/crypto/tls/fips140_test.go @@ -151,7 +151,7 @@ func isFIPSCurve(id CurveID) bool { switch id { case CurveP256, CurveP384, CurveP521: return true - case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024: + case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, MLKEM1024: // Only for the native module. return !boring.Enabled case X25519: diff --git a/src/crypto/tls/handshake_client.go b/src/crypto/tls/handshake_client.go index 04f6ff62d5d552..f2cb03eee286eb 100644 --- a/src/crypto/tls/handshake_client.go +++ b/src/crypto/tls/handshake_client.go @@ -140,7 +140,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli } if len(hello.supportedCurves) == 0 { - return nil, nil, nil, errors.New("tls: no supported elliptic curves for ECDHE") + return nil, nil, nil, errors.New("tls: no supported key exchange methods (CurveIDs)") } // Since the order is fixed, the first one is always the one to send a // key share for. All the PQ hybrids sort first, and produce a fallback diff --git a/src/crypto/tls/handshake_client_tls13.go b/src/crypto/tls/handshake_client_tls13.go index 15f07ed1a23bd6..d367020c2066bf 100644 --- a/src/crypto/tls/handshake_client_tls13.go +++ b/src/crypto/tls/handshake_client_tls13.go @@ -54,7 +54,8 @@ func (hs *clientHandshakeStateTLS13) handshake() error { } // Consistency check on the presence of a keyShare and its parameters. - if hs.keyShareKeys == nil || hs.keyShareKeys.ecdhe == nil || len(hs.hello.keyShares) == 0 { + if hs.keyShareKeys == nil || (hs.keyShareKeys.ecdhe == nil && hs.keyShareKeys.mlkem == nil) || + len(hs.hello.keyShares) == 0 { return c.sendAlert(alertInternalError) } diff --git a/src/crypto/tls/key_schedule.go b/src/crypto/tls/key_schedule.go index c3d041afdfe287..652a6489b899d6 100644 --- a/src/crypto/tls/key_schedule.go +++ b/src/crypto/tls/key_schedule.go @@ -107,11 +107,40 @@ func keyExchangeForCurveID(id CurveID) (keyExchange, error) { return &hybridKeyExchange{id, ecdhKeyExchange{CurveP384, ecdh.P384()}, 97, mlkem.EncapsulationKeySize1024, mlkem.CiphertextSize1024, mlkemGenerateKey1024, mlkemNewPublicKey1024}, nil + case MLKEM1024: + return &mlkem1024KeyExchange{}, nil default: return nil, errors.New("tls: unsupported key exchange") } } +type mlkem1024KeyExchange struct{} + +func (ke *mlkem1024KeyExchange) keyShares(_ io.Reader) (*keySharePrivateKeys, []keyShare, error) { + priv, err := mlkem.GenerateKey1024() + if err != nil { + return nil, nil, err + } + return &keySharePrivateKeys{mlkem: priv}, []keyShare{{MLKEM1024, priv.EncapsulationKey().Bytes()}}, nil +} + +func (ke *mlkem1024KeyExchange) serverSharedSecret(_ io.Reader, clientKeyShare []byte) ([]byte, keyShare, error) { + peerKey, err := mlkem.NewEncapsulationKey1024(clientKeyShare) + if err != nil { + return nil, keyShare{}, err + } + sharedKey, keyShareData := peerKey.Encapsulate() + return sharedKey, keyShare{MLKEM1024, keyShareData}, nil +} + +func (ke *mlkem1024KeyExchange) clientSharedSecret(priv *keySharePrivateKeys, serverKeyShare []byte) ([]byte, error) { + sharedKey, err := priv.mlkem.Decapsulate(serverKeyShare) + if err != nil { + return nil, err + } + return sharedKey, nil +} + type ecdhKeyExchange struct { id CurveID curve ecdh.Curve diff --git a/src/crypto/tls/tls_test.go b/src/crypto/tls/tls_test.go index 51d53f9aaa0061..252bcd547d2e1c 100644 --- a/src/crypto/tls/tls_test.go +++ b/src/crypto/tls/tls_test.go @@ -2108,6 +2108,18 @@ func TestHandshakeMLKEM(t *testing.T) { expectClient: []CurveID{SecP256r1MLKEM768, CurveP256}, expectSelected: CurveP256, }, + { + name: "CurveP384HRR", + clientConfig: func(config *Config) { + config.CurvePreferences = []CurveID{SecP256r1MLKEM768, CurveP384} + }, + serverConfig: func(config *Config) { + config.CurvePreferences = []CurveID{CurveP384} + }, + expectClient: []CurveID{SecP256r1MLKEM768, CurveP384}, + expectSelected: CurveP384, + expectHRR: true, + }, { name: "ClientMLKEMOnly", clientConfig: func(config *Config) { @@ -2163,14 +2175,60 @@ func TestHandshakeMLKEM(t *testing.T) { testenv.SetGODEBUG(t, "tlssecpmlkem=0") }, clientConfig: func(config *Config) { - config.CurvePreferences = []CurveID{CurveP256, SecP256r1MLKEM768} + config.CurvePreferences = []CurveID{CurveP256, SecP256r1MLKEM768, MLKEM1024} }, serverConfig: func(config *Config) { - config.CurvePreferences = []CurveID{CurveP256, SecP256r1MLKEM768} + config.CurvePreferences = []CurveID{CurveP256, SecP256r1MLKEM768, MLKEM1024} }, - expectClient: []CurveID{SecP256r1MLKEM768, CurveP256}, + expectClient: []CurveID{SecP256r1MLKEM768, MLKEM1024, CurveP256}, expectSelected: SecP256r1MLKEM768, }, + { + name: "ClientMLKEM1024Only", + clientConfig: func(config *Config) { + config.CurvePreferences = []CurveID{MLKEM1024} + }, + serverConfig: func(config *Config) { + config.CurvePreferences = append(defaultWithPQ, MLKEM1024) + }, + expectClient: []CurveID{MLKEM1024}, + expectSelected: MLKEM1024, + }, + { + name: "ServerMLKEM1024Only", + clientConfig: func(config *Config) { + config.CurvePreferences = append(defaultWithPQ, MLKEM1024) + }, + serverConfig: func(config *Config) { + config.CurvePreferences = []CurveID{MLKEM1024} + }, + expectClient: []CurveID{X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, + MLKEM1024, X25519, CurveP256, CurveP384, CurveP521}, + expectSelected: MLKEM1024, + expectHRR: true, + }, + { + name: "MLKEM1024NotPreferredOverHybrid", + clientConfig: func(config *Config) { + config.CurvePreferences = []CurveID{MLKEM1024, X25519MLKEM768} + }, + serverConfig: func(config *Config) { + config.CurvePreferences = []CurveID{MLKEM1024, X25519MLKEM768} + }, + expectClient: []CurveID{X25519MLKEM768, MLKEM1024}, + expectSelected: X25519MLKEM768, + }, + { + name: "MLKEM1024PreferredOverECC", + clientConfig: func(config *Config) { + config.CurvePreferences = []CurveID{X25519, MLKEM1024} + }, + serverConfig: func(config *Config) { + config.CurvePreferences = []CurveID{X25519, MLKEM1024} + }, + expectClient: []CurveID{MLKEM1024, X25519}, + expectSelected: MLKEM1024, + }, } baseServerConfig := testConfigServer.Clone() From 78b71d40fdaafe05531f9bb668dc19ff0bbde01a Mon Sep 17 00:00:00 2001 From: Michael Pratt Date: Tue, 19 May 2026 13:45:40 -0400 Subject: [PATCH 05/32] encoding/json/jsontext: skip allocation test when inlining is disabled This test is failing on the noopt builder, where disabling inlining prevents the compiler from avoiding the allocation. Fixes #79502 Change-Id: I795284fa42c50174720c8b6ca08cf75e6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/779960 Auto-Submit: Michael Pratt Reviewed-by: Damien Neil LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Auto-Submit: Damien Neil --- src/encoding/json/jsontext/token_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/encoding/json/jsontext/token_test.go b/src/encoding/json/jsontext/token_test.go index f0b1e41e2020d7..788eb5e42894a9 100644 --- a/src/encoding/json/jsontext/token_test.go +++ b/src/encoding/json/jsontext/token_test.go @@ -13,6 +13,8 @@ import ( "reflect" "strconv" "testing" + + "internal/testenv" ) func TestTokenStringAllocations(t *testing.T) { From 5cc4ceb8006f64c8268a020051349607d48832a5 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Thu, 27 Nov 2025 15:03:45 +0100 Subject: [PATCH 06/32] crypto/tls: add TestInvalidHandshakeSignature Change-Id: I574b2443ea3695264379da28425acf716a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/725040 Reviewed-by: David Chase Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Auto-Submit: Filippo Valsorda --- src/crypto/tls/tls_test.go | 128 ++++++++++++++++++++++++++++++++----- 1 file changed, 113 insertions(+), 15 deletions(-) diff --git a/src/crypto/tls/tls_test.go b/src/crypto/tls/tls_test.go index 252bcd547d2e1c..d288b66fedf93a 100644 --- a/src/crypto/tls/tls_test.go +++ b/src/crypto/tls/tls_test.go @@ -2523,21 +2523,6 @@ func TestHandshakeMLDSA(t *testing.T) { } } -// bitFlippingSigner wraps a crypto.Signer and flips the last bit of every -// signature it produces, used to test that peers reject invalid signatures. -type bitFlippingSigner struct{ crypto.Signer } - -func (s bitFlippingSigner) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) { - sig, err := s.Signer.Sign(rand, msg, opts) - if err != nil { - return nil, err - } - if len(sig) > 0 { - sig[len(sig)-1] ^= 1 - } - return sig, nil -} - func TestX509KeyPairPopulateCertificate(t *testing.T) { key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { @@ -2858,3 +2843,116 @@ func (s messageOnlySigner) SignMessage(rand io.Reader, msg []byte, opts crypto.S digest := h.Sum(nil) return s.Signer.Sign(rand, digest, opts) } + +// bitFlippingSigner wraps a crypto.Signer and flips a bit in the signature, +// producing an invalid signature. +type bitFlippingSigner struct{ crypto.Signer } + +func (s bitFlippingSigner) Public() crypto.PublicKey { + return s.Signer.Public() +} + +func (s bitFlippingSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { + sig, err := s.Signer.Sign(rand, digest, opts) + if err != nil { + return nil, err + } + sig[0] ^= 1 + return sig, nil +} + +// TestInvalidHandshakeSignature tests that invalid handshake signatures are +// rejected for all TLS versions, for both server and client certificates, +// even when InsecureSkipVerify or RequireAnyClientCert are used (which disable +// certificate chain verification, but not signature verification). +func TestInvalidHandshakeSignature(t *testing.T) { + t.Run("TLSv10", func(t *testing.T) { + skipFIPS(t) + testInvalidHandshakeSignature(t, VersionTLS10) + }) + t.Run("TLSv12", func(t *testing.T) { testInvalidHandshakeSignature(t, VersionTLS12) }) + t.Run("TLSv13", func(t *testing.T) { testInvalidHandshakeSignature(t, VersionTLS13) }) +} + +func testInvalidHandshakeSignature(t *testing.T, version uint16) { + serverConfig := testConfigServer.Clone() + serverConfig.MaxVersion = version + serverConfig.MinVersion = version + serverConfig.SessionTicketsDisabled = true + clientConfig := testConfigClient.Clone() + clientConfig.MaxVersion = version + clientConfig.MinVersion = version + + // Test that the server rejects invalid client certificate signatures, + // even when RequireAnyClientCert is used. + t.Run("ClientSignature", func(t *testing.T) { + serverConfig := serverConfig.Clone() + serverConfig.ClientAuth = RequireAnyClientCert + clientConfig := clientConfig.Clone() + clientConfig.Certificates = []Certificate{{ + Certificate: testClientECDSAP256Cert.Certificate, + PrivateKey: bitFlippingSigner{testClientECDSAP256Cert.PrivateKey.(crypto.Signer)}, + }} + + clientErr, serverErr := testInvalidSignatureHandshake(t, clientConfig, serverConfig) + if serverErr == nil { + t.Fatalf("expected server to reject invalid client signature; client err = %v", clientErr) + } + if !strings.Contains(serverErr.Error(), "invalid signature") { + t.Errorf("expected 'invalid signature' error, got: %v", serverErr) + } + }) + + // Test that the client rejects invalid server certificate signatures. + t.Run("ServerSignature", func(t *testing.T) { + serverConfig := serverConfig.Clone() + serverConfig.Certificates = []Certificate{{ + Certificate: testRSA2048Cert.Certificate, + PrivateKey: bitFlippingSigner{testRSA2048Cert.PrivateKey.(crypto.Signer)}, + }} + + clientErr, serverErr := testInvalidSignatureHandshake(t, clientConfig, serverConfig) + if clientErr == nil { + t.Fatalf("expected client to reject invalid server signature; server err = %v", serverErr) + } + if !strings.Contains(clientErr.Error(), "invalid signature") { + t.Errorf("expected 'invalid signature' error, got: %v", clientErr) + } + }) + + // Test that InsecureSkipVerify doesn't disable server signature verification. + t.Run("ServerSignature/InsecureSkipVerify", func(t *testing.T) { + clientConfig := clientConfig.Clone() + clientConfig.InsecureSkipVerify = true + serverConfig := serverConfig.Clone() + serverConfig.Certificates = []Certificate{{ + Certificate: testRSA2048Cert.Certificate, + PrivateKey: bitFlippingSigner{testRSA2048Cert.PrivateKey.(crypto.Signer)}, + }} + + clientErr, serverErr := testInvalidSignatureHandshake(t, clientConfig, serverConfig) + if clientErr == nil { + t.Fatalf("expected client to reject invalid server signature despite InsecureSkipVerify; server err = %v", serverErr) + } + if !strings.Contains(clientErr.Error(), "invalid signature") { + t.Errorf("expected 'invalid signature' error, got: %v", clientErr) + } + }) +} + +// testInvalidSignatureHandshake performs a TLS handshake and returns the +// errors from both client and server. Unlike testHandshake, it doesn't try +// to exchange data after the handshake. +func testInvalidSignatureHandshake(t *testing.T, clientConfig, serverConfig *Config) (clientErr, serverErr error) { + c, s := localPipe(t) + done := make(chan struct{}) + go func() { + defer close(done) + clientErr = Client(c, clientConfig).Handshake() + c.Close() + }() + serverErr = Server(s, serverConfig).Handshake() + s.Close() + <-done + return +} From a7bc19bf372429af284e88ab7368d6494f0b0cf6 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Tue, 12 May 2026 15:56:41 -0400 Subject: [PATCH 07/32] crypto/tls: remove the tlsunsafeekm GODEBUG setting Updates #75316 Change-Id: I2efa3e485653f5b403d92e5d99959e356a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/777380 Reviewed-by: Roland Shoemaker Auto-Submit: Filippo Valsorda Reviewed-by: David Chase LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- doc/godebug.md | 2 ++ src/crypto/tls/common.go | 5 ----- src/crypto/tls/conn.go | 11 +---------- src/crypto/tls/prf.go | 2 +- src/internal/godebugs/table.go | 2 +- src/runtime/metrics/doc.go | 4 ---- 6 files changed, 5 insertions(+), 21 deletions(-) diff --git a/doc/godebug.md b/doc/godebug.md index f28af6a0b73756..d3a343820fc888 100644 --- a/doc/godebug.md +++ b/doc/godebug.md @@ -158,6 +158,8 @@ and the [go command documentation](/cmd/go#hdr-Build_and_test_caching). Go 1.27 removed the `gotypesalias` setting, as noted in the [Go 1.22](#go-122) section. +Go 1.27 removed the `tlsunsafeekm` setting, as noted in the [Go 1.22](#go-122) section. + Go 1.27 added a new `htmlmetacontenturlescape` setting that controls whether html/template will escape URLs in the `url=` portion of the content attribute of HTML meta tags. The default `htmlmetacontentescape=1` will cause URLs to be diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index 26904c4ae5ca24..77879e31e020e7 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -338,11 +338,6 @@ type ConnectionState struct { // the seed. If the connection was set to allow renegotiation via // Config.Renegotiation, or if the connections supports neither TLS 1.3 nor // Extended Master Secret, this function will return an error. -// -// Exporting key material without Extended Master Secret or TLS 1.3 was disabled -// in Go 1.22 due to security issues (see the Security Considerations sections -// of RFC 5705 and RFC 7627), but can be re-enabled with the GODEBUG setting -// tlsunsafeekm=1. func (cs *ConnectionState) ExportKeyingMaterial(label string, context []byte, length int) ([]byte, error) { return cs.ekm(label, context, length) } diff --git a/src/crypto/tls/conn.go b/src/crypto/tls/conn.go index 7ce69e914a60b7..b4508d6a812303 100644 --- a/src/crypto/tls/conn.go +++ b/src/crypto/tls/conn.go @@ -15,7 +15,6 @@ import ( "errors" "fmt" "hash" - "internal/godebug" "io" "net" "sync" @@ -1610,8 +1609,6 @@ func (c *Conn) ConnectionState() ConnectionState { return c.connectionStateLocked() } -var tlsunsafeekm = godebug.New("tlsunsafeekm") - func (c *Conn) connectionStateLocked() ConnectionState { var state ConnectionState state.HandshakeComplete = c.isHandshakeComplete.Load() @@ -1638,13 +1635,7 @@ func (c *Conn) connectionStateLocked() ConnectionState { if c.config.Renegotiation != RenegotiateNever { state.ekm = noEKMBecauseRenegotiation } else if c.vers != VersionTLS13 && !c.extMasterSecret { - state.ekm = func(label string, context []byte, length int) ([]byte, error) { - if tlsunsafeekm.Value() == "1" { - tlsunsafeekm.IncNonDefault() - return c.ekm(label, context, length) - } - return noEKMBecauseNoEMS(label, context, length) - } + state.ekm = noEKMBecauseNoEMS } else { state.ekm = c.ekm } diff --git a/src/crypto/tls/prf.go b/src/crypto/tls/prf.go index 19f80ac2c91c20..bc59300f41c762 100644 --- a/src/crypto/tls/prf.go +++ b/src/crypto/tls/prf.go @@ -248,7 +248,7 @@ func noEKMBecauseRenegotiation(label string, context []byte, length int) ([]byte // Master Secret is not negotiated and thus we wish to fail all key-material // export requests. func noEKMBecauseNoEMS(label string, context []byte, length int) ([]byte, error) { - return nil, errors.New("crypto/tls: ExportKeyingMaterial is unavailable when neither TLS 1.3 nor Extended Master Secret are negotiated; override with GODEBUG=tlsunsafeekm=1") + return nil, errors.New("crypto/tls: ExportKeyingMaterial is unavailable when neither TLS 1.3 nor Extended Master Secret are negotiated") } // ekmFromMasterSecret generates exported keying material as defined in RFC 5705. diff --git a/src/internal/godebugs/table.go b/src/internal/godebugs/table.go index 56a139006c28a9..70f44c1e885e84 100644 --- a/src/internal/godebugs/table.go +++ b/src/internal/godebugs/table.go @@ -67,7 +67,6 @@ var All = []Info{ {Name: "tlsrsakex", Package: "crypto/tls", Changed: 22, Old: "1"}, {Name: "tlssecpmlkem", Package: "crypto/tls", Changed: 26, Old: "0", Opaque: true}, {Name: "tlssha1", Package: "crypto/tls", Changed: 25, Old: "1"}, - {Name: "tlsunsafeekm", Package: "crypto/tls", Changed: 22, Old: "1"}, // Mark tracebacklabels as Opaque so we don't generate a metric that we can't increment. // IncNonDefault uses a sync.Once, which involves sync.Mutex, and is not safe from a signal handler. // (Tracebacks are generated in signal-handlers.) @@ -99,6 +98,7 @@ type RemovedInfo struct { var Removed = []RemovedInfo{ {Name: "x509sha1", Removed: 24}, {Name: "gotypesalias", Removed: 27}, + {Name: "tlsunsafeekm", Removed: 27}, // Old: "1" } // Lookup returns the Info with the given name. diff --git a/src/runtime/metrics/doc.go b/src/runtime/metrics/doc.go index edd955928e332e..91c971fed3db5b 100644 --- a/src/runtime/metrics/doc.go +++ b/src/runtime/metrics/doc.go @@ -397,10 +397,6 @@ Below is the full list of supported metrics, ordered lexicographically. The number of non-default behaviors executed by the crypto/tls package due to a non-default GODEBUG=tlssha1=... setting. - /godebug/non-default-behavior/tlsunsafeekm:events - The number of non-default behaviors executed by the crypto/tls - package due to a non-default GODEBUG=tlsunsafeekm=... setting. - /godebug/non-default-behavior/updatemaxprocs:events The number of non-default behaviors executed by the runtime package due to a non-default GODEBUG=updatemaxprocs=... setting. From 14a4bc2051c024da154bf43e14824db79e9b4042 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Tue, 12 May 2026 16:00:23 -0400 Subject: [PATCH 08/32] crypto/tls: remove tlsrsakex GODEBUG setting Updates #75316 Change-Id: I6eb8482505a83b8b63edcb7d443e227a6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/777381 Auto-Submit: Filippo Valsorda Reviewed-by: David Chase Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- doc/godebug.md | 2 ++ src/crypto/tls/bogo_config.json | 2 +- src/crypto/tls/cipher_suites.go | 7 +------ src/crypto/tls/common.go | 4 +--- src/crypto/tls/defaults.go | 2 -- src/crypto/tls/handshake_client.go | 4 ---- src/crypto/tls/handshake_server.go | 4 ---- src/crypto/tls/tls_test.go | 18 ++++++++---------- src/internal/godebugs/table.go | 2 +- src/runtime/metrics/doc.go | 4 ---- 10 files changed, 14 insertions(+), 35 deletions(-) diff --git a/doc/godebug.md b/doc/godebug.md index d3a343820fc888..5520771052f9ac 100644 --- a/doc/godebug.md +++ b/doc/godebug.md @@ -160,6 +160,8 @@ Go 1.27 removed the `gotypesalias` setting, as noted in the [Go 1.22](#go-122) s Go 1.27 removed the `tlsunsafeekm` setting, as noted in the [Go 1.22](#go-122) section. +Go 1.27 removed the `tlsrsakex` setting, as noted in the [Go 1.22](#go-122) section. + Go 1.27 added a new `htmlmetacontenturlescape` setting that controls whether html/template will escape URLs in the `url=` portion of the content attribute of HTML meta tags. The default `htmlmetacontentescape=1` will cause URLs to be diff --git a/src/crypto/tls/bogo_config.json b/src/crypto/tls/bogo_config.json index 719d78857c1962..0d2c1a709b5a85 100644 --- a/src/crypto/tls/bogo_config.json +++ b/src/crypto/tls/bogo_config.json @@ -47,7 +47,7 @@ "*-Verify-RSA_PKCS1_SHA256_LEGACY-TLS12": "Likewise, we don't know how to handle it in TLS 1.2, so we send the wrong alert", "*-VerifyDefault-*": "Our signature algorithms are not configurable, so there is no difference between default and supported", "Ed25519DefaultDisable-*": "We support Ed25519 by default", - "NoCommonSignatureAlgorithms-TLS12-Fallback": "We don't support the legacy RSA exchange (without tlsrsakex=1)", + "NoCommonSignatureAlgorithms-TLS12-Fallback": "We don't support the legacy RSA exchange", "*_SHA1-TLS12": "We don't support SHA-1 in TLS 1.2 (without tlssha1=1)", "Agree-Digest-SHA1": "We don't support SHA-1 in TLS 1.2 (without tlssha1=1)", diff --git a/src/crypto/tls/cipher_suites.go b/src/crypto/tls/cipher_suites.go index 7597aabe780a89..186d6aadf60f3d 100644 --- a/src/crypto/tls/cipher_suites.go +++ b/src/crypto/tls/cipher_suites.go @@ -346,16 +346,11 @@ var disabledCipherSuites = map[uint16]bool{ TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: true, TLS_ECDHE_RSA_WITH_RC4_128_SHA: true, TLS_RSA_WITH_RC4_128_SHA: true, -} -// rsaKexCiphers contains the ciphers which use RSA based key exchange, -// which we also disable by default unless a GODEBUG is set. -var rsaKexCiphers = map[uint16]bool{ - TLS_RSA_WITH_RC4_128_SHA: true, + // RSA key exchange TLS_RSA_WITH_3DES_EDE_CBC_SHA: true, TLS_RSA_WITH_AES_128_CBC_SHA: true, TLS_RSA_WITH_AES_256_CBC_SHA: true, - TLS_RSA_WITH_AES_128_CBC_SHA256: true, TLS_RSA_WITH_AES_128_GCM_SHA256: true, TLS_RSA_WITH_AES_256_GCM_SHA384: true, } diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index 77879e31e020e7..87a4e7d98a85d7 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -723,9 +723,7 @@ type Config struct { // the list is ignored. Note that TLS 1.3 ciphersuites are not configurable. // // If CipherSuites is nil, a safe default list is used. The default cipher - // suites might change over time. In Go 1.22 RSA key exchange based cipher - // suites were removed from the default list, but can be re-added with the - // GODEBUG setting tlsrsakex=1. In Go 1.23 3DES cipher suites were removed + // suites might change over time. In Go 1.23 3DES cipher suites were removed // from the default list, but can be re-added with the GODEBUG setting // tls3des=1. CipherSuites []uint16 diff --git a/src/crypto/tls/defaults.go b/src/crypto/tls/defaults.go index b853d1c7d6423f..97ffeb466c89f6 100644 --- a/src/crypto/tls/defaults.go +++ b/src/crypto/tls/defaults.go @@ -66,7 +66,6 @@ func defaultSupportedSignatureAlgorithms() []SignatureScheme { } } -var tlsrsakex = godebug.New("tlsrsakex") var tls3des = godebug.New("tls3des") func supportedCipherSuites(aesGCMPreferred bool) []uint16 { @@ -81,7 +80,6 @@ func defaultCipherSuites(aesGCMPreferred bool) []uint16 { cipherSuites := supportedCipherSuites(aesGCMPreferred) return slices.DeleteFunc(cipherSuites, func(c uint16) bool { return disabledCipherSuites[c] || - tlsrsakex.Value() != "1" && rsaKexCiphers[c] || tls3des.Value() != "1" && tdesCiphers[c] }) } diff --git a/src/crypto/tls/handshake_client.go b/src/crypto/tls/handshake_client.go index f2cb03eee286eb..7425275dd573f9 100644 --- a/src/crypto/tls/handshake_client.go +++ b/src/crypto/tls/handshake_client.go @@ -627,10 +627,6 @@ func (hs *clientHandshakeState) pickCipherSuite() error { return errors.New("tls: server chose an unconfigured cipher suite") } - if hs.c.config.CipherSuites == nil && !fips140tls.Required() && rsaKexCiphers[hs.suite.id] { - tlsrsakex.Value() // ensure godebug is initialized - tlsrsakex.IncNonDefault() - } if hs.c.config.CipherSuites == nil && !fips140tls.Required() && tdesCiphers[hs.suite.id] { tls3des.Value() // ensure godebug is initialized tls3des.IncNonDefault() diff --git a/src/crypto/tls/handshake_server.go b/src/crypto/tls/handshake_server.go index 92f3e979bf8b84..8256c35076836d 100644 --- a/src/crypto/tls/handshake_server.go +++ b/src/crypto/tls/handshake_server.go @@ -412,10 +412,6 @@ func (hs *serverHandshakeState) pickCipherSuite() error { } c.cipherSuite = hs.suite.id - if c.config.CipherSuites == nil && !fips140tls.Required() && rsaKexCiphers[hs.suite.id] { - tlsrsakex.Value() // ensure godebug is initialized - tlsrsakex.IncNonDefault() - } if c.config.CipherSuites == nil && !fips140tls.Required() && tdesCiphers[hs.suite.id] { tls3des.Value() // ensure godebug is initialized tls3des.IncNonDefault() diff --git a/src/crypto/tls/tls_test.go b/src/crypto/tls/tls_test.go index d288b66fedf93a..e14ce9cbd28e14 100644 --- a/src/crypto/tls/tls_test.go +++ b/src/crypto/tls/tls_test.go @@ -1715,16 +1715,14 @@ func TestCipherSuites(t *testing.T) { } // Check that disabled suites are marked insecure. - for _, badSuites := range []map[uint16]bool{disabledCipherSuites, rsaKexCiphers} { - for id := range badSuites { - c := CipherSuiteByID(id) - if c == nil { - t.Errorf("%#04x: no CipherSuite entry", id) - continue - } - if !c.Insecure { - t.Errorf("%#04x: disabled by default but not marked insecure", id) - } + for id := range disabledCipherSuites { + c := CipherSuiteByID(id) + if c == nil { + t.Errorf("%#04x: no CipherSuite entry", id) + continue + } + if !c.Insecure { + t.Errorf("%#04x: disabled by default but not marked insecure", id) } } diff --git a/src/internal/godebugs/table.go b/src/internal/godebugs/table.go index 70f44c1e885e84..2d9c2b72778cfe 100644 --- a/src/internal/godebugs/table.go +++ b/src/internal/godebugs/table.go @@ -64,7 +64,6 @@ var All = []Info{ {Name: "tls3des", Package: "crypto/tls", Changed: 23, Old: "1"}, {Name: "tlsmaxrsasize", Package: "crypto/tls"}, {Name: "tlsmlkem", Package: "crypto/tls", Changed: 24, Old: "0", Opaque: true}, - {Name: "tlsrsakex", Package: "crypto/tls", Changed: 22, Old: "1"}, {Name: "tlssecpmlkem", Package: "crypto/tls", Changed: 26, Old: "0", Opaque: true}, {Name: "tlssha1", Package: "crypto/tls", Changed: 25, Old: "1"}, // Mark tracebacklabels as Opaque so we don't generate a metric that we can't increment. @@ -99,6 +98,7 @@ var Removed = []RemovedInfo{ {Name: "x509sha1", Removed: 24}, {Name: "gotypesalias", Removed: 27}, {Name: "tlsunsafeekm", Removed: 27}, // Old: "1" + {Name: "tlsrsakex", Removed: 27}, // Old: "1" } // Lookup returns the Info with the given name. diff --git a/src/runtime/metrics/doc.go b/src/runtime/metrics/doc.go index 91c971fed3db5b..8ddbca14c91c6f 100644 --- a/src/runtime/metrics/doc.go +++ b/src/runtime/metrics/doc.go @@ -389,10 +389,6 @@ Below is the full list of supported metrics, ordered lexicographically. The number of non-default behaviors executed by the crypto/tls package due to a non-default GODEBUG=tlsmaxrsasize=... setting. - /godebug/non-default-behavior/tlsrsakex:events - The number of non-default behaviors executed by the crypto/tls - package due to a non-default GODEBUG=tlsrsakex=... setting. - /godebug/non-default-behavior/tlssha1:events The number of non-default behaviors executed by the crypto/tls package due to a non-default GODEBUG=tlssha1=... setting. From 0f4862de578a0d86062e45abedb3c141e675fb65 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Tue, 12 May 2026 16:03:59 -0400 Subject: [PATCH 09/32] crypto/tls: remove tls3des GODEBUG setting Updates #75316 Change-Id: Iedd2a6746d0ebd6a7b7147f34cb7435b6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/777382 TryBot-Bypass: Filippo Valsorda Reviewed-by: David Chase Reviewed-by: Roland Shoemaker Auto-Submit: Filippo Valsorda --- doc/godebug.md | 2 ++ src/crypto/tls/cipher_suites.go | 6 +----- src/crypto/tls/common.go | 4 +--- src/crypto/tls/defaults.go | 5 +---- src/crypto/tls/handshake_client.go | 5 ----- src/crypto/tls/handshake_server.go | 5 ----- src/internal/godebugs/table.go | 2 +- src/runtime/metrics/doc.go | 4 ---- 8 files changed, 6 insertions(+), 27 deletions(-) diff --git a/doc/godebug.md b/doc/godebug.md index 5520771052f9ac..8b1a5ffd2f20cc 100644 --- a/doc/godebug.md +++ b/doc/godebug.md @@ -162,6 +162,8 @@ Go 1.27 removed the `tlsunsafeekm` setting, as noted in the [Go 1.22](#go-122) s Go 1.27 removed the `tlsrsakex` setting, as noted in the [Go 1.22](#go-122) section. +Go 1.27 removed the `tls3des` setting, as noted in the [Go 1.23](#go-123) section. + Go 1.27 added a new `htmlmetacontenturlescape` setting that controls whether html/template will escape URLs in the `url=` portion of the content attribute of HTML meta tags. The default `htmlmetacontentescape=1` will cause URLs to be diff --git a/src/crypto/tls/cipher_suites.go b/src/crypto/tls/cipher_suites.go index 186d6aadf60f3d..1952be64fa952c 100644 --- a/src/crypto/tls/cipher_suites.go +++ b/src/crypto/tls/cipher_suites.go @@ -353,13 +353,9 @@ var disabledCipherSuites = map[uint16]bool{ TLS_RSA_WITH_AES_256_CBC_SHA: true, TLS_RSA_WITH_AES_128_GCM_SHA256: true, TLS_RSA_WITH_AES_256_GCM_SHA384: true, -} -// tdesCiphers contains 3DES ciphers, -// which we also disable by default unless a GODEBUG is set. -var tdesCiphers = map[uint16]bool{ + // 3DES TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: true, - TLS_RSA_WITH_3DES_EDE_CBC_SHA: true, } var ( diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index 87a4e7d98a85d7..296fd1db824bbb 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -723,9 +723,7 @@ type Config struct { // the list is ignored. Note that TLS 1.3 ciphersuites are not configurable. // // If CipherSuites is nil, a safe default list is used. The default cipher - // suites might change over time. In Go 1.23 3DES cipher suites were removed - // from the default list, but can be re-added with the GODEBUG setting - // tls3des=1. + // suites might change over time. CipherSuites []uint16 // PreferServerCipherSuites is a legacy field and has no effect. diff --git a/src/crypto/tls/defaults.go b/src/crypto/tls/defaults.go index 97ffeb466c89f6..c89036047ad5a3 100644 --- a/src/crypto/tls/defaults.go +++ b/src/crypto/tls/defaults.go @@ -66,8 +66,6 @@ func defaultSupportedSignatureAlgorithms() []SignatureScheme { } } -var tls3des = godebug.New("tls3des") - func supportedCipherSuites(aesGCMPreferred bool) []uint16 { if aesGCMPreferred { return slices.Clone(cipherSuitesPreferenceOrder) @@ -79,8 +77,7 @@ func supportedCipherSuites(aesGCMPreferred bool) []uint16 { func defaultCipherSuites(aesGCMPreferred bool) []uint16 { cipherSuites := supportedCipherSuites(aesGCMPreferred) return slices.DeleteFunc(cipherSuites, func(c uint16) bool { - return disabledCipherSuites[c] || - tls3des.Value() != "1" && tdesCiphers[c] + return disabledCipherSuites[c] }) } diff --git a/src/crypto/tls/handshake_client.go b/src/crypto/tls/handshake_client.go index 7425275dd573f9..3487e07c01ae04 100644 --- a/src/crypto/tls/handshake_client.go +++ b/src/crypto/tls/handshake_client.go @@ -627,11 +627,6 @@ func (hs *clientHandshakeState) pickCipherSuite() error { return errors.New("tls: server chose an unconfigured cipher suite") } - if hs.c.config.CipherSuites == nil && !fips140tls.Required() && tdesCiphers[hs.suite.id] { - tls3des.Value() // ensure godebug is initialized - tls3des.IncNonDefault() - } - hs.c.cipherSuite = hs.suite.id return nil } diff --git a/src/crypto/tls/handshake_server.go b/src/crypto/tls/handshake_server.go index 8256c35076836d..3e9ad8836bc5c0 100644 --- a/src/crypto/tls/handshake_server.go +++ b/src/crypto/tls/handshake_server.go @@ -412,11 +412,6 @@ func (hs *serverHandshakeState) pickCipherSuite() error { } c.cipherSuite = hs.suite.id - if c.config.CipherSuites == nil && !fips140tls.Required() && tdesCiphers[hs.suite.id] { - tls3des.Value() // ensure godebug is initialized - tls3des.IncNonDefault() - } - for _, id := range hs.clientHello.cipherSuites { if id == TLS_FALLBACK_SCSV { // The client is doing a fallback connection. See RFC 7507. diff --git a/src/internal/godebugs/table.go b/src/internal/godebugs/table.go index 2d9c2b72778cfe..0f827136f874f1 100644 --- a/src/internal/godebugs/table.go +++ b/src/internal/godebugs/table.go @@ -61,7 +61,6 @@ var All = []Info{ {Name: "rsa1024min", Package: "crypto/rsa", Changed: 24, Old: "0"}, {Name: "tarinsecurepath", Package: "archive/tar"}, {Name: "tls10server", Package: "crypto/tls", Changed: 22, Old: "1"}, - {Name: "tls3des", Package: "crypto/tls", Changed: 23, Old: "1"}, {Name: "tlsmaxrsasize", Package: "crypto/tls"}, {Name: "tlsmlkem", Package: "crypto/tls", Changed: 24, Old: "0", Opaque: true}, {Name: "tlssecpmlkem", Package: "crypto/tls", Changed: 26, Old: "0", Opaque: true}, @@ -99,6 +98,7 @@ var Removed = []RemovedInfo{ {Name: "gotypesalias", Removed: 27}, {Name: "tlsunsafeekm", Removed: 27}, // Old: "1" {Name: "tlsrsakex", Removed: 27}, // Old: "1" + {Name: "tls3des", Removed: 27}, // Old: "1" } // Lookup returns the Info with the given name. diff --git a/src/runtime/metrics/doc.go b/src/runtime/metrics/doc.go index 8ddbca14c91c6f..f06d142ae25404 100644 --- a/src/runtime/metrics/doc.go +++ b/src/runtime/metrics/doc.go @@ -381,10 +381,6 @@ Below is the full list of supported metrics, ordered lexicographically. The number of non-default behaviors executed by the crypto/tls package due to a non-default GODEBUG=tls10server=... setting. - /godebug/non-default-behavior/tls3des:events - The number of non-default behaviors executed by the crypto/tls - package due to a non-default GODEBUG=tls3des=... setting. - /godebug/non-default-behavior/tlsmaxrsasize:events The number of non-default behaviors executed by the crypto/tls package due to a non-default GODEBUG=tlsmaxrsasize=... setting. From 1634ae8c7c26d6c5cdd1a2c53d420ea7c9572ae3 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Tue, 12 May 2026 16:10:09 -0400 Subject: [PATCH 10/32] crypto/tls: remove the tls10server GODEBUG setting Updates #75316 Change-Id: I43e7311777fb79b9486a05c8e8d3a42e6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/777383 Auto-Submit: Filippo Valsorda Reviewed-by: Roland Shoemaker Reviewed-by: David Chase LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- doc/godebug.md | 2 ++ src/crypto/tls/common.go | 9 +-------- src/crypto/tls/handshake_server.go | 5 ----- src/internal/godebugs/table.go | 2 +- src/runtime/metrics/doc.go | 4 ---- 5 files changed, 4 insertions(+), 18 deletions(-) diff --git a/doc/godebug.md b/doc/godebug.md index 8b1a5ffd2f20cc..432a5ab1233880 100644 --- a/doc/godebug.md +++ b/doc/godebug.md @@ -164,6 +164,8 @@ Go 1.27 removed the `tlsrsakex` setting, as noted in the [Go 1.22](#go-122) sect Go 1.27 removed the `tls3des` setting, as noted in the [Go 1.23](#go-123) section. +Go 1.27 removed the `tls10server` setting, as noted in the [Go 1.22](#go-122) section. + Go 1.27 added a new `htmlmetacontenturlescape` setting that controls whether html/template will escape URLs in the `url=` portion of the content attribute of HTML meta tags. The default `htmlmetacontentescape=1` will cause URLs to be diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index 296fd1db824bbb..9bf8df095b63ee 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -788,9 +788,6 @@ type Config struct { // // By default, TLS 1.2 is currently used as the minimum. TLS 1.0 is the // minimum supported by this package. - // - // The server-side default can be reverted to TLS 1.0 by including the value - // "tls10server=1" in the GODEBUG environment variable. MinVersion uint16 // MaxVersion contains the maximum TLS version that is acceptable. @@ -1219,8 +1216,6 @@ var supportedVersions = []uint16{ const roleClient = true const roleServer = false -var tls10server = godebug.New("tls10server") - // supportedVersions returns the list of supported TLS versions, sorted from // highest to lowest (and hence also in preference order). func (c *Config) supportedVersions(isClient bool) []uint16 { @@ -1230,9 +1225,7 @@ func (c *Config) supportedVersions(isClient bool) []uint16 { continue } if (c == nil || c.MinVersion == 0) && v < VersionTLS12 { - if isClient || tls10server.Value() != "1" { - continue - } + continue } if isClient && c.EncryptedClientHelloConfigList != nil && v < VersionTLS13 { continue diff --git a/src/crypto/tls/handshake_server.go b/src/crypto/tls/handshake_server.go index 3e9ad8836bc5c0..f7179df66317f1 100644 --- a/src/crypto/tls/handshake_server.go +++ b/src/crypto/tls/handshake_server.go @@ -209,11 +209,6 @@ func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, *echServer return nil, nil, errors.New("tls: Encrypted Client Hello cannot be used pre-TLS 1.3") } - if c.config.MinVersion == 0 && c.vers < VersionTLS12 { - tls10server.Value() // ensure godebug is initialized - tls10server.IncNonDefault() - } - return clientHello, ech, nil } diff --git a/src/internal/godebugs/table.go b/src/internal/godebugs/table.go index 0f827136f874f1..6f3a98c95eed37 100644 --- a/src/internal/godebugs/table.go +++ b/src/internal/godebugs/table.go @@ -60,7 +60,6 @@ var All = []Info{ {Name: "randseednop", Package: "math/rand", Changed: 24, Old: "0"}, {Name: "rsa1024min", Package: "crypto/rsa", Changed: 24, Old: "0"}, {Name: "tarinsecurepath", Package: "archive/tar"}, - {Name: "tls10server", Package: "crypto/tls", Changed: 22, Old: "1"}, {Name: "tlsmaxrsasize", Package: "crypto/tls"}, {Name: "tlsmlkem", Package: "crypto/tls", Changed: 24, Old: "0", Opaque: true}, {Name: "tlssecpmlkem", Package: "crypto/tls", Changed: 26, Old: "0", Opaque: true}, @@ -99,6 +98,7 @@ var Removed = []RemovedInfo{ {Name: "tlsunsafeekm", Removed: 27}, // Old: "1" {Name: "tlsrsakex", Removed: 27}, // Old: "1" {Name: "tls3des", Removed: 27}, // Old: "1" + {Name: "tls10server", Removed: 27}, // Old: "1" } // Lookup returns the Info with the given name. diff --git a/src/runtime/metrics/doc.go b/src/runtime/metrics/doc.go index f06d142ae25404..310d232f6ffa3d 100644 --- a/src/runtime/metrics/doc.go +++ b/src/runtime/metrics/doc.go @@ -377,10 +377,6 @@ Below is the full list of supported metrics, ordered lexicographically. package due to a non-default GODEBUG=tarinsecurepath=... setting. - /godebug/non-default-behavior/tls10server:events - The number of non-default behaviors executed by the crypto/tls - package due to a non-default GODEBUG=tls10server=... setting. - /godebug/non-default-behavior/tlsmaxrsasize:events The number of non-default behaviors executed by the crypto/tls package due to a non-default GODEBUG=tlsmaxrsasize=... setting. From 2f57f7626e7e22e6d93ac8f2cdcbc14ffbbc45c5 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Tue, 12 May 2026 16:13:26 -0400 Subject: [PATCH 11/32] crypto/tls: remove the x509keypairleaf GODEBUG setting Fixes #75316 Change-Id: I241af97bf6a05e94f40a9f62393ed4fe6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/777384 Reviewed-by: David Chase LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Roland Shoemaker Auto-Submit: Filippo Valsorda --- doc/godebug.md | 2 ++ src/crypto/tls/tls.go | 18 +---------------- src/crypto/tls/tls_test.go | 36 +++++++--------------------------- src/internal/godebugs/table.go | 10 +++++----- src/runtime/metrics/doc.go | 5 ----- 5 files changed, 15 insertions(+), 56 deletions(-) diff --git a/doc/godebug.md b/doc/godebug.md index 432a5ab1233880..9c5c01d3b27670 100644 --- a/doc/godebug.md +++ b/doc/godebug.md @@ -166,6 +166,8 @@ Go 1.27 removed the `tls3des` setting, as noted in the [Go 1.23](#go-123) sectio Go 1.27 removed the `tls10server` setting, as noted in the [Go 1.22](#go-122) section. +Go 1.27 removed the `x509keypairleaf` setting, as noted in the [Go 1.23](#go-123) section. + Go 1.27 added a new `htmlmetacontenturlescape` setting that controls whether html/template will escape URLs in the `url=` portion of the content attribute of HTML meta tags. The default `htmlmetacontentescape=1` will cause URLs to be diff --git a/src/crypto/tls/tls.go b/src/crypto/tls/tls.go index 680d80f38952da..eefac8a98bca9d 100644 --- a/src/crypto/tls/tls.go +++ b/src/crypto/tls/tls.go @@ -34,7 +34,6 @@ import ( "encoding/pem" "errors" "fmt" - "internal/godebug" "net" "os" "strings" @@ -240,10 +239,6 @@ func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Con // files. The files must contain PEM encoded data. The certificate file may // contain intermediate certificates following the leaf certificate to form a // certificate chain. On successful return, Certificate.Leaf will be populated. -// -// Before Go 1.23 Certificate.Leaf was left nil, and the parsed certificate was -// discarded. This behavior can be re-enabled by setting "x509keypairleaf=0" -// in the GODEBUG environment variable. func LoadX509KeyPair(certFile, keyFile string) (Certificate, error) { certPEMBlock, err := os.ReadFile(certFile) if err != nil { @@ -256,14 +251,8 @@ func LoadX509KeyPair(certFile, keyFile string) (Certificate, error) { return X509KeyPair(certPEMBlock, keyPEMBlock) } -var x509keypairleaf = godebug.New("x509keypairleaf") - // X509KeyPair parses a public/private key pair from a pair of // PEM encoded data. On successful return, Certificate.Leaf will be populated. -// -// Before Go 1.23 Certificate.Leaf was left nil, and the parsed certificate was -// discarded. This behavior can be re-enabled by setting "x509keypairleaf=0" -// in the GODEBUG environment variable. func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error) { fail := func(err error) (Certificate, error) { return Certificate{}, err } @@ -317,12 +306,7 @@ func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error) { if err != nil { return fail(err) } - - if x509keypairleaf.Value() != "0" { - cert.Leaf = x509Cert - } else { - x509keypairleaf.IncNonDefault() - } + cert.Leaf = x509Cert cert.PrivateKey, err = parsePrivateKey(keyDERBlock.Bytes) if err != nil { diff --git a/src/crypto/tls/tls_test.go b/src/crypto/tls/tls_test.go index e14ce9cbd28e14..3db1365a971349 100644 --- a/src/crypto/tls/tls_test.go +++ b/src/crypto/tls/tls_test.go @@ -2541,35 +2541,13 @@ func TestX509KeyPairPopulateCertificate(t *testing.T) { } certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) - t.Run("x509keypairleaf=0", func(t *testing.T) { - testenv.SetGODEBUG(t, "x509keypairleaf=0") - cert, err := X509KeyPair(certPEM, keyPEM) - if err != nil { - t.Fatal(err) - } - if cert.Leaf != nil { - t.Fatal("Leaf should not be populated") - } - }) - t.Run("x509keypairleaf=1", func(t *testing.T) { - testenv.SetGODEBUG(t, "x509keypairleaf=1") - cert, err := X509KeyPair(certPEM, keyPEM) - if err != nil { - t.Fatal(err) - } - if cert.Leaf == nil { - t.Fatal("Leaf should be populated") - } - }) - t.Run("GODEBUG unset", func(t *testing.T) { - cert, err := X509KeyPair(certPEM, keyPEM) - if err != nil { - t.Fatal(err) - } - if cert.Leaf == nil { - t.Fatal("Leaf should be populated") - } - }) + cert, err := X509KeyPair(certPEM, keyPEM) + if err != nil { + t.Fatal(err) + } + if cert.Leaf == nil { + t.Fatal("Leaf should be populated") + } } func TestEarlyLargeCertMsg(t *testing.T) { diff --git a/src/internal/godebugs/table.go b/src/internal/godebugs/table.go index 6f3a98c95eed37..10b3919d51e10d 100644 --- a/src/internal/godebugs/table.go +++ b/src/internal/godebugs/table.go @@ -73,7 +73,6 @@ var All = []Info{ {Name: "urlstrictcolons", Package: "net/url", Changed: 26, Old: "0"}, {Name: "winreadlinkvolume", Package: "os", Changed: 23, Old: "0"}, {Name: "winsymlink", Package: "os", Changed: 23, Old: "0"}, - {Name: "x509keypairleaf", Package: "crypto/tls", Changed: 23, Old: "0"}, {Name: "x509negativeserial", Package: "crypto/x509", Changed: 23, Old: "1"}, {Name: "x509rsacrt", Package: "crypto/x509", Changed: 24, Old: "0"}, {Name: "x509sha256skid", Package: "crypto/x509", Changed: 25, Old: "0"}, @@ -95,10 +94,11 @@ type RemovedInfo struct { var Removed = []RemovedInfo{ {Name: "x509sha1", Removed: 24}, {Name: "gotypesalias", Removed: 27}, - {Name: "tlsunsafeekm", Removed: 27}, // Old: "1" - {Name: "tlsrsakex", Removed: 27}, // Old: "1" - {Name: "tls3des", Removed: 27}, // Old: "1" - {Name: "tls10server", Removed: 27}, // Old: "1" + {Name: "tlsunsafeekm", Removed: 27}, // Old: "1" + {Name: "tlsrsakex", Removed: 27}, // Old: "1" + {Name: "tls3des", Removed: 27}, // Old: "1" + {Name: "tls10server", Removed: 27}, // Old: "1" + {Name: "x509keypairleaf", Removed: 27}, // Old: "0" } // Lookup returns the Info with the given name. diff --git a/src/runtime/metrics/doc.go b/src/runtime/metrics/doc.go index 310d232f6ffa3d..22648f4008c73f 100644 --- a/src/runtime/metrics/doc.go +++ b/src/runtime/metrics/doc.go @@ -407,11 +407,6 @@ Below is the full list of supported metrics, ordered lexicographically. The number of non-default behaviors executed by the os package due to a non-default GODEBUG=winsymlink=... setting. - /godebug/non-default-behavior/x509keypairleaf:events - The number of non-default behaviors executed by the crypto/tls - package due to a non-default GODEBUG=x509keypairleaf=... - setting. - /godebug/non-default-behavior/x509negativeserial:events The number of non-default behaviors executed by the crypto/x509 package due to a non-default GODEBUG=x509negativeserial=... From 18f72b38427f56729ddf60dda87a969b0a9e73ad Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Wed, 13 May 2026 14:41:09 -0400 Subject: [PATCH 12/32] crypto/tls: add a test for running with broken certificates Change-Id: Iaaa0bc449ce24c81f1052b89152c3b5a6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/777880 Reviewed-by: Dmitri Shuralyov Reviewed-by: Roland Shoemaker Auto-Submit: Filippo Valsorda Reviewed-by: Daniel McCarney LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- src/crypto/tls/conn_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/crypto/tls/conn_test.go b/src/crypto/tls/conn_test.go index d272abf3bb601e..9fa09d97deceda 100644 --- a/src/crypto/tls/conn_test.go +++ b/src/crypto/tls/conn_test.go @@ -124,6 +124,37 @@ func TestCertificateSelection(t *testing.T) { } } +// TestBrokenCertificateSkipped checks that a Certificate in Config.Certificates +// whose leaf doesn't parse as X.509 doesn't prevent the next, valid certificate +// from being selected. It exercises both the legacy BuildNameToCertificate path +// and the SupportsCertificate-based selection. +func TestBrokenCertificateSkipped(t *testing.T) { + brokenCert := Certificate{Certificate: [][]byte{[]byte("not a valid X.509 certificate")}} + for _, test := range []struct { + name string + buildIndex bool + }{ + {name: "BuildNameToCertificate", buildIndex: true}, + {name: "SupportsCertificate", buildIndex: false}, + } { + t.Run(test.name, func(t *testing.T) { + serverConfig := testConfigServer.Clone() + serverConfig.Certificates = []Certificate{brokenCert, testECDSAP256Cert} + if test.buildIndex { + serverConfig.BuildNameToCertificate() + } + clientConfig := testConfigClient.Clone() + _, cs, err := testHandshake(t, clientConfig, serverConfig) + if err != nil { + t.Fatalf("handshake failed: %v", err) + } + if !cs.PeerCertificates[0].Equal(testECDSAP256Cert.Leaf) { + t.Fatalf("handshake succeeded but wrong certificate was used") + } + }) + } +} + // Run with multiple crypto configs to test the logic for computing TLS record overheads. func runDynamicRecordSizingTest(t *testing.T, serverConfig *Config) { clientConn, serverConn := localPipe(t) From 1debc9f0ce7302fde485f9d756302413c2a8d938 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Thu, 14 May 2026 10:03:27 -0400 Subject: [PATCH 13/32] crypto/tls: surface private key parsing error from X509KeyPair This can include e.g. an error that mentiones that ML-DSA is not available due to the FIPS 140-3 module version. Change-Id: I6f505d9baff80fee23edf6f8e995dd846a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/777881 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Dmitri Shuralyov Reviewed-by: Daniel McCarney Auto-Submit: Filippo Valsorda Reviewed-by: Roland Shoemaker --- src/crypto/tls/tls.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/crypto/tls/tls.go b/src/crypto/tls/tls.go index eefac8a98bca9d..5a710a3b88dbfa 100644 --- a/src/crypto/tls/tls.go +++ b/src/crypto/tls/tls.go @@ -357,20 +357,21 @@ func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error) { // PKCS #1 private keys by default, while OpenSSL 1.0.0 generates PKCS #8 keys. // OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three. func parsePrivateKey(der []byte) (crypto.PrivateKey, error) { - if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { - return key, nil + key, err := x509.ParsePKCS8PrivateKey(der) + pkcs8Err := err // Return the PKCS#8 error if all parsing attempts fail. + if err != nil { + key, err = x509.ParsePKCS1PrivateKey(der) } - if key, err := x509.ParsePKCS8PrivateKey(der); err == nil { - switch key := key.(type) { - case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, *mldsa.PrivateKey: - return key, nil - default: - return nil, errors.New("tls: found unknown private key type in PKCS#8 wrapping") - } + if err != nil { + key, err = x509.ParseECPrivateKey(der) } - if key, err := x509.ParseECPrivateKey(der); err == nil { + if err != nil { + return nil, fmt.Errorf("tls: failed to parse private key: %w", pkcs8Err) + } + switch key := key.(type) { + case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, *mldsa.PrivateKey: return key, nil + default: + return nil, errors.New("tls: found unknown private key type in PKCS#8 wrapping") } - - return nil, errors.New("tls: failed to parse private key") } From 2f9a9642e10b633f363bf32b3947b3b9f55b3ace Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Sat, 11 Apr 2026 14:39:52 +0200 Subject: [PATCH 14/32] crypto/ecdsa: reject empty hashes It's an API misuse and it's unreachable from other standard library packages, but since it produces/accepts trivially forged signatures, reject it explicitly. Change-Id: If7a56d18d6ec445a4d2620a71d85ab7d6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/765640 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Auto-Submit: Filippo Valsorda Reviewed-by: Roland Shoemaker Reviewed-by: David Chase --- src/crypto/ecdsa/ecdsa.go | 8 ++++++++ src/crypto/internal/fips140/ecdsa/ecdsa.go | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/src/crypto/ecdsa/ecdsa.go b/src/crypto/ecdsa/ecdsa.go index b2319ba0ecf9bd..7e924fe82d4881 100644 --- a/src/crypto/ecdsa/ecdsa.go +++ b/src/crypto/ecdsa/ecdsa.go @@ -380,6 +380,10 @@ func generateFIPS[P ecdsa.Point[P]](curve elliptic.Curve, c *ecdsa.Curve[P], ran // is set. This setting will be removed in a future Go release. Instead, use // [testing/cryptotest.SetGlobalRandom]. func SignASN1(r io.Reader, priv *PrivateKey, hash []byte) ([]byte, error) { + if len(hash) == 0 { + return nil, errors.New("ecdsa: hash cannot be empty") + } + if boring.Enabled && rand.IsDefaultReader(r) { b, err := boringPrivateKey(priv) if err != nil { @@ -497,6 +501,10 @@ func addASN1IntBytes(b *cryptobyte.Builder, bytes []byte) { // The inputs are not considered confidential, and may leak through timing side // channels, or if an attacker has control of part of the inputs. func VerifyASN1(pub *PublicKey, hash, sig []byte) bool { + if len(hash) == 0 { + return false + } + if boring.Enabled { key, err := boringPublicKey(pub) if err != nil { diff --git a/src/crypto/internal/fips140/ecdsa/ecdsa.go b/src/crypto/internal/fips140/ecdsa/ecdsa.go index 1c00f55810a772..f01ee127236106 100644 --- a/src/crypto/internal/fips140/ecdsa/ecdsa.go +++ b/src/crypto/internal/fips140/ecdsa/ecdsa.go @@ -282,6 +282,9 @@ func Sign[P Point[P], H hash.Hash](c *Curve[P], h func() H, priv *PrivateKey, ra if priv.pub.curve != c.curve { return nil, errors.New("ecdsa: private key does not match curve") } + if len(hash) == 0 { + return nil, errors.New("ecdsa: hash cannot be empty") + } fips140.RecordApproved() fipsSelfTest() @@ -315,6 +318,9 @@ func SignDeterministic[P Point[P], H hash.Hash](c *Curve[P], h func() H, priv *P if priv.pub.curve != c.curve { return nil, errors.New("ecdsa: private key does not match curve") } + if len(hash) == 0 { + return nil, errors.New("ecdsa: hash cannot be empty") + } fips140.RecordApproved() fipsSelfTestDeterministic() drbg := newDRBG(h, priv.d, bits2octets(c, hash), nil) // RFC 6979, Section 3.3 @@ -447,6 +453,9 @@ func Verify[P Point[P]](c *Curve[P], pub *PublicKey, hash []byte, sig *Signature if pub.curve != c.curve { return errors.New("ecdsa: public key does not match curve") } + if len(hash) == 0 { + return errors.New("ecdsa: hash cannot be empty") + } fips140.RecordApproved() fipsSelfTest() return verify(c, pub, hash, sig) From 7f4f2c1c7be6ba312845426632ef8dc04a4a5637 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Sat, 11 Apr 2026 14:43:36 +0200 Subject: [PATCH 15/32] crypto/ecdsa: check the hash length in PrivateKey.Sign Unfortunately, SignASN1 doesn't take the hash function at all, but PrivateKey.Sign does, so we can check the hash length there. This is arguably a breaking change, but the previous behavior is almost certain to be a bug. opts was allowed to be nil, so continue to allow that. Change-Id: I75a11ab3f9df9de4234b1fc913f26ab06a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/765641 Reviewed-by: David Chase Reviewed-by: Roland Shoemaker Reviewed-by: Mark Freeman Reviewed-by: Daniel McCarney Auto-Submit: Filippo Valsorda LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- doc/next/6-stdlib/99-minor/crypto/ecdsa/hashlen.md | 2 ++ src/crypto/ecdsa/ecdsa.go | 5 +++++ src/crypto/internal/fips140/ecdsa/ecdsa.go | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 doc/next/6-stdlib/99-minor/crypto/ecdsa/hashlen.md diff --git a/doc/next/6-stdlib/99-minor/crypto/ecdsa/hashlen.md b/doc/next/6-stdlib/99-minor/crypto/ecdsa/hashlen.md new file mode 100644 index 00000000000000..88673f8fa3a135 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/ecdsa/hashlen.md @@ -0,0 +1,2 @@ +[PrivateKey.Sign] now checks that the length of the hash is correct, if opts is +not nil. diff --git a/src/crypto/ecdsa/ecdsa.go b/src/crypto/ecdsa/ecdsa.go index 7e924fe82d4881..40a89017570171 100644 --- a/src/crypto/ecdsa/ecdsa.go +++ b/src/crypto/ecdsa/ecdsa.go @@ -324,6 +324,11 @@ func (priv *PrivateKey) Sign(random io.Reader, digest []byte, opts crypto.Signer if random == nil { return signRFC6979(priv, digest, opts) } + if opts != nil { + if hashSize := opts.HashFunc().Size(); hashSize != len(digest) { + return nil, errors.New("ecdsa: hash length does not match hash function") + } + } random = rand.CustomReader(random) return SignASN1(random, priv, digest) } diff --git a/src/crypto/internal/fips140/ecdsa/ecdsa.go b/src/crypto/internal/fips140/ecdsa/ecdsa.go index f01ee127236106..5759c34c6a12bb 100644 --- a/src/crypto/internal/fips140/ecdsa/ecdsa.go +++ b/src/crypto/internal/fips140/ecdsa/ecdsa.go @@ -274,7 +274,7 @@ type Signature struct { R, S []byte } -// Sign signs a hash (which shall be the result of hashing a larger message with +// Sign signs a hash (which should be the result of hashing a larger message with // the hash function H) using the private key, priv. If the hash is longer than // the bit-length of the private key's curve order, the hash will be truncated // to that length. From 83b29183afa653b2efb96260fa041d08f1e210ea Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Wed, 22 Apr 2026 10:36:42 -0400 Subject: [PATCH 16/32] crypto/internal/fips140/rsa: add large exponent OAEP for ACVP Similar to the prior largeexponent.go work this adds a new testing only private key type & associated API surface for performing RSA OAEP encryption and decryption. This is necessary to support ACVP KTS-IFC testing with random exponents, since we can't constrain the ACVP server selected exponent to the range supported by the production APIs. We take the hit of duplicating some code to this test only package instead of increasing the complexity of the production code only to support this narrow usage. Change-Id: I3e34385e808da4cd40dd81e8553e0a92a6c294fe Reviewed-on: https://go-review.googlesource.com/c/go/+/769760 Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Filippo Valsorda Reviewed-by: David Chase --- .../internal/fips140/rsa/largeexponent.go | 250 ++++++++++++++++++ 1 file changed, 250 insertions(+) diff --git a/src/crypto/internal/fips140/rsa/largeexponent.go b/src/crypto/internal/fips140/rsa/largeexponent.go index e09052509518b5..ba9a3365ea7eb9 100644 --- a/src/crypto/internal/fips140/rsa/largeexponent.go +++ b/src/crypto/internal/fips140/rsa/largeexponent.go @@ -6,10 +6,14 @@ package rsa import ( "bytes" + "crypto/internal/constanttime" "crypto/internal/fips140" "crypto/internal/fips140/bigmod" + "crypto/internal/fips140/drbg" + "crypto/internal/fips140/subtle" "errors" "hash" + "io" ) // TestingOnlyLargeExponentPublicKey is a variant of [PublicKey] that supports @@ -113,3 +117,249 @@ func TestingOnlyLargeExponentVerifyPSS(pub *TestingOnlyLargeExponentPublicKey, h } return emsaPSSVerify(digest, em, emBits, pssSaltLengthAutodetect, hash) } + +// TestingOnlyLargeExponentPrivateKey is a variant of [PrivateKey] that supports +// large public exponents. It is only meant for supporting the full ACVP test +// suite. This type must not be used in production code. +type TestingOnlyLargeExponentPrivateKey struct { + n *bigmod.Modulus + e []byte // big-endian public exponent + d *bigmod.Nat + p, q *bigmod.Modulus + dP []byte + dQ []byte + qInv *bigmod.Nat +} + +func (priv *TestingOnlyLargeExponentPrivateKey) Size() int { + return (priv.n.BitLen() + 7) / 8 +} + +// TestingOnlyNewLargeExponentPrivateKeyWithPrecomputation creates a new RSA private key +// with a large public exponent from the given parameters. It is only meant for ACVP testing. +func TestingOnlyNewLargeExponentPrivateKeyWithPrecomputation(N []byte, e []byte, d, P, Q, dP, dQ, qInv []byte) (*TestingOnlyLargeExponentPrivateKey, error) { + n, err := bigmod.NewModulus(N) + if err != nil { + return nil, err + } + p, err := bigmod.NewModulus(P) + if err != nil { + return nil, err + } + q, err := bigmod.NewModulus(Q) + if err != nil { + return nil, err + } + dN, err := bigmod.NewNat().SetBytes(d, n) + if err != nil { + return nil, err + } + qInvNat, err := bigmod.NewNat().SetBytes(qInv, p) + if err != nil { + return nil, err + } + + priv := &TestingOnlyLargeExponentPrivateKey{ + n: n, e: e, d: dN, p: p, q: q, + dP: dP, dQ: dQ, qInv: qInvNat, + } + if err := checkLargeExponentPrivateKey(priv); err != nil { + return nil, err + } + return priv, nil +} + +func checkLargeExponentPrivateKey(priv *TestingOnlyLargeExponentPrivateKey) error { + // Check public key portion. + pub := &TestingOnlyLargeExponentPublicKey{N: priv.n, E: priv.e} + if err := checkLargeExponentPublicKey(pub); err != nil { + return err + } + + N := priv.n + p := priv.p + q := priv.q + + // FIPS 186-5, Section 5.1 requires "that p and q be of the same bit length." + if p.BitLen() != q.BitLen() { + // We don't enforce this for testing, just note it. + } + + // Check that pq ≡ 1 mod N (and that p < N and q < N). + pN := bigmod.NewNat().ExpandFor(N) + if _, err := pN.SetBytes(p.Nat().Bytes(p), N); err != nil { + return errors.New("crypto/rsa: invalid prime") + } + qN := bigmod.NewNat().ExpandFor(N) + if _, err := qN.SetBytes(q.Nat().Bytes(q), N); err != nil { + return errors.New("crypto/rsa: invalid prime") + } + if pN.Mul(qN, N).IsZero() != 1 { + return errors.New("crypto/rsa: p * q != n") + } + + // Check that de ≡ 1 mod p-1, and de ≡ 1 mod q-1. + // Uses byte-slice exponent for large exponents. + pMinus1, err := bigmod.NewModulus(p.Nat().SubOne(p).Bytes(p)) + if err != nil { + return errors.New("crypto/rsa: invalid prime") + } + dP, err := bigmod.NewNat().SetBytes(priv.dP, pMinus1) + if err != nil { + return errors.New("crypto/rsa: invalid CRT exponent") + } + de := bigmod.NewNat() + if _, err := de.SetBytes(priv.e, pMinus1); err != nil { + // Exponent might be larger than p-1, reduce it. + eNat, _ := bigmod.NewNat().SetBytes(priv.e, priv.n) + de.Mod(eNat, pMinus1) + } + de.Mul(dP, pMinus1) + if de.IsOne() != 1 { + return errors.New("crypto/rsa: invalid CRT exponent") + } + + qMinus1, err := bigmod.NewModulus(q.Nat().SubOne(q).Bytes(q)) + if err != nil { + return errors.New("crypto/rsa: invalid prime") + } + dQ, err := bigmod.NewNat().SetBytes(priv.dQ, qMinus1) + if err != nil { + return errors.New("crypto/rsa: invalid CRT exponent") + } + if _, err := de.SetBytes(priv.e, qMinus1); err != nil { + // Exponent might be larger than q-1, reduce it. + eNat, _ := bigmod.NewNat().SetBytes(priv.e, priv.n) + de.Mod(eNat, qMinus1) + } + de.Mul(dQ, qMinus1) + if de.IsOne() != 1 { + return errors.New("crypto/rsa: invalid CRT exponent") + } + + // Check that qInv * q ≡ 1 mod p. + qP, err := bigmod.NewNat().SetOverflowingBytes(q.Nat().Bytes(q), p) + if err != nil { + // q >= 2^⌈log2(p)⌉ + qP = bigmod.NewNat().Mod(q.Nat(), p) + } + if qP.Mul(priv.qInv, p).IsOne() != 1 { + return errors.New("crypto/rsa: invalid CRT coefficient") + } + + return nil +} + +func decryptLargeExponent(priv *TestingOnlyLargeExponentPrivateKey, ciphertext []byte) ([]byte, error) { + N := priv.n + c, err := bigmod.NewNat().SetBytes(ciphertext, N) + if err != nil { + return nil, ErrDecryption + } + + // CRT-based decryption (same as regular decrypt, doesn't use E). + P, Q := priv.p, priv.q + t0 := bigmod.NewNat() + // m = c ^ Dp mod p + m := bigmod.NewNat().Exp(t0.Mod(c, P), priv.dP, P) + // m2 = c ^ Dq mod q + m2 := bigmod.NewNat().Exp(t0.Mod(c, Q), priv.dQ, Q) + // m = m - m2 mod p + m.Sub(t0.Mod(m2, P), P) + // m = m * Qinv mod p + m.Mul(priv.qInv, P) + // m = m * q mod N + m.ExpandFor(N).Mul(t0.Mod(Q.Nat(), N), N) + // m = m + m2 mod N + m.Add(m2.ExpandFor(N), N) + + return m.Bytes(N), nil +} + +// TestingOnlyLargeExponentDecryptOAEP decrypts ciphertext using RSAES-OAEP with +// a private key that has a large public exponent. It is only meant for ACVP testing. +func TestingOnlyLargeExponentDecryptOAEP(hash, mgfHash hash.Hash, priv *TestingOnlyLargeExponentPrivateKey, ciphertext []byte, label []byte) ([]byte, error) { + fipsSelfTest() + fips140.RecordApproved() + checkApprovedHash(hash) + + k := priv.Size() + if len(ciphertext) > k || k < hash.Size()*2+2 { + return nil, ErrDecryption + } + + em, err := decryptLargeExponent(priv, ciphertext) + if err != nil { + return nil, err + } + + hash.Reset() + hash.Write(label) + lHash := hash.Sum(nil) + + firstByteIsZero := constanttime.ByteEq(em[0], 0) + + seed := em[1 : hash.Size()+1] + db := em[hash.Size()+1:] + + mgf1XOR(seed, mgfHash, db) + mgf1XOR(db, mgfHash, seed) + + lHash2 := db[0:hash.Size()] + + lHash2Good := subtle.ConstantTimeCompare(lHash, lHash2) + + var lookingForIndex, index, invalid int + lookingForIndex = 1 + rest := db[hash.Size():] + + for i := 0; i < len(rest); i++ { + equals0 := constanttime.ByteEq(rest[i], 0) + equals1 := constanttime.ByteEq(rest[i], 1) + index = constanttime.Select(lookingForIndex&equals1, i, index) + lookingForIndex = constanttime.Select(equals1, 0, lookingForIndex) + invalid = constanttime.Select(lookingForIndex&^equals0, 1, invalid) + } + + if firstByteIsZero&lHash2Good&^invalid&^lookingForIndex != 1 { + return nil, ErrDecryption + } + + return rest[index+1:], nil +} + +// TestingOnlyLargeExponentEncryptOAEP encrypts the given message with RSAES-OAEP +// using a public key with a large exponent. It is only meant for ACVP testing. +func TestingOnlyLargeExponentEncryptOAEP(hash, mgfHash hash.Hash, random io.Reader, pub *TestingOnlyLargeExponentPublicKey, msg []byte, label []byte) ([]byte, error) { + fipsSelfTest() + fips140.RecordApproved() + checkApprovedHash(hash) + if err := checkLargeExponentPublicKey(pub); err != nil { + return nil, err + } + k := pub.Size() + if len(msg) > k-2*hash.Size()-2 { + return nil, ErrMessageTooLong + } + + hash.Reset() + hash.Write(label) + lHash := hash.Sum(nil) + + em := make([]byte, k) + seed := em[1 : 1+hash.Size()] + db := em[1+hash.Size():] + + copy(db[0:hash.Size()], lHash) + db[len(db)-len(msg)-1] = 1 + copy(db[len(db)-len(msg):], msg) + + if err := drbg.ReadWithReader(random, seed); err != nil { + return nil, err + } + + mgf1XOR(db, mgfHash, seed) + mgf1XOR(seed, mgfHash, db) + + return encryptLargeExponent(pub, em) +} From 469636308be7a54876dae34aa0605fa8b9e9e1b4 Mon Sep 17 00:00:00 2001 From: Michael Pratt Date: Tue, 19 May 2026 15:10:53 -0400 Subject: [PATCH 17/32] encoding/json/jsontext: drop duplicate import This is a semantic conflict between CL 779920 and CL 779960. Change-Id: I0a6e153200d8c0ef0c1cad05e52b42176a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/780060 TryBot-Bypass: Michael Pratt Auto-Submit: Michael Pratt Reviewed-by: Robert Griesemer --- src/encoding/json/jsontext/token_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/encoding/json/jsontext/token_test.go b/src/encoding/json/jsontext/token_test.go index 788eb5e42894a9..f0b1e41e2020d7 100644 --- a/src/encoding/json/jsontext/token_test.go +++ b/src/encoding/json/jsontext/token_test.go @@ -13,8 +13,6 @@ import ( "reflect" "strconv" "testing" - - "internal/testenv" ) func TestTokenStringAllocations(t *testing.T) { From 4b77d329eac3c4fb207bcef648f4195a6587de7d Mon Sep 17 00:00:00 2001 From: Michael Pratt Date: Tue, 19 May 2026 12:34:46 -0400 Subject: [PATCH 18/32] encoding/json/v2: add string option hint optimization Starting with CL 779300, we return an error if fields with a `string` tag do not have a supported type. Most field validation occurs in makeStructFields. makeStructFields runs in a per-type sync.Once, with errors reported during each Marshal/Unmarshal call before any marshaling or unmarshaling begins. The string validation semantics depend on the StringifyWithLegacySemantics option, so the full validation cannot occur inside the sync.Once. Instead each Marshal/Unmarshal call must loop over every field to perform validation. We must use a second loop over the fields rather than performing validation inline inside the main marshal loop to match the other field validation semantic of reporting errors before marshaling begins. The loop isn't very expensive, but it does double the number of times we need to loop at each struct field. Package benchmarks did not show any statistically significant regressions on specific benchmarks (all regressions I saw disappear when running just that benchmark with more iterations). Regardless, BenchmarkUnmarshal/Struct/ManySmall spends ~2% of CPU time on this loop. Most structs won't have any fields with a string tag, so add a simple hint for when at least one field has the tag, and skip the validation loop otherwise. For #79065. Change-Id: I6f8b7d8c120c43e68b32dd1b19107d266a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/779940 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Auto-Submit: Michael Pratt Reviewed-by: Damien Neil --- src/encoding/json/v2/arshal_default.go | 4 ++-- src/encoding/json/v2/fields.go | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/encoding/json/v2/arshal_default.go b/src/encoding/json/v2/arshal_default.go index ee001d5d75f13a..2c6b0c64cc9601 100644 --- a/src/encoding/json/v2/arshal_default.go +++ b/src/encoding/json/v2/arshal_default.go @@ -1085,7 +1085,7 @@ func makeStructArshaler(t reflect.Type) *arshaler { // This validation is effectively a makeStructFields error that // occurs before any marshalling begins, but since it depends // on the marshal options it can't be part of the sync.Once. - if !mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + if fields.hasString && !mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { for i := range fields.flattened { f := &fields.flattened[i] if f.string { @@ -1290,7 +1290,7 @@ func makeStructArshaler(t reflect.Type) *arshaler { // This validation is effectively a makeStructFields error that // occurs before any marshalling begins, but since it depends // on the marshal options it can't be part of the sync.Once. - if !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + if fields.hasString && !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { for i := range fields.flattened { f := &fields.flattened[i] if f.string { diff --git a/src/encoding/json/v2/fields.go b/src/encoding/json/v2/fields.go index 0fcf4f849835a1..d91e5bf26e3762 100644 --- a/src/encoding/json/v2/fields.go +++ b/src/encoding/json/v2/fields.go @@ -34,6 +34,7 @@ type structFields struct { byActualName map[string]*structField byFoldedName map[string][]*structField inlinedFallback *structField + hasString bool // one or more fields set the string option } // reindex recomputes index to avoid bounds check during runtime. @@ -81,6 +82,9 @@ func makeStructFields(root reflect.Type) (fs structFields, serr *SemanticError) return cmp.Or(serr, &SemanticError{GoType: t, Err: fmt.Errorf(f, a...)}) } + // Whether any field sets the string option. + var hasString bool + // Setup a queue for a breadth-first search. var queueIndex int type queueEntry struct { @@ -248,6 +252,9 @@ func makeStructFields(root reflect.Type) (fs structFields, serr *SemanticError) f.id = len(allFields) f.fncs = lookupArshaler(sf.Type) allFields = append(allFields, f) + if f.string { + hasString = true + } } } @@ -313,6 +320,7 @@ func makeStructFields(root reflect.Type) (fs structFields, serr *SemanticError) flattened: flattened, byActualName: make(map[string]*structField, len(flattened)), byFoldedName: make(map[string][]*structField, len(flattened)), + hasString: hasString, } for i, f := range fs.flattened { foldedName := string(foldName([]byte(f.name))) From c07a0f09b82e31a15e63527ddfaf01b081abfd7a Mon Sep 17 00:00:00 2001 From: Paul Murphy Date: Wed, 13 May 2026 08:27:01 -0500 Subject: [PATCH 19/32] doc: document new ppc64/linux features Updates #76244 Change-Id: Id5dac150a179246d3b73dfdab6653b362cbbded9 Reviewed-on: https://go-review.googlesource.com/c/go/+/777620 Reviewed-by: Cherry Mui Reviewed-by: Dmitri Shuralyov Reviewed-by: Dmitri Shuralyov TryBot-Bypass: Dmitri Shuralyov Commit-Queue: Paul Murphy Auto-Submit: Paul Murphy --- doc/next/7-ports.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/next/7-ports.md b/doc/next/7-ports.md index 3f39ced990f92f..7ce1d914ac1d28 100644 --- a/doc/next/7-ports.md +++ b/doc/next/7-ports.md @@ -6,3 +6,13 @@ As [announced](go1.26#darwin) in the Go 1.26 release notes, Go 1.27 requires macOS 13 Ventura or later; support for previous versions has been discontinued. + +### Linux {#linux} + +On ppc64, the ABI has been migrated to ELFv2. This change +has no effect for those building and running pure Go +binaries. + +On ppc64, external linking, cgo, and PIE binaries are now +supported. Using these features requires an ELFv2 compatible +runtime (libc, kernel, and all linked and loaded libraries). From 5563d58a15912df7c7ad48100d3b7abdcc0cddb4 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 19 May 2026 11:30:32 -0700 Subject: [PATCH 20/32] go/printer: update comments and simplify test (cleanup) Follow-up on CL 752220. Change-Id: I848f9e721b0a209c70903bfb23a38f3d716c65a3 Reviewed-on: https://go-review.googlesource.com/c/go/+/779921 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Neal Patel Reviewed-by: Robert Griesemer Auto-Submit: Robert Griesemer --- src/go/printer/nodes.go | 20 +++++++++++--------- src/go/printer/printer_test.go | 26 ++++++++++---------------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/go/printer/nodes.go b/src/go/printer/nodes.go index 5f697e88bc74a3..f481a196c1daa4 100644 --- a/src/go/printer/nodes.go +++ b/src/go/printer/nodes.go @@ -1312,8 +1312,7 @@ func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, po // isCompositeLitLike reports whether x is a composite literal or an expression // whose core is a composite literal (e.g. &T{...}), ignoring parentheses. func isCompositeLitLike(x ast.Expr) bool { - x = stripParensAlways(x) - switch x := x.(type) { + switch x := stripParensAlways(x).(type) { case *ast.CompositeLit: return true case *ast.UnaryExpr: @@ -1326,10 +1325,13 @@ func isCompositeLitLike(x ast.Expr) bool { // indentList reports whether an expression list would look better if it // were indented wholesale (starting with the very first element, rather // than starting at the first line break). +// Currently this function is only used to improve formatting of return +// statements. func (p *printer) indentList(list []ast.Expr) bool { // Heuristic: indentList reports whether there are more than one multi- - // line element in the list, or if there is any element that is not - // starting on the same line as the previous one ends. + // line element (such as a complex expression, but excluding composite + // literals) in the list, or if there is any element that is not starting + // on the same line as the previous one ends. if len(list) >= 2 { var b = p.lineFor(list[0].Pos()) var e = p.lineFor(list[len(list)-1].End()) @@ -1345,11 +1347,11 @@ func (p *printer) indentList(list []ast.Expr) bool { // line as the previous one ended return true } - if xb < xe { - // x is a multi-line element. - if !isCompositeLitLike(x) { - n++ - } + if xb < xe && !isCompositeLitLike(x) { + // x is a multi-line element but not a composite literal + // (composite literals have their own field indentation + // already, see go.dev/issue/7195) + n++ } line = xe } diff --git a/src/go/printer/printer_test.go b/src/go/printer/printer_test.go index fd79726b8e11d6..97940f480306dc 100644 --- a/src/go/printer/printer_test.go +++ b/src/go/printer/printer_test.go @@ -907,40 +907,34 @@ func TestEmptyDecl(t *testing.T) { // issue 63566 // when printing a return statement with multiple multi-line composite literals. func TestIssue7195(t *testing.T) { const src = `package p - type T struct{ x int } - -func f() (*T, *T) { - return &T{ +func _() (T, *T) { + return T{ x: 1, }, &T{ x: 2, } } ` - fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "", src, 0) - if err != nil { - t.Fatal(err) - } - var buf bytes.Buffer - if err := Fprint(&buf, fset, file); err != nil { - t.Fatal(err) - } const want = `package p type T struct{ x int } -func f() (*T, *T) { - return &T{ +func _() (T, *T) { + return T{ x: 1, }, &T{ x: 2, } } ` - if got := buf.String(); got != want { + + got, err := format([]byte(src), 0) + if err != nil { + t.Fatal(err) + } + if got := string(got); got != want { t.Fatalf("got:\n%s\nwant:\n%s\n", got, want) } } From 0db7bea636f42bf55189168c2a34b741e3f187b1 Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Tue, 19 May 2026 11:42:41 -0700 Subject: [PATCH 21/32] cmd/dist: fix JSON processing of trailing bytes The testJSONFilter.process was implicitly depending on the exact internal buffer size of the json.Decoder, which happened to be large enough in v1 for most lines that cmd/dist cared about. However, the re-implementation of v1 using encoding/json/v2 changes the implicit buffer size used, which results in some trailing bytes accidentally being discarded. The correct use of json.Decoder.Buffered requires also consulting the input io.Reader for additional trailing data. Fixes #79498 Change-Id: If4bdac12b638d78e0870808f2285fb1731b0fae6 Reviewed-on: https://go-review.googlesource.com/c/go/+/779980 Reviewed-by: Michael Pratt Reviewed-by: Damien Neil Auto-Submit: Damien Neil LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- src/cmd/dist/testjson.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cmd/dist/testjson.go b/src/cmd/dist/testjson.go index c190c665ebf779..8ae6e8905d578c 100644 --- a/src/cmd/dist/testjson.go +++ b/src/cmd/dist/testjson.go @@ -80,7 +80,8 @@ func (f *testJSONFilter) process(line []byte) { // struct, or other additions outside of it. If humans are ever looking // at the output, it's really nice to keep field order because it // preserves a lot of regularity in the output. - dec := json.NewDecoder(bytes.NewBuffer(line)) + lineBuf := bytes.NewBuffer(line) + dec := json.NewDecoder(lineBuf) dec.UseNumber() val, err := decodeJSONValue(dec) if err == nil && val.atom == json.Delim('{') { @@ -105,6 +106,7 @@ func (f *testJSONFilter) process(line []byte) { // Copy any trailing text. We expect at most a "\n" here, but // there could be other text and we want to feed that through. io.Copy(f.w, dec.Buffered()) + io.Copy(f.w, lineBuf) return } } From edf006c9a3fbb23304dd53590e62fef9250ebb94 Mon Sep 17 00:00:00 2001 From: "Nicholas S. Husin" Date: Tue, 19 May 2026 15:04:41 -0400 Subject: [PATCH 22/32] net/mail: escape arbitrary input when including them in errors This ensures that input with unexpected characters like control characters will not be interpreted in unintended ways should they ever be printed or logged. Change-Id: I9ee18b44d8ca067886dca065e5aae9266a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/780041 Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Nicholas Husin --- src/net/mail/message.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/net/mail/message.go b/src/net/mail/message.go index 80140a467771b9..7c18c28d31485d 100644 --- a/src/net/mail/message.go +++ b/src/net/mail/message.go @@ -81,7 +81,7 @@ func readHeader(r *textproto.Reader) (map[string][]string, error) { if err != nil { return m, err } - return m, errors.New("malformed initial line: " + line) + return m, fmt.Errorf("malformed initial line: %q", line) } for { @@ -93,7 +93,7 @@ func readHeader(r *textproto.Reader) (map[string][]string, error) { // Key ends at first colon. k, v, ok := strings.Cut(kv, ":") if !ok { - return m, errors.New("malformed header line: " + kv) + return m, fmt.Errorf("malformed header line: %q", kv) } key := textproto.CanonicalMIMEHeaderKey(k) From ad46b4815ea61500580f967ff2464e94d2ee9e18 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sun, 12 Apr 2026 20:39:26 +0800 Subject: [PATCH 23/32] crypto/tls: clamp effective minimum version to TLS 1.3 when using QUIC Change-Id: Ieec72362bacf1956a2bd5e0b2eb8dad88e624bd1 Reviewed-on: https://go-review.googlesource.com/c/go/+/745980 Reviewed-by: Damien Neil LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Roland Shoemaker --- src/crypto/tls/common.go | 19 +++++---- src/crypto/tls/handshake_client.go | 6 +-- src/crypto/tls/handshake_server.go | 7 ++-- src/crypto/tls/handshake_server_tls13.go | 2 +- src/crypto/tls/quic.go | 7 ---- src/crypto/tls/quic_test.go | 53 ++++++++++++++++++++++++ 6 files changed, 73 insertions(+), 21 deletions(-) diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index 9bf8df095b63ee..b2cb869650ca97 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -495,6 +495,9 @@ type ClientHelloInfo struct { // for use with SupportsCertificate. config *Config + // isQUIC indicates whether the connection is a QUIC connection. + isQUIC bool + // ctx is the context of the handshake that is in progress. ctx context.Context } @@ -1068,7 +1071,6 @@ func (c *Config) initLegacySessionTicketKeyRLocked() { } else if !bytes.HasPrefix(c.SessionTicketKey[:], deprecatedSessionTicketKey) && len(c.sessionTicketKeys) == 0 { c.sessionTicketKeys = []ticketKey{c.ticketKeyFromBytes(c.SessionTicketKey)} } - } // ticketKeys returns the ticketKeys for this connection. @@ -1218,7 +1220,7 @@ const roleServer = false // supportedVersions returns the list of supported TLS versions, sorted from // highest to lowest (and hence also in preference order). -func (c *Config) supportedVersions(isClient bool) []uint16 { +func (c *Config) supportedVersions(isClient, isQUIC bool) []uint16 { versions := make([]uint16, 0, len(supportedVersions)) for _, v := range supportedVersions { if fips140tls.Required() && !slices.Contains(allowedSupportedVersionsFIPS, v) { @@ -1236,13 +1238,16 @@ func (c *Config) supportedVersions(isClient bool) []uint16 { if c != nil && c.MaxVersion != 0 && v > c.MaxVersion { continue } + if isQUIC && v < VersionTLS13 { + continue + } versions = append(versions, v) } return versions } -func (c *Config) maxSupportedVersion(isClient bool) uint16 { - supportedVersions := c.supportedVersions(isClient) +func (c *Config) maxSupportedVersion(isClient, isQUIC bool) uint16 { + supportedVersions := c.supportedVersions(isClient, isQUIC) if len(supportedVersions) == 0 { return 0 } @@ -1294,8 +1299,8 @@ func (c *Config) supportsCurve(version uint16, x CurveID) bool { // mutualVersion returns the protocol version to use given the advertised // versions of the peer. The highest supported version is preferred. -func (c *Config) mutualVersion(isClient bool, peerVersions []uint16) (uint16, bool) { - supportedVersions := c.supportedVersions(isClient) +func (c *Config) mutualVersion(isClient, isQUIC bool, peerVersions []uint16) (uint16, bool) { + supportedVersions := c.supportedVersions(isClient, isQUIC) for _, v := range supportedVersions { if slices.Contains(peerVersions, v) { return v, true @@ -1381,7 +1386,7 @@ func (chi *ClientHelloInfo) SupportsCertificate(c *Certificate) error { if config == nil { config = &Config{} } - vers, ok := config.mutualVersion(roleServer, chi.SupportedVersions) + vers, ok := config.mutualVersion(roleServer, chi.isQUIC, chi.SupportedVersions) if !ok { return errors.New("no mutually supported protocol versions") } diff --git a/src/crypto/tls/handshake_client.go b/src/crypto/tls/handshake_client.go index 3487e07c01ae04..52bf6087b62955 100644 --- a/src/crypto/tls/handshake_client.go +++ b/src/crypto/tls/handshake_client.go @@ -59,7 +59,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCli return nil, nil, nil, errors.New("tls: NextProtos values too large") } - supportedVersions := config.supportedVersions(roleClient) + supportedVersions := config.supportedVersions(roleClient, c.quic != nil) if len(supportedVersions) == 0 { return nil, nil, nil, errors.New("tls: no supported versions satisfy MinVersion and MaxVersion") } @@ -316,7 +316,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) { // If we are negotiating a protocol version that's lower than what we // support, check for the server downgrade canaries. // See RFC 8446, Section 4.1.3. - maxVers := c.config.maxSupportedVersion(roleClient) + maxVers := c.config.maxSupportedVersion(roleClient, c.quic != nil) tls12Downgrade := string(serverHello.random[24:]) == downgradeCanaryTLS12 tls11Downgrade := string(serverHello.random[24:]) == downgradeCanaryTLS11 if maxVers == VersionTLS13 && c.vers <= VersionTLS12 && (tls12Downgrade || tls11Downgrade) || @@ -508,7 +508,7 @@ func (c *Conn) pickTLSVersion(serverHello *serverHelloMsg) error { peerVersion = serverHello.supportedVersion } - vers, ok := c.config.mutualVersion(roleClient, []uint16{peerVersion}) + vers, ok := c.config.mutualVersion(roleClient, c.quic != nil, []uint16{peerVersion}) if !ok { c.sendAlert(alertProtocolVersion) return fmt.Errorf("tls: server selected unsupported protocol version %x", peerVersion) diff --git a/src/crypto/tls/handshake_server.go b/src/crypto/tls/handshake_server.go index f7179df66317f1..cde70463ef0fa4 100644 --- a/src/crypto/tls/handshake_server.go +++ b/src/crypto/tls/handshake_server.go @@ -188,7 +188,7 @@ func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, *echServer } else if len(clientVersions) == 0 { clientVersions = supportedVersionsFromMax(clientHello.vers) } - c.vers, ok = c.config.mutualVersion(roleServer, clientVersions) + c.vers, ok = c.config.mutualVersion(roleServer, c.quic != nil, clientVersions) if !ok { c.sendAlert(alertProtocolVersion) return nil, nil, fmt.Errorf("tls: client offered only unsupported versions: %x", clientVersions) @@ -235,7 +235,7 @@ func (hs *serverHandshakeState) processClientHello() error { hs.hello.random = make([]byte, 32) serverRandom := hs.hello.random // Downgrade protection canaries. See RFC 8446, Section 4.1.3. - maxVers := c.config.maxSupportedVersion(roleServer) + maxVers := c.config.maxSupportedVersion(roleServer, c.quic != nil) if maxVers >= VersionTLS12 && c.vers < maxVers || testingOnlyForceDowngradeCanary { if c.vers == VersionTLS12 { copy(serverRandom[24:], downgradeCanaryTLS12) @@ -410,7 +410,7 @@ func (hs *serverHandshakeState) pickCipherSuite() error { for _, id := range hs.clientHello.cipherSuites { if id == TLS_FALLBACK_SCSV { // The client is doing a fallback connection. See RFC 7507. - if hs.clientHello.vers < c.config.maxSupportedVersion(roleServer) { + if hs.clientHello.vers < c.config.maxSupportedVersion(roleServer, c.quic != nil) { c.sendAlert(alertInappropriateFallback) return errors.New("tls: client using inappropriate protocol fallback") } @@ -1037,6 +1037,7 @@ func clientHelloInfo(ctx context.Context, c *Conn, clientHello *clientHelloMsg) Conn: conn, HelloRetryRequest: c.didHRR, config: c.config, + isQUIC: c.quic != nil, ctx: ctx, } } diff --git a/src/crypto/tls/handshake_server_tls13.go b/src/crypto/tls/handshake_server_tls13.go index 74b379e766c9ba..3175d74587d83a 100644 --- a/src/crypto/tls/handshake_server_tls13.go +++ b/src/crypto/tls/handshake_server_tls13.go @@ -132,7 +132,7 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { if id == TLS_FALLBACK_SCSV { // Use c.vers instead of max(supported_versions) because an attacker // could defeat this by adding an arbitrary high version otherwise. - if c.vers < c.config.maxSupportedVersion(roleServer) { + if c.vers < c.config.maxSupportedVersion(roleServer, c.quic != nil) { c.sendAlert(alertInappropriateFallback) return errors.New("tls: client using inappropriate protocol fallback") } diff --git a/src/crypto/tls/quic.go b/src/crypto/tls/quic.go index b2e78887a40346..b872d4fe0174a0 100644 --- a/src/crypto/tls/quic.go +++ b/src/crypto/tls/quic.go @@ -185,16 +185,12 @@ type quicState struct { // QUICClient returns a new TLS client side connection using QUICTransport as the // underlying transport. The config cannot be nil. -// -// The config's MinVersion must be at least TLS 1.3. func QUICClient(config *QUICConfig) *QUICConn { return newQUICConn(Client(nil, config.TLSConfig), config) } // QUICServer returns a new TLS server side connection using QUICTransport as the // underlying transport. The config cannot be nil. -// -// The config's MinVersion must be at least TLS 1.3. func QUICServer(config *QUICConfig) *QUICConn { return newQUICConn(Server(nil, config.TLSConfig), config) } @@ -221,9 +217,6 @@ func (q *QUICConn) Start(ctx context.Context) error { return quicError(errors.New("tls: Start called more than once")) } q.conn.quic.started = true - if q.conn.config.MinVersion < VersionTLS13 { - return quicError(errors.New("tls: Config MinVersion must be at least TLS 1.3")) - } go q.conn.HandshakeContext(ctx) if _, ok := <-q.conn.quic.blockedc; !ok { return q.conn.handshakeErr diff --git a/src/crypto/tls/quic_test.go b/src/crypto/tls/quic_test.go index 57d7bf2d9d276f..71ad515a25eec8 100644 --- a/src/crypto/tls/quic_test.go +++ b/src/crypto/tls/quic_test.go @@ -215,6 +215,59 @@ func TestQUICConnection(t *testing.T) { } } +func TestQUICVersions(t *testing.T) { + for _, tc := range []struct { + name string + clientMin uint16 + clientMax uint16 + serverMin uint16 + serverMax uint16 + wantErr bool + }{ + { + name: "defaults", + }, + { + name: "MinVersion TLS 1.2", + clientMin: VersionTLS12, + serverMin: VersionTLS12, + }, + { + name: "MinVersion TLS 1.3", + clientMin: VersionTLS13, + serverMin: VersionTLS13, + }, + { + name: "client MaxVersion TLS 1.2", + clientMax: VersionTLS12, + wantErr: true, + }, + { + name: "server MaxVersion TLS 1.2", + serverMax: VersionTLS12, + wantErr: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + client := testConfig.Clone() + client.MinVersion = tc.clientMin + client.MaxVersion = tc.clientMax + server := testConfig.Clone() + server.MinVersion = tc.serverMin + server.MaxVersion = tc.serverMax + + cli := newTestQUICClient(t, &QUICConfig{TLSConfig: client}) + cli.conn.SetTransportParameters(nil) + srv := newTestQUICServer(t, &QUICConfig{TLSConfig: server}) + srv.conn.SetTransportParameters(nil) + err := runTestQUICConnection(context.Background(), cli, srv, nil) + if tc.wantErr == (err == nil) { + t.Errorf("got err=%v, wantErr=%v", err, tc.wantErr) + } + }) + } +} + func TestQUICSessionResumption(t *testing.T) { clientConfig := &QUICConfig{TLSConfig: testConfigClient.Clone()} clientConfig.TLSConfig.MinVersion = VersionTLS13 From aee6009ba5e1d71948b03ac0458fbc99e3a14ace Mon Sep 17 00:00:00 2001 From: Cherry Mui Date: Mon, 23 Feb 2026 12:38:03 -0500 Subject: [PATCH 24/32] cmd/link: check linkname access to assembly symbols With the introduction of checklinkname, using linkname to reference a symbol from a different symbol is checked, and is permitted only when there is a push linkname. One exception is that linkname reference to an assembly symbol is always permitted, for now. This CL addresses this exception, and let checklinkname handle assembly symbols as well. The rule is similar to Go functions: linkname access is permitted if there is a push linkname annotation. One trickiness is that the assembly function is defined in assembly, whereas the annotation is in Go. They are different objects. To connect the two, we apply the annotation to the ABI wrapper, and let the linker to check the ABI wrapper symbol. A further complication is that on non-regABI platforms there is no ABI wrapper. In this case, we just allow the access for now. As most popular platforms are register ABI platforms, this shouldn't leave too big a hole. To allow one package pushes assembly functions to another, like, package a defines an assembly symbol b.F, it is permitted to reference directly from the target package (b in the example) based on the name. This change also makes it handle linkname references to ABI wrappers more strict. Previously it was always permitted. Now we treat the ABI wrapper the same as the underlying symbol. With this, we can migrate runtime.newcoro to linknamestd and remove it from the blocklist, and the coro_var and coro_asm test cases in cmd/link.TestCheckLinkname still pass. Change-Id: I6c03467d3eaaa536663e52ce289e3c1c23079aa6 Reviewed-on: https://go-review.googlesource.com/c/go/+/761481 Reviewed-by: David Chase LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- src/cmd/compile/internal/ssagen/abi.go | 4 +++ src/cmd/internal/obj/objfile.go | 4 +-- src/cmd/internal/obj/x86/asm6.go | 9 ++++++ src/cmd/link/internal/loader/loader.go | 32 ++++++++++++++----- src/cmd/link/link_test.go | 5 +++ src/cmd/link/testdata/linkname/badlinkname.go | 9 +++++- src/cmd/link/testdata/linkname/systemstack.go | 19 +++++++++++ src/iter/iter.go | 2 +- src/runtime/coro.go | 2 +- 9 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 src/cmd/link/testdata/linkname/systemstack.go diff --git a/src/cmd/compile/internal/ssagen/abi.go b/src/cmd/compile/internal/ssagen/abi.go index 011c0d12f43262..724b9e94000916 100644 --- a/src/cmd/compile/internal/ssagen/abi.go +++ b/src/cmd/compile/internal/ssagen/abi.go @@ -280,6 +280,10 @@ func makeABIWrapper(f *ir.Func, wrapperABI obj.ABI) { fn.SetABIWrapper(true) fn.SetDupok(true) + // Propagate linkname attribute. + fn.LinksymABI(fn.ABI).Set(obj.AttrLinkname, f.Linksym().IsLinkname()) + fn.LinksymABI(fn.ABI).Set(obj.AttrLinknameStd, f.Linksym().IsLinknameStd()) + // ABI0-to-ABIInternal wrappers will be mainly loading params from // stack into registers (and/or storing stack locations back to // registers after the wrapped call); in most cases they won't diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go index fc928f3d7016ea..6bd5bca04462b4 100644 --- a/src/cmd/internal/obj/objfile.go +++ b/src/cmd/internal/obj/objfile.go @@ -371,9 +371,7 @@ func (w *writer) Sym(s *LSym) { if s.IsPkgInit() { flag2 |= goobj.SymFlagPkgInit } - if s.IsLinkname() || (w.ctxt.IsAsm && name != "") || name == "main.main" { - // Assembly reference is treated the same as linkname, - // but not for unnamed (aux) symbols. + if s.IsLinkname() || name == "main.main" { // The runtime linknames main.main. flag2 |= goobj.SymFlagLinkname } diff --git a/src/cmd/internal/obj/x86/asm6.go b/src/cmd/internal/obj/x86/asm6.go index 5aff15c5b3995b..89b64f4eedd8ea 100644 --- a/src/cmd/internal/obj/x86/asm6.go +++ b/src/cmd/internal/obj/x86/asm6.go @@ -2265,7 +2265,16 @@ func instinit(ctxt *obj.Link) { switch ctxt.Headtype { case objabi.Hplan9: + // _privates is a special symbol on Plan 9 that + // points to per–process private data (like TLS area). + // See https://9p.io/magic/man2html/2/exec . + // The assembler inserts a reference to this symbol + // for accessing the G. Mark it as linkname so it is + // allowed to access from anywhere. (Would be nice to + // mark it external, but we don't have a mechanism for + // that.) plan9privates = ctxt.Lookup("_privates") + plan9privates.Set(obj.AttrLinkname, true) } for i := range avxOptab { diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go index cd8100baf21d71..ef48467a04b608 100644 --- a/src/cmd/link/internal/loader/loader.go +++ b/src/cmd/link/internal/loader/loader.go @@ -15,6 +15,7 @@ import ( "debug/elf" "fmt" "internal/abi" + "internal/buildcfg" "io" "iter" "log" @@ -2368,7 +2369,7 @@ func loadObjRefs(l *Loader, r *oReader, arch *sys.Arch) { v := abiToVer(osym.ABI(), r.version) gi := l.LookupOrCreateSym(name, v) r.syms[ndef+i] = gi - if osym.IsLinkname() || osym.IsLinknameStd() { + if osym.IsLinkname() || osym.IsLinknameStd() || r.FromAssembly() { // Check if a linkname reference is allowed. // Only check references (pull), not definitions (push), // so push is always allowed. @@ -2426,8 +2427,6 @@ func abiToVer(abi uint16, localSymVersion int) int { // If a name is in this map, it is allowed only in listed packages, // even if it has a linknamed definition. var blockedLinknames = map[string][]string{ - // coroutines - "runtime.newcoro": {"iter"}, // fips info "go:fipsinfo": {"crypto/internal/fips140/check"}, // New internal linknames in Go 1.24 @@ -2553,6 +2552,27 @@ func (l *Loader) checkLinkname(refpkg *oReader, name string, s Sym) { return } osym := r.Sym(li) + if r.FromAssembly() && !osym.IsLinknameStd() && !osym.IsLinkname() { + if strings.HasPrefix(name, pkg) { + // Allow if by name it is pushed to pkg, e.g. in package a, + // an assembly function is defined as b.F, then it is allowed + // to be used in package b. + return + } + // For an assembly symbol, check if there is a linkname applied + // to its ABI wrapper. + if !buildcfg.Experiment.RegabiWrappers { + // If ABI wrapper is not enabled (i.e. non-regabi platform), + // permit for now, as there is no good way to check. + return + } + otherABI := 1 - abiToVer(osym.ABI(), r.version) // for now, we only have ABI 0 and 1 + w := l.Lookup(name, otherABI) // TODO: use an aux symbol instead of name lookup? + if w != 0 { + r, li = l.toLocal(w) + osym = r.Sym(li) + } + } if osym.IsLinknameStd() { // It is pushed with linknamestd. Allow only pulls from the // standard library. @@ -2560,12 +2580,8 @@ func (l *Loader) checkLinkname(refpkg *oReader, name string, s Sym) { return } } - if osym.IsLinkname() || osym.ABIWrapper() { + if osym.IsLinkname() { // Allow if the def has a linkname (push). - // ABI wrapper usually wraps an assembly symbol, a linknamed symbol, - // or an external symbol, or provide access of a Go symbol to assembly. - // For now, allow ABI wrappers. - // TODO: check the wrapped symbol? return } error() diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go index 807cf15f8c6f20..e7c406c4ad1eb8 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -12,6 +12,7 @@ import ( "debug/pe" "errors" "internal/abi" + "internal/buildcfg" "internal/platform" "internal/testenv" "internal/xcoff" @@ -1715,6 +1716,10 @@ func TestCheckLinkname(t *testing.T) { {"coro2.go", false}, // pull linkname of a builtin symbol is not ok {"builtin.go", false}, + // using a linkname to reference a runtime assembly + // function is not ok (except on non-regabi platforms) + {"systemstack.go", !buildcfg.Experiment.RegabiWrappers}, + // misc {"addmoduledata.go", false}, {"freegc.go", false}, // legacy bad linkname is ok, for now diff --git a/src/cmd/link/testdata/linkname/badlinkname.go b/src/cmd/link/testdata/linkname/badlinkname.go index fb9f9c6b7d164e..29e645126d0ba5 100644 --- a/src/cmd/link/testdata/linkname/badlinkname.go +++ b/src/cmd/link/testdata/linkname/badlinkname.go @@ -3,7 +3,8 @@ // license that can be found in the LICENSE file. // Existing pull linknames in the wild are allowed _for now_, -// for legacy reason. Test a function and a method. +// for legacy reason. Test a function, a method, and an +// assembly symbol. // NOTE: this may not be allowed in the future. Don't do this! package main @@ -19,6 +20,12 @@ func noescape(unsafe.Pointer) unsafe.Pointer //go:linkname rtype_String reflect.(*rtype).String func rtype_String(unsafe.Pointer) string +//go:linkname memmove runtime.memmove +func memmove(to, from unsafe.Pointer, n uintptr) + +var n uintptr // use a global to prevent compiler optimize out memmove call + func main() { println(rtype_String(noescape(nil))) + memmove(nil, nil, n) } diff --git a/src/cmd/link/testdata/linkname/systemstack.go b/src/cmd/link/testdata/linkname/systemstack.go new file mode 100644 index 00000000000000..7fc575d6eb7679 --- /dev/null +++ b/src/cmd/link/testdata/linkname/systemstack.go @@ -0,0 +1,19 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Linkname systemstack is not allowed, even if it is +// defined in assembly. + +package main + +import _ "unsafe" + +func f() {} + +func main() { + systemstack(f) +} + +//go:linkname systemstack runtime.systemstack +func systemstack(func()) diff --git a/src/iter/iter.go b/src/iter/iter.go index 57d19d047bcf2c..171ae4f30fb642 100644 --- a/src/iter/iter.go +++ b/src/iter/iter.go @@ -230,7 +230,7 @@ type Seq2[K, V any] func(yield func(K, V) bool) type coro struct{} -//go:linkname newcoro runtime.newcoro +//go:linknamestd newcoro runtime.newcoro func newcoro(func(*coro)) *coro //go:linknamestd coroswitch runtime.coroswitch diff --git a/src/runtime/coro.go b/src/runtime/coro.go index 72c58b79647275..901e9391bfe640 100644 --- a/src/runtime/coro.go +++ b/src/runtime/coro.go @@ -34,7 +34,7 @@ type coro struct { lockedInt uint32 // mp's internal lockOSThread counter at coro creation time. } -//go:linkname newcoro +//go:linknamestd newcoro // newcoro creates a new coro containing a // goroutine blocked waiting to run f From c8b14e157f5228c9a4f513ac726d8cbb62640622 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Thu, 5 Jun 2025 18:09:08 -0700 Subject: [PATCH 25/32] math/big: only use pool for large allocations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The native allocator seems faster for small things. Only start accessing the pool if we need something large. Currently "large" is more than 4 words. It seems a reasonable threshold, although I didn't do much experimentation to pick a number. Fixes #73999 1.24.2 to tip: goos: darwin goarch: arm64 pkg: github.com/dustin/go-humanize cpu: Apple M2 Ultra │ base │ pre │ │ sec/op │ sec/op vs base │ ParseBigBytes-24 625.0n ± 1% 665.8n ± 0% +6.53% (p=0.000 n=10) 1.24.2 to tip+this CL: goos: darwin goarch: arm64 pkg: github.com/dustin/go-humanize cpu: Apple M2 Ultra │ base │ post │ │ sec/op │ sec/op vs base │ ParseBigBytes-24 625.0n ± 1% 626.8n ± 0% ~ (p=0.470 n=10) Change-Id: Ic071e6d82d4aa4a0d3a6ec6e026f513c83cb0b37 Reviewed-on: https://go-review.googlesource.com/c/go/+/679475 Reviewed-by: Dmitri Shuralyov Auto-Submit: Keith Randall Reviewed-by: Cuong Manh Le LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Keith Randall --- src/math/big/nat.go | 85 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 17 deletions(-) diff --git a/src/math/big/nat.go b/src/math/big/nat.go index 43e36d309389f5..39981c28e07132 100644 --- a/src/math/big/nat.go +++ b/src/math/big/nat.go @@ -265,33 +265,27 @@ func (z nat) mulRange(stk *stack, a, b uint64) nat { return z.mul(stk, nat(nil).mulRange(stk, a, m), nat(nil).mulRange(stk, m+1, b)) } -// A stack provides temporary storage for complex calculations +// A stackInner provides temporary storage for complex calculations // such as multiplication and division. -// The stack is a simple slice of words, extended as needed -// to hold all the temporary storage for a calculation. -// In general, if a function takes a *stack, it expects a non-nil *stack. -// However, certain functions may allow passing a nil *stack instead, -// so that they can handle trivial stack-free cases without forcing the -// caller to obtain and free a stack that will be unused. These functions -// document that they accept a nil *stack in their doc comments. -type stack struct { +// It should only be used by [stack], below. +type stackInner struct { w []Word } -var stackPool sync.Pool +var stackPool sync.Pool // pool of *stackInner // getStack returns a temporary stack. // The caller must call [stack.free] to give up use of the stack when finished. -func getStack() *stack { - s, _ := stackPool.Get().(*stack) +func getStackInner() *stackInner { + s, _ := stackPool.Get().(*stackInner) if s == nil { - s = new(stack) + s = new(stackInner) } return s } // free returns the stack for use by another calculation. -func (s *stack) free() { +func (s *stackInner) free() { s.w = s.w[:0] stackPool.Put(s) } @@ -299,7 +293,7 @@ func (s *stack) free() { // save returns the current stack pointer. // A future call to restore with the same value // frees any temporaries allocated on the stack after the call to save. -func (s *stack) save() int { +func (s *stackInner) save() int { return len(s.w) } @@ -310,12 +304,12 @@ func (s *stack) save() int { // // which makes sure to pop any temporaries allocated in the current function // from the stack before returning. -func (s *stack) restore(n int) { +func (s *stackInner) restore(n int) { s.w = s.w[:n] } // nat returns a nat of n words, allocated on the stack. -func (s *stack) nat(n int) nat { +func (s *stackInner) nat(n int) nat { nr := (n + 3) &^ 3 // round up to multiple of 4 off := len(s.w) s.w = slices.Grow(s.w, nr) @@ -327,6 +321,63 @@ func (s *stack) nat(n int) nat { return x } +// A stack provides temporary storage for complex calculations +// such as multiplication and division. +// In general, if a function takes a *stack, it expects a non-nil *stack. +// However, certain functions may allow passing a nil *stack instead, +// so that they can handle trivial stack-free cases without forcing the +// caller to obtain and free a stack that will be unused. These functions +// document that they accept a nil *stack in their doc comments. +type stack struct { + si *stackInner +} + +func getStack() *stack { + return &stack{} +} +func (s *stack) free() { + si := s.si + if si != nil { + si.free() + } +} +func (s *stack) save() int { + si := s.si + if si == nil { + return 0 + } + return si.save() +} +func (s *stack) restore(n int) { + si := s.si + if si == nil { + return + } + si.restore(n) +} +func (s *stack) nat(n int) nat { + si := s.si + if si == nil { + if n <= 4 { + // For small allocations, just ask the allocator. + // It isn't worth pooling these allocations. + // See issue 73999. + r := slices.Grow(nat(nil), n) + r = r[:n] + if n > 0 { + r[0] = 0xabcdef + } + return r + } + si, _ = stackPool.Get().(*stackInner) + if si == nil { + si = new(stackInner) + } + s.si = si + } + return si.nat(n) +} + // bitLen returns the length of x in bits. // Unlike most methods, it works even if x is not normalized. func (x nat) bitLen() int { From 1dd2bef375390973b025e50cef6dcbafb24ca223 Mon Sep 17 00:00:00 2001 From: Neal Patel Date: Tue, 28 Apr 2026 11:21:40 -0400 Subject: [PATCH 26/32] runtime/secret: fix cgo crashes inside of secret.Do Fixes #78999 Change-Id: I9fba1fd771f06fc57de1ecb70d9f9cb8bc8078fe Reviewed-on: https://go-review.googlesource.com/c/go/+/771640 Reviewed-by: Daniel Morsing LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Dmitri Shuralyov --- src/runtime/asm_amd64.s | 5 +++ src/runtime/crash_cgo_test.go | 22 +++++++++++++ src/runtime/testdata/testprogcgo/secretcgo.go | 31 +++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 src/runtime/testdata/testprogcgo/secretcgo.go diff --git a/src/runtime/asm_amd64.s b/src/runtime/asm_amd64.s index 96b80de1f1dd52..559fe25adce6e8 100644 --- a/src/runtime/asm_amd64.s +++ b/src/runtime/asm_amd64.s @@ -942,6 +942,11 @@ TEXT ·asmcgocall(SB),NOSPLIT,$0-20 JEQ nosecret CALL ·secretEraseRegisters(SB) + get_tls(CX) + MOVQ g(CX), DI + MOVQ g_m(DI), R8 + MOVQ m_g0(R8), SI + nosecret: #endif MOVQ fn+0(FP), AX diff --git a/src/runtime/crash_cgo_test.go b/src/runtime/crash_cgo_test.go index fb95d368c23ee3..d4b7421ac09a65 100644 --- a/src/runtime/crash_cgo_test.go +++ b/src/runtime/crash_cgo_test.go @@ -15,6 +15,7 @@ import ( "internal/testenv" "os" "os/exec" + "path/filepath" "runtime" "strconv" "strings" @@ -118,6 +119,27 @@ func TestCgoCallbackX15(t *testing.T) { } } +func TestSecretCgo(t *testing.T) { + t.Parallel() + testenv.MustHaveGoBuild(t) + testenv.MustHaveCGO(t) + + exe := filepath.Join(t.TempDir(), "secretcgo.exe") + cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", exe) + cmd.Dir = "testdata/testprogcgo" + cmd = testenv.CleanCmdEnv(cmd) + cmd.Env = append(cmd.Env, "GOEXPERIMENT=runtimesecret") + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("building testprogcgo with runtimesecret: %v\n%s", err, out) + } + + got := runBuiltTestProg(t, exe, "SecretCgo") + if want := "OK\n"; got != want { + t.Fatalf("expected %q, got:\n%s", want, got) + } +} + func TestCgoExternalThreadPanic(t *testing.T) { t.Parallel() if runtime.GOOS == "plan9" { diff --git a/src/runtime/testdata/testprogcgo/secretcgo.go b/src/runtime/testdata/testprogcgo/secretcgo.go new file mode 100644 index 00000000000000..a4fec846294b0a --- /dev/null +++ b/src/runtime/testdata/testprogcgo/secretcgo.go @@ -0,0 +1,31 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build goexperiment.runtimesecret + +package main + +/* +static int cAdd(int a, int b) { return a + b; } +*/ +import "C" + +import ( + "fmt" + "runtime/secret" +) + +func init() { + register("SecretCgo", SecretCgo) +} + +func SecretCgo() { + secret.Do(func() { + r := C.cAdd(1, 2) + if r != 3 { + panic(fmt.Sprintf("got %d, want 3", r)) + } + }) + fmt.Println("OK") +} From 24e654197a941864dd6696a2b9ff2f515f1e40dd Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Tue, 19 May 2026 12:47:44 -0400 Subject: [PATCH 27/32] runtime: add benchmarks for allocating slices of pointers This is meant to help evaluate CL 778981 For #79286 Change-Id: I361672582b16cce7746b659727d851106a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/779602 TryBot-Bypass: Michael Matloob Reviewed-by: Michael Pratt Reviewed-by: Michael Matloob --- src/runtime/_mkmalloc/mkmalloc.go | 13 ++ src/runtime/malloc_bench_generated_test.go | 210 +++++++++++++++++++++ src/runtime/malloc_stubs_test.go | 7 + 3 files changed, 230 insertions(+) diff --git a/src/runtime/_mkmalloc/mkmalloc.go b/src/runtime/_mkmalloc/mkmalloc.go index 0a008388049bb0..3ecae4d92cdcea 100644 --- a/src/runtime/_mkmalloc/mkmalloc.go +++ b/src/runtime/_mkmalloc/mkmalloc.go @@ -889,6 +889,11 @@ func benchmarkConfig(classes []class, sizeToSizeClass []uint8) generatorConfig { {foldCondition, "noscan_", str(false)}, }, }) + config.specs = append(config.specs, spec{ + templateFunc: "benchmarkScanSliceStub", + name: fmt.Sprintf("benchmarkMallocgcScanSlice%d", elemsize), + ops: []op{{subBasicLit, "size_", str(elemsize)}}, + }) } for size := 1; size < tinySize; size++ { @@ -920,6 +925,14 @@ func generateTopBenchmark(classes []class, sizeToSizeClass []uint8) string { for sc := uint8(1); sc <= scMax; sc++ { elemsize := classes[sc].size bench += fmt.Sprintf(`b.Run("size=%d", benchmarkMallocgcScan%d)`, elemsize, elemsize) + "\n" + + } + bench += `}) + b.Run("scan=scanslice", func(b *testing.B) { +` + for sc := uint8(1); sc <= scMax; sc++ { + elemsize := classes[sc].size + bench += fmt.Sprintf(`b.Run("size=%d", benchmarkMallocgcScanSlice%d)`, elemsize, elemsize) + "\n" } bench += `}) }` diff --git a/src/runtime/malloc_bench_generated_test.go b/src/runtime/malloc_bench_generated_test.go index 34755f25feef53..f0a7473b6d6003 100644 --- a/src/runtime/malloc_bench_generated_test.go +++ b/src/runtime/malloc_bench_generated_test.go @@ -49,6 +49,13 @@ func benchmarkMallocgcScan8(b *testing.B) { }) } +func benchmarkMallocgcScanSlice8(b *testing.B) { + const size = 8 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan16(b *testing.B) { const size = 16 b.Run("kind=new", func(b *testing.B) { @@ -89,6 +96,13 @@ func benchmarkMallocgcScan16(b *testing.B) { }) } +func benchmarkMallocgcScanSlice16(b *testing.B) { + const size = 16 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan24(b *testing.B) { const size = 24 b.Run("kind=new", func(b *testing.B) { @@ -129,6 +143,13 @@ func benchmarkMallocgcScan24(b *testing.B) { }) } +func benchmarkMallocgcScanSlice24(b *testing.B) { + const size = 24 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan32(b *testing.B) { const size = 32 b.Run("kind=new", func(b *testing.B) { @@ -169,6 +190,13 @@ func benchmarkMallocgcScan32(b *testing.B) { }) } +func benchmarkMallocgcScanSlice32(b *testing.B) { + const size = 32 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan48(b *testing.B) { const size = 48 b.Run("kind=new", func(b *testing.B) { @@ -209,6 +237,13 @@ func benchmarkMallocgcScan48(b *testing.B) { }) } +func benchmarkMallocgcScanSlice48(b *testing.B) { + const size = 48 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan64(b *testing.B) { const size = 64 b.Run("kind=new", func(b *testing.B) { @@ -249,6 +284,13 @@ func benchmarkMallocgcScan64(b *testing.B) { }) } +func benchmarkMallocgcScanSlice64(b *testing.B) { + const size = 64 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan80(b *testing.B) { const size = 80 b.Run("kind=new", func(b *testing.B) { @@ -289,6 +331,13 @@ func benchmarkMallocgcScan80(b *testing.B) { }) } +func benchmarkMallocgcScanSlice80(b *testing.B) { + const size = 80 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan96(b *testing.B) { const size = 96 b.Run("kind=new", func(b *testing.B) { @@ -329,6 +378,13 @@ func benchmarkMallocgcScan96(b *testing.B) { }) } +func benchmarkMallocgcScanSlice96(b *testing.B) { + const size = 96 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan112(b *testing.B) { const size = 112 b.Run("kind=new", func(b *testing.B) { @@ -369,6 +425,13 @@ func benchmarkMallocgcScan112(b *testing.B) { }) } +func benchmarkMallocgcScanSlice112(b *testing.B) { + const size = 112 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan128(b *testing.B) { const size = 128 b.Run("kind=new", func(b *testing.B) { @@ -409,6 +472,13 @@ func benchmarkMallocgcScan128(b *testing.B) { }) } +func benchmarkMallocgcScanSlice128(b *testing.B) { + const size = 128 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan144(b *testing.B) { const size = 144 b.Run("kind=new", func(b *testing.B) { @@ -449,6 +519,13 @@ func benchmarkMallocgcScan144(b *testing.B) { }) } +func benchmarkMallocgcScanSlice144(b *testing.B) { + const size = 144 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan160(b *testing.B) { const size = 160 b.Run("kind=new", func(b *testing.B) { @@ -489,6 +566,13 @@ func benchmarkMallocgcScan160(b *testing.B) { }) } +func benchmarkMallocgcScanSlice160(b *testing.B) { + const size = 160 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan176(b *testing.B) { const size = 176 b.Run("kind=new", func(b *testing.B) { @@ -529,6 +613,13 @@ func benchmarkMallocgcScan176(b *testing.B) { }) } +func benchmarkMallocgcScanSlice176(b *testing.B) { + const size = 176 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan192(b *testing.B) { const size = 192 b.Run("kind=new", func(b *testing.B) { @@ -569,6 +660,13 @@ func benchmarkMallocgcScan192(b *testing.B) { }) } +func benchmarkMallocgcScanSlice192(b *testing.B) { + const size = 192 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan208(b *testing.B) { const size = 208 b.Run("kind=new", func(b *testing.B) { @@ -609,6 +707,13 @@ func benchmarkMallocgcScan208(b *testing.B) { }) } +func benchmarkMallocgcScanSlice208(b *testing.B) { + const size = 208 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan224(b *testing.B) { const size = 224 b.Run("kind=new", func(b *testing.B) { @@ -649,6 +754,13 @@ func benchmarkMallocgcScan224(b *testing.B) { }) } +func benchmarkMallocgcScanSlice224(b *testing.B) { + const size = 224 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan240(b *testing.B) { const size = 240 b.Run("kind=new", func(b *testing.B) { @@ -689,6 +801,13 @@ func benchmarkMallocgcScan240(b *testing.B) { }) } +func benchmarkMallocgcScanSlice240(b *testing.B) { + const size = 240 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan256(b *testing.B) { const size = 256 b.Run("kind=new", func(b *testing.B) { @@ -729,6 +848,13 @@ func benchmarkMallocgcScan256(b *testing.B) { }) } +func benchmarkMallocgcScanSlice256(b *testing.B) { + const size = 256 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan288(b *testing.B) { const size = 288 b.Run("kind=new", func(b *testing.B) { @@ -769,6 +895,13 @@ func benchmarkMallocgcScan288(b *testing.B) { }) } +func benchmarkMallocgcScanSlice288(b *testing.B) { + const size = 288 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan320(b *testing.B) { const size = 320 b.Run("kind=new", func(b *testing.B) { @@ -809,6 +942,13 @@ func benchmarkMallocgcScan320(b *testing.B) { }) } +func benchmarkMallocgcScanSlice320(b *testing.B) { + const size = 320 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan352(b *testing.B) { const size = 352 b.Run("kind=new", func(b *testing.B) { @@ -849,6 +989,13 @@ func benchmarkMallocgcScan352(b *testing.B) { }) } +func benchmarkMallocgcScanSlice352(b *testing.B) { + const size = 352 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan384(b *testing.B) { const size = 384 b.Run("kind=new", func(b *testing.B) { @@ -889,6 +1036,13 @@ func benchmarkMallocgcScan384(b *testing.B) { }) } +func benchmarkMallocgcScanSlice384(b *testing.B) { + const size = 384 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan416(b *testing.B) { const size = 416 b.Run("kind=new", func(b *testing.B) { @@ -929,6 +1083,13 @@ func benchmarkMallocgcScan416(b *testing.B) { }) } +func benchmarkMallocgcScanSlice416(b *testing.B) { + const size = 416 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan448(b *testing.B) { const size = 448 b.Run("kind=new", func(b *testing.B) { @@ -969,6 +1130,13 @@ func benchmarkMallocgcScan448(b *testing.B) { }) } +func benchmarkMallocgcScanSlice448(b *testing.B) { + const size = 448 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan480(b *testing.B) { const size = 480 b.Run("kind=new", func(b *testing.B) { @@ -1009,6 +1177,13 @@ func benchmarkMallocgcScan480(b *testing.B) { }) } +func benchmarkMallocgcScanSlice480(b *testing.B) { + const size = 480 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcNoscan512(b *testing.B) { const size = 512 b.Run("kind=new", func(b *testing.B) { @@ -1049,6 +1224,13 @@ func benchmarkMallocgcScan512(b *testing.B) { }) } +func benchmarkMallocgcScanSlice512(b *testing.B) { + const size = 512 + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} + func benchmarkMallocgcTiny1(b *testing.B) { const size = 1 type s struct { @@ -1390,4 +1572,32 @@ func BenchmarkMallocgc(b *testing.B) { b.Run("size=480", benchmarkMallocgcScan480) b.Run("size=512", benchmarkMallocgcScan512) }) + b.Run("scan=scanslice", func(b *testing.B) { + b.Run("size=8", benchmarkMallocgcScanSlice8) + b.Run("size=16", benchmarkMallocgcScanSlice16) + b.Run("size=24", benchmarkMallocgcScanSlice24) + b.Run("size=32", benchmarkMallocgcScanSlice32) + b.Run("size=48", benchmarkMallocgcScanSlice48) + b.Run("size=64", benchmarkMallocgcScanSlice64) + b.Run("size=80", benchmarkMallocgcScanSlice80) + b.Run("size=96", benchmarkMallocgcScanSlice96) + b.Run("size=112", benchmarkMallocgcScanSlice112) + b.Run("size=128", benchmarkMallocgcScanSlice128) + b.Run("size=144", benchmarkMallocgcScanSlice144) + b.Run("size=160", benchmarkMallocgcScanSlice160) + b.Run("size=176", benchmarkMallocgcScanSlice176) + b.Run("size=192", benchmarkMallocgcScanSlice192) + b.Run("size=208", benchmarkMallocgcScanSlice208) + b.Run("size=224", benchmarkMallocgcScanSlice224) + b.Run("size=240", benchmarkMallocgcScanSlice240) + b.Run("size=256", benchmarkMallocgcScanSlice256) + b.Run("size=288", benchmarkMallocgcScanSlice288) + b.Run("size=320", benchmarkMallocgcScanSlice320) + b.Run("size=352", benchmarkMallocgcScanSlice352) + b.Run("size=384", benchmarkMallocgcScanSlice384) + b.Run("size=416", benchmarkMallocgcScanSlice416) + b.Run("size=448", benchmarkMallocgcScanSlice448) + b.Run("size=480", benchmarkMallocgcScanSlice480) + b.Run("size=512", benchmarkMallocgcScanSlice512) + }) } diff --git a/src/runtime/malloc_stubs_test.go b/src/runtime/malloc_stubs_test.go index 10e34ade9f24da..7b536977de5386 100644 --- a/src/runtime/malloc_stubs_test.go +++ b/src/runtime/malloc_stubs_test.go @@ -57,3 +57,10 @@ func benchmarkStub(b *testing.B) { } }) } + +func benchmarkScanSliceStub(b *testing.B) { + const size = size_ + for b.Loop() { + runtime.Escape(make([]*uint64, size/8)) + } +} From 063f8b07c1afd9d775c2d1f29ff62d4e7072794c Mon Sep 17 00:00:00 2001 From: Neal Patel Date: Tue, 19 May 2026 17:09:05 -0400 Subject: [PATCH 28/32] crypto/tls: fix broken quic_test.go Change-Id: I43f805d88553f858866fe82178b3dea22fe74a43 Reviewed-on: https://go-review.googlesource.com/c/go/+/779622 Reviewed-by: Neal Patel Reviewed-by: Nicholas Husin Auto-Submit: Neal Patel Reviewed-by: Nicholas Husin LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com --- src/crypto/tls/quic_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto/tls/quic_test.go b/src/crypto/tls/quic_test.go index 71ad515a25eec8..29dc05448fb1c5 100644 --- a/src/crypto/tls/quic_test.go +++ b/src/crypto/tls/quic_test.go @@ -249,10 +249,10 @@ func TestQUICVersions(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - client := testConfig.Clone() + client := testConfigClient.Clone() client.MinVersion = tc.clientMin client.MaxVersion = tc.clientMax - server := testConfig.Clone() + server := testConfigServer.Clone() server.MinVersion = tc.serverMin server.MaxVersion = tc.serverMax From 8ddf0031cfb2ab06c83efa80873ba84869ec515a Mon Sep 17 00:00:00 2001 From: Cherry Mui Date: Thu, 30 Apr 2026 13:01:28 -0400 Subject: [PATCH 29/32] cmd/compile: disallow nointerface method satisfying type constraint Type constraint satisfaction is interface satisfaction. If a type does not satisfy a regular interface, it should also not satisfy the same interface as a type constraint. Otherwise, if the type satisfies the type constraint, one can use that to construct a (dynamic) interface value with a type that doesn't actually satisfy the interface. The go:nointerface directive tells the compiler that the method does not satisfy an interface. Therefore it should also not satisfy a type constraint. Fixes #74626. Change-Id: I7c64c76044a665755e4e74035085daff42447f9e Reviewed-on: https://go-review.googlesource.com/c/go/+/772620 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Robert Griesemer --- src/cmd/compile/internal/noder/noder.go | 4 ++++ src/cmd/compile/internal/types2/decl.go | 6 ++++++ src/cmd/compile/internal/types2/lookup.go | 8 ++++++++ src/cmd/compile/internal/types2/object.go | 5 +++-- src/go/types/decl.go | 3 +++ src/go/types/lookup.go | 8 ++++++++ src/go/types/object.go | 5 +++-- test/fixedbugs/issue74626.go | 25 +++++++++++++++++++++++ 8 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 test/fixedbugs/issue74626.go diff --git a/src/cmd/compile/internal/noder/noder.go b/src/cmd/compile/internal/noder/noder.go index 4e48126e82ead2..5d33072690f075 100644 --- a/src/cmd/compile/internal/noder/noder.go +++ b/src/cmd/compile/internal/noder/noder.go @@ -175,6 +175,10 @@ type pragmas struct { WasmExport *WasmExport } +func (p *pragmas) Nointerface() bool { + return p.Flag&ir.Nointerface != 0 +} + // WasmImport stores metadata associated with the //go:wasmimport pragma type WasmImport struct { Pos syntax.Pos diff --git a/src/cmd/compile/internal/types2/decl.go b/src/cmd/compile/internal/types2/decl.go index 9644253aebc80a..2028edff0d988d 100644 --- a/src/cmd/compile/internal/types2/decl.go +++ b/src/cmd/compile/internal/types2/decl.go @@ -684,6 +684,12 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) { fdecl := decl.fdecl check.funcType(sig, fdecl.Recv, fdecl.TParamList, fdecl.Type) + if fdecl.Pragma != nil { + if p, ok := fdecl.Pragma.(interface{ Nointerface() bool }); ok && p.Nointerface() { + obj.nointerface = true + } + } + // Set the scope's extent to the complete "func (...) { ... }" // so that Scope.Innermost works correctly. sig.scope.pos = fdecl.Pos() diff --git a/src/cmd/compile/internal/types2/lookup.go b/src/cmd/compile/internal/types2/lookup.go index 457352fc88ba0c..107101fe1ca038 100644 --- a/src/cmd/compile/internal/types2/lookup.go +++ b/src/cmd/compile/internal/types2/lookup.go @@ -390,6 +390,7 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y ambigSel ptrRecv field + nointerface ) state := ok @@ -453,6 +454,11 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y check.objDecl(f) } + if f.nointerface { + state = nointerface + break + } + if !equivalent(f.typ, m.typ) { state = wrongSig break @@ -512,6 +518,8 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y *cause = check.sprintf("(method %s has pointer receiver)", m.Name()) case field: *cause = check.sprintf("(%s.%s is a field, not a method)", V, m.Name()) + case nointerface: + *cause = check.sprintf("(%s method is marked 'nointerface')", m.Name()) default: panic("unreachable") } diff --git a/src/cmd/compile/internal/types2/object.go b/src/cmd/compile/internal/types2/object.go index 87d8d70a2a4da5..a3ab50f7cad406 100644 --- a/src/cmd/compile/internal/types2/object.go +++ b/src/cmd/compile/internal/types2/object.go @@ -396,8 +396,9 @@ func (*Var) isDependency() {} // a variable may be a dependency of an initializa // An abstract method may belong to many interfaces due to embedding. type Func struct { object - hasPtrRecv_ bool // only valid for methods that don't have a type yet; use hasPtrRecv() to read origin *Func // if non-nil, the Func from which this one was instantiated + hasPtrRecv_ bool // only valid for methods that don't have a type yet; use hasPtrRecv() to read + nointerface bool } // NewFunc returns a new function with the given signature, representing @@ -412,7 +413,7 @@ func NewFunc(pos syntax.Pos, pkg *Package, name string, sig *Signature) *Func { // as this would violate object.{Type,color} invariants. // TODO(adonovan): propose to disallow NewFunc with nil *Signature. } - return &Func{object{nil, pos, pkg, name, typ, 0, nopos}, false, nil} + return &Func{object{nil, pos, pkg, name, typ, 0, nopos}, nil, false, false} } // Signature returns the signature (type) of the function or method. diff --git a/src/go/types/decl.go b/src/go/types/decl.go index 34694262cfda4a..fc32f1d6d917df 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -771,6 +771,9 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) { fdecl := decl.fdecl check.funcType(sig, fdecl.Recv, fdecl.Type) + // types2 handles go:nointerface pragma here by setting obj.nointerface. + // go/types currently doesn't handle pragmas. + // Set the scope's extent to the complete "func (...) { ... }" // so that Scope.Innermost works correctly. sig.scope.pos = fdecl.Pos() diff --git a/src/go/types/lookup.go b/src/go/types/lookup.go index bacfeac56d7df2..0d056e7c70e9b3 100644 --- a/src/go/types/lookup.go +++ b/src/go/types/lookup.go @@ -393,6 +393,7 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y ambigSel ptrRecv field + nointerface ) state := ok @@ -456,6 +457,11 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y check.objDecl(f) } + if f.nointerface { + state = nointerface + break + } + if !equivalent(f.typ, m.typ) { state = wrongSig break @@ -515,6 +521,8 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y *cause = check.sprintf("(method %s has pointer receiver)", m.Name()) case field: *cause = check.sprintf("(%s.%s is a field, not a method)", V, m.Name()) + case nointerface: + *cause = check.sprintf("(%s method is marked 'nointerface')", m.Name()) default: panic("unreachable") } diff --git a/src/go/types/object.go b/src/go/types/object.go index 46cc830bce3473..0e104c34e35450 100644 --- a/src/go/types/object.go +++ b/src/go/types/object.go @@ -399,8 +399,9 @@ func (*Var) isDependency() {} // a variable may be a dependency of an initializa // An abstract method may belong to many interfaces due to embedding. type Func struct { object - hasPtrRecv_ bool // only valid for methods that don't have a type yet; use hasPtrRecv() to read origin *Func // if non-nil, the Func from which this one was instantiated + hasPtrRecv_ bool // only valid for methods that don't have a type yet; use hasPtrRecv() to read + nointerface bool } // NewFunc returns a new function with the given signature, representing @@ -415,7 +416,7 @@ func NewFunc(pos token.Pos, pkg *Package, name string, sig *Signature) *Func { // as this would violate object.{Type,color} invariants. // TODO(adonovan): propose to disallow NewFunc with nil *Signature. } - return &Func{object{nil, pos, pkg, name, typ, 0, nopos}, false, nil} + return &Func{object{nil, pos, pkg, name, typ, 0, nopos}, nil, false, false} } // Signature returns the signature (type) of the function or method. diff --git a/test/fixedbugs/issue74626.go b/test/fixedbugs/issue74626.go new file mode 100644 index 00000000000000..b41fbe4ec4e454 --- /dev/null +++ b/test/fixedbugs/issue74626.go @@ -0,0 +1,25 @@ +// errorcheck -goexperiment fieldtrack + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +type Fooer interface { + Foo() string +} + +type FooImpl struct{} + +//go:nointerface +func (FooImpl) Foo() string { return "foo" } + +func toInterface[T Fooer](fooer T) Fooer { + return fooer +} + +func main() { + var iface Fooer = toInterface(FooImpl{}) // ERROR "does not satisfy Fooer" + iface.Foo() +} From b7ad0fe09238eba928f6e23d871ec3674769f8b8 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 19 May 2026 13:25:24 -0700 Subject: [PATCH 30/32] all: use SkipObjectResolution mode in parser.ParseFile calls where possible Object resolution during parsing has always been problematic because there is incomplete type information, and the mechanism has beeen deprecated several years ago. Because of backward-compatibility, the mechanism - which was always enabled originally - must be explicitly disabled via the SkipObjectResolution mode. Almost none of the code in the std library requires it. Pass the SkipObjectResolution mode to parser.ParseFile where possible. This eliminates a typically unnecessary phase and may speed up the ParseFile call. Change-Id: Id74a4e8ca0cee781bc90b7d585f332a5220843b8 Reviewed-on: https://go-review.googlesource.com/c/go/+/779982 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Alan Donovan Reviewed-by: Robert Griesemer Auto-Submit: Robert Griesemer --- src/cmd/api/main_test.go | 2 +- .../internal/testerrors/argposition_test.go | 2 +- src/cmd/compile/internal/ssa/_gen/rulegen.go | 4 +- src/cmd/compile/internal/test/ssa_test.go | 2 +- .../compile/internal/typecheck/mkbuiltin.go | 2 +- src/cmd/cover/cover.go | 2 +- src/cmd/cover/cover_test.go | 2 +- src/cmd/cover/func.go | 2 +- src/cmd/go/internal/generate/generate.go | 2 +- src/cmd/go/internal/modindex/build_read.go | 2 +- src/cmd/gofmt/gofmt.go | 2 +- .../internal/dwarf/putvarabbrevgen_test.go | 2 +- src/cmd/internal/goobj/mkbuiltin.go | 2 +- src/go/ast/ast.go | 2 +- src/go/ast/commentmap_test.go | 4 +- src/go/ast/example_test.go | 10 ++--- src/go/ast/filter_test.go | 2 +- src/go/ast/issues_test.go | 4 +- src/go/ast/walk_test.go | 2 +- src/go/build/read.go | 2 +- src/go/doc/doc_test.go | 2 +- src/go/doc/example_internal_test.go | 2 +- src/go/doc/example_test.go | 2 +- src/go/format/format_test.go | 4 +- src/go/internal/gcimporter/gcimporter_test.go | 4 +- src/go/internal/gcimporter/genmeth_test.go | 2 +- src/go/parser/example_test.go | 2 +- src/go/printer/example_test.go | 2 +- src/go/printer/performance_test.go | 2 +- src/go/printer/printer_test.go | 38 +++++++++---------- src/go/token/example_test.go | 2 +- src/go/types/alias_test.go | 2 +- src/go/types/api_test.go | 12 +++--- src/go/types/check_test.go | 2 +- src/go/types/eval_test.go | 4 +- src/go/types/example_test.go | 2 +- src/go/types/generate_test.go | 2 +- src/go/types/gotype.go | 1 + src/go/types/hash_test.go | 2 +- src/go/types/issues_test.go | 2 +- src/go/types/methodset_test.go | 4 +- src/go/types/self_test.go | 2 +- src/go/types/stdlib_test.go | 4 +- src/go/types/typeset_test.go | 2 +- src/internal/types/errors/codes_test.go | 4 +- src/internal/types/errors/generrordocs.go | 2 +- src/runtime/_mkmalloc/mkmalloc.go | 2 +- src/runtime/metrics/description_test.go | 2 +- 48 files changed, 84 insertions(+), 83 deletions(-) diff --git a/src/cmd/api/main_test.go b/src/cmd/api/main_test.go index 411ecc12722239..8e6c6f216ebd31 100644 --- a/src/cmd/api/main_test.go +++ b/src/cmd/api/main_test.go @@ -389,7 +389,7 @@ func (w *Walker) parseFile(dir, file string) (*ast.File, error) { return f, nil } - f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) + f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments|parser.SkipObjectResolution) if err != nil { return nil, err } diff --git a/src/cmd/cgo/internal/testerrors/argposition_test.go b/src/cmd/cgo/internal/testerrors/argposition_test.go index 035552127b8f46..30447e7a68807e 100644 --- a/src/cmd/cgo/internal/testerrors/argposition_test.go +++ b/src/cmd/cgo/internal/testerrors/argposition_test.go @@ -96,7 +96,7 @@ func TestArgumentsPositions(t *testing.T) { t.Fatal(err) } fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "", mainProcessed, parser.AllErrors) + f, err := parser.ParseFile(fset, "", mainProcessed, parser.AllErrors|parser.SkipObjectResolution) if err != nil { fmt.Println(err) return diff --git a/src/cmd/compile/internal/ssa/_gen/rulegen.go b/src/cmd/compile/internal/ssa/_gen/rulegen.go index 3daf3e8605af7a..6cabb0006c980e 100644 --- a/src/cmd/compile/internal/ssa/_gen/rulegen.go +++ b/src/cmd/compile/internal/ssa/_gen/rulegen.go @@ -289,7 +289,7 @@ func genRulesSuffix(arch arch, suff string) { buf := new(bytes.Buffer) fprint(buf, genFile) fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "", buf, parser.ParseComments) + file, err := parser.ParseFile(fset, "", buf, parser.ParseComments|parser.SkipObjectResolution) if err != nil { filename := fmt.Sprintf("%s_broken.go", arch.name) if err := os.WriteFile(filename, buf.Bytes(), 0644); err != nil { @@ -841,7 +841,7 @@ func exprf(format string, a ...interface{}) ast.Expr { func stmtf(format string, a ...interface{}) Statement { src := fmt.Sprintf(format, a...) fsrc := "package p\nfunc _() {\n" + src + "\n}\n" - file, err := parser.ParseFile(token.NewFileSet(), "", fsrc, 0) + file, err := parser.ParseFile(token.NewFileSet(), "", fsrc, parser.SkipObjectResolution) if err != nil { log.Fatalf("stmt parse error on %q: %v", src, err) } diff --git a/src/cmd/compile/internal/test/ssa_test.go b/src/cmd/compile/internal/test/ssa_test.go index 10f32f65ad9fd9..c0a8b6c1c0817c 100644 --- a/src/cmd/compile/internal/test/ssa_test.go +++ b/src/cmd/compile/internal/test/ssa_test.go @@ -95,7 +95,7 @@ func TestCode(t *testing.T) { t.Fatalf("can't read testdata/%s: %v", f.Name(), err) } fset := token.NewFileSet() - code, err := parser.ParseFile(fset, f.Name(), text, 0) + code, err := parser.ParseFile(fset, f.Name(), text, parser.SkipObjectResolution) if err != nil { t.Fatalf("can't parse testdata/%s: %v", f.Name(), err) } diff --git a/src/cmd/compile/internal/typecheck/mkbuiltin.go b/src/cmd/compile/internal/typecheck/mkbuiltin.go index 28afac5d7ab190..c3b0848b1eef17 100644 --- a/src/cmd/compile/internal/typecheck/mkbuiltin.go +++ b/src/cmd/compile/internal/typecheck/mkbuiltin.go @@ -79,7 +79,7 @@ func params(tlist ...*types.Type) []*types.Field { func mkbuiltin(w io.Writer, name string) { fset := token.NewFileSet() - f, err := parser.ParseFile(fset, filepath.Join("_builtin", name+".go"), nil, 0) + f, err := parser.ParseFile(fset, filepath.Join("_builtin", name+".go"), nil, parser.SkipObjectResolution) if err != nil { log.Fatal(err) } diff --git a/src/cmd/cover/cover.go b/src/cmd/cover/cover.go index 578a978f4e53da..bc24c52f138a9b 100644 --- a/src/cmd/cover/cover.go +++ b/src/cmd/cover/cover.go @@ -773,7 +773,7 @@ func (p *Package) annotateFile(name string, fd io.Writer) { if err != nil { log.Fatalf("cover: %s: %s", name, err) } - parsedFile, err := parser.ParseFile(fset, name, content, parser.ParseComments) + parsedFile, err := parser.ParseFile(fset, name, content, parser.ParseComments|parser.SkipObjectResolution) if err != nil { log.Fatalf("cover: %s: %s", name, err) } diff --git a/src/cmd/cover/cover_test.go b/src/cmd/cover/cover_test.go index 504c263c9c6415..b86ebd0d149a0b 100644 --- a/src/cmd/cover/cover_test.go +++ b/src/cmd/cover/cover_test.go @@ -259,7 +259,7 @@ func TestDirectives(t *testing.T) { // come before the beginning of the named declaration and after the end // of the previous declaration. fset := token.NewFileSet() - astFile, err := parser.ParseFile(fset, testDirectives, output, 0) + astFile, err := parser.ParseFile(fset, testDirectives, output, parser.SkipObjectResolution) if err != nil { t.Fatal(err) } diff --git a/src/cmd/cover/func.go b/src/cmd/cover/func.go index dffd3c1a0553ac..82e9d0c7ac9af1 100644 --- a/src/cmd/cover/func.go +++ b/src/cmd/cover/func.go @@ -93,7 +93,7 @@ func funcOutput(profile, outputFile string) error { // findFuncs parses the file and returns a slice of FuncExtent descriptors. func findFuncs(name string) ([]*FuncExtent, error) { fset := token.NewFileSet() - parsedFile, err := parser.ParseFile(fset, name, nil, 0) + parsedFile, err := parser.ParseFile(fset, name, nil, parser.SkipObjectResolution) if err != nil { return nil, err } diff --git a/src/cmd/go/internal/generate/generate.go b/src/cmd/go/internal/generate/generate.go index bb66ce4bbdd067..c4ab16cf929dde 100644 --- a/src/cmd/go/internal/generate/generate.go +++ b/src/cmd/go/internal/generate/generate.go @@ -244,7 +244,7 @@ func generate(absFile string) bool { } // Parse package clause - filePkg, err := parser.ParseFile(token.NewFileSet(), "", src, parser.PackageClauseOnly) + filePkg, err := parser.ParseFile(token.NewFileSet(), "", src, parser.PackageClauseOnly|parser.SkipObjectResolution) if err != nil { // Invalid package clause - ignore file. return true diff --git a/src/cmd/go/internal/modindex/build_read.go b/src/cmd/go/internal/modindex/build_read.go index f2e48f36cdff54..86b908a9feae3d 100644 --- a/src/cmd/go/internal/modindex/build_read.go +++ b/src/cmd/go/internal/modindex/build_read.go @@ -304,7 +304,7 @@ func readGoInfo(f io.Reader, info *fileInfo) error { } // Parse file header & record imports. - info.parsed, info.parseErr = parser.ParseFile(info.fset, info.name, info.header, parser.ImportsOnly|parser.ParseComments) + info.parsed, info.parseErr = parser.ParseFile(info.fset, info.name, info.header, parser.ImportsOnly|parser.ParseComments|parser.SkipObjectResolution) if info.parseErr != nil { return nil } diff --git a/src/cmd/gofmt/gofmt.go b/src/cmd/gofmt/gofmt.go index ad6ad636524479..8f8540b41f83a4 100644 --- a/src/cmd/gofmt/gofmt.go +++ b/src/cmd/gofmt/gofmt.go @@ -79,7 +79,7 @@ func usage() { } func initParserMode() { - parserMode = parser.ParseComments + parserMode = parser.ParseComments | parser.SkipObjectResolution if *allErrors { parserMode |= parser.AllErrors } diff --git a/src/cmd/internal/dwarf/putvarabbrevgen_test.go b/src/cmd/internal/dwarf/putvarabbrevgen_test.go index 24500a33889d62..ffe400a30a8504 100644 --- a/src/cmd/internal/dwarf/putvarabbrevgen_test.go +++ b/src/cmd/internal/dwarf/putvarabbrevgen_test.go @@ -80,7 +80,7 @@ func TestPutVarAbbrevGenerator(t *testing.T) { func pvagenerate(t *testing.T) string { var fset token.FileSet - f, err := parser.ParseFile(&fset, "./dwarf.go", nil, parser.ParseComments) + f, err := parser.ParseFile(&fset, "./dwarf.go", nil, parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Fatal(err) } diff --git a/src/cmd/internal/goobj/mkbuiltin.go b/src/cmd/internal/goobj/mkbuiltin.go index 8b73bb115bec88..7e1c425c9d18bd 100644 --- a/src/cmd/internal/goobj/mkbuiltin.go +++ b/src/cmd/internal/goobj/mkbuiltin.go @@ -53,7 +53,7 @@ func mkbuiltin(w io.Writer) { const pkg = "runtime" fset := token.NewFileSet() path := filepath.Join("..", "..", "compile", "internal", "typecheck", "_builtin", "runtime.go") - f, err := parser.ParseFile(fset, path, nil, 0) + f, err := parser.ParseFile(fset, path, nil, parser.SkipObjectResolution) if err != nil { log.Fatal(err) } diff --git a/src/go/ast/ast.go b/src/go/ast/ast.go index cbd4edd595b05d..c916abded148cd 100644 --- a/src/go/ast/ast.go +++ b/src/go/ast/ast.go @@ -1113,7 +1113,7 @@ func (p *Package) End() token.Pos { return token.NoPos } // The syntax tree must have been parsed with the [go/parser.ParseComments] flag. // Example: // -// f, err := parser.ParseFile(fset, filename, src, parser.ParseComments|parser.PackageClauseOnly) +// f, err := parser.ParseFile(fset, filename, src, parser.ParseComments|parser.PackageClauseOnly|parser.SkipObjectResolution) // if err != nil { ... } // gen := ast.IsGenerated(f) func IsGenerated(file *File) bool { diff --git a/src/go/ast/commentmap_test.go b/src/go/ast/commentmap_test.go index 0d5e8de0137669..332d099de4433d 100644 --- a/src/go/ast/commentmap_test.go +++ b/src/go/ast/commentmap_test.go @@ -103,7 +103,7 @@ func ctext(list []*CommentGroup) string { func TestCommentMap(t *testing.T) { fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "", src, parser.ParseComments) + f, err := parser.ParseFile(fset, "", src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Fatal(err) } @@ -141,7 +141,7 @@ func TestCommentMap(t *testing.T) { func TestFilter(t *testing.T) { fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "", src, parser.ParseComments) + f, err := parser.ParseFile(fset, "", src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Fatal(err) } diff --git a/src/go/ast/example_test.go b/src/go/ast/example_test.go index 36daa7e7e1e646..6f54028b633224 100644 --- a/src/go/ast/example_test.go +++ b/src/go/ast/example_test.go @@ -24,7 +24,7 @@ var X = f(3.14)*2 + c // Create the AST by parsing src. fset := token.NewFileSet() // positions are relative to fset - f, err := parser.ParseFile(fset, "src.go", src, 0) + f, err := parser.ParseFile(fset, "src.go", src, parser.SkipObjectResolution) if err != nil { panic(err) } @@ -66,8 +66,8 @@ func main() { ` // Create the AST by parsing src. - fset := token.NewFileSet() // positions are relative to fset - f, err := parser.ParseFile(fset, "", src, 0) + fset := token.NewFileSet() // positions are relative to fset + f, err := parser.ParseFile(fset, "", src, 0) // requires object resolution if err != nil { panic(err) } @@ -151,7 +151,7 @@ func f(x, y int) { ` fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "", src, 0) + f, err := parser.ParseFile(fset, "", src, parser.SkipObjectResolution) if err != nil { panic(err) } @@ -200,7 +200,7 @@ func main() { // Create the AST by parsing src. fset := token.NewFileSet() // positions are relative to fset - f, err := parser.ParseFile(fset, "src.go", src, parser.ParseComments) + f, err := parser.ParseFile(fset, "src.go", src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { panic(err) } diff --git a/src/go/ast/filter_test.go b/src/go/ast/filter_test.go index d5cb0c2e3f1797..c33dc56773a336 100644 --- a/src/go/ast/filter_test.go +++ b/src/go/ast/filter_test.go @@ -57,7 +57,7 @@ func (x *t2) f2() {} func TestFilterDuplicates(t *testing.T) { // parse input fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "", input, 0) + file, err := parser.ParseFile(fset, "", input, 0) // requires object resolution if err != nil { t.Fatal(err) } diff --git a/src/go/ast/issues_test.go b/src/go/ast/issues_test.go index f5e26af462f55a..2db6f8afcc78dc 100644 --- a/src/go/ast/issues_test.go +++ b/src/go/ast/issues_test.go @@ -19,7 +19,7 @@ func TestIssue33649(t *testing.T) { `package p; func _() { _ = 0 }`, } { fset := token.NewFileSet() - f, _ := parser.ParseFile(fset, "", src, parser.AllErrors) + f, _ := parser.ParseFile(fset, "", src, parser.AllErrors|parser.SkipObjectResolution) if f == nil { panic("invalid test setup: parser didn't return an AST") } @@ -125,7 +125,7 @@ package p `, true}, } { fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "", test.src, parser.PackageClauseOnly|parser.ParseComments) + f, err := parser.ParseFile(fset, "", test.src, parser.PackageClauseOnly|parser.ParseComments|parser.SkipObjectResolution) if f == nil { t.Fatalf("parse %d failed to return AST: %v", i, err) } diff --git a/src/go/ast/walk_test.go b/src/go/ast/walk_test.go index 172b2e3f5dd269..60a1b5f3fa5de9 100644 --- a/src/go/ast/walk_test.go +++ b/src/go/ast/walk_test.go @@ -46,7 +46,7 @@ func g() { } ` fset := token.NewFileSet() - f, _ := parser.ParseFile(fset, "a.go", src, 0) + f, _ := parser.ParseFile(fset, "a.go", src, parser.SkipObjectResolution) str := func(n ast.Node) string { return strings.TrimPrefix(reflect.TypeOf(n).String(), "*ast.") diff --git a/src/go/build/read.go b/src/go/build/read.go index 3185cf09bec72e..737f409d4eb427 100644 --- a/src/go/build/read.go +++ b/src/go/build/read.go @@ -311,7 +311,7 @@ func readGoInfo(f io.Reader, info *fileInfo) error { } // Parse file header & record imports. - info.parsed, info.parseErr = parser.ParseFile(info.fset, info.name, info.header, parser.ImportsOnly|parser.ParseComments) + info.parsed, info.parseErr = parser.ParseFile(info.fset, info.name, info.header, parser.ImportsOnly|parser.ParseComments|parser.SkipObjectResolution) if info.parseErr != nil { return nil } diff --git a/src/go/doc/doc_test.go b/src/go/doc/doc_test.go index b79087e5387d3d..144a411c8180cb 100644 --- a/src/go/doc/doc_test.go +++ b/src/go/doc/doc_test.go @@ -154,7 +154,7 @@ func Test(t *testing.T) { func TestFuncs(t *testing.T) { fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "funcs.go", strings.NewReader(funcsTestFile), parser.ParseComments) + file, err := parser.ParseFile(fset, "funcs.go", strings.NewReader(funcsTestFile), parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Fatal(err) } diff --git a/src/go/doc/example_internal_test.go b/src/go/doc/example_internal_test.go index 08ddfafdd441dd..90644e34b800f0 100644 --- a/src/go/doc/example_internal_test.go +++ b/src/go/doc/example_internal_test.go @@ -100,7 +100,7 @@ import ( } { t.Run(test.name, func(t *testing.T) { fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "test.go", strings.NewReader(test.in), parser.ParseComments) + file, err := parser.ParseFile(fset, "test.go", strings.NewReader(test.in), parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Fatal(err) } diff --git a/src/go/doc/example_test.go b/src/go/doc/example_test.go index db2b2d34cd3060..100e359212a813 100644 --- a/src/go/doc/example_test.go +++ b/src/go/doc/example_test.go @@ -29,7 +29,7 @@ func TestExamples(t *testing.T) { for _, filename := range filenames { t.Run(strings.TrimSuffix(filepath.Base(filename), ".go"), func(t *testing.T) { fset := token.NewFileSet() - astFile, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) + astFile, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) // requires object resolution if err != nil { t.Fatal(err) } diff --git a/src/go/format/format_test.go b/src/go/format/format_test.go index 6cc0278b79b696..0980774f18577c 100644 --- a/src/go/format/format_test.go +++ b/src/go/format/format_test.go @@ -44,7 +44,7 @@ func TestNode(t *testing.T) { } fset := token.NewFileSet() - file, err := parser.ParseFile(fset, testfile, src, parser.ParseComments) + file, err := parser.ParseFile(fset, testfile, src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Fatal(err) } @@ -67,7 +67,7 @@ func TestNodeNoModify(t *testing.T) { ) fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "", src, parser.ParseComments) + file, err := parser.ParseFile(fset, "", src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Fatal(err) } diff --git a/src/go/internal/gcimporter/gcimporter_test.go b/src/go/internal/gcimporter/gcimporter_test.go index c4861c706751f5..17f0893e1aaf14 100644 --- a/src/go/internal/gcimporter/gcimporter_test.go +++ b/src/go/internal/gcimporter/gcimporter_test.go @@ -231,7 +231,7 @@ func sanitizeObjectString(s string) string { func checkFile(t *testing.T, filename string, src []byte) *types.Package { fset := token.NewFileSet() - f, err := parser.ParseFile(fset, filename, src, 0) + f, err := parser.ParseFile(fset, filename, src, parser.SkipObjectResolution) if err != nil { t.Fatal(err) } @@ -792,7 +792,7 @@ func TestIssue69912(t *testing.T) { } check := func(pkgname, src string, imports importMap) (*types.Package, error) { - f, err := parser.ParseFile(fset, "a.go", src, 0) + f, err := parser.ParseFile(fset, "a.go", src, parser.SkipObjectResolution) if err != nil { return nil, err } diff --git a/src/go/internal/gcimporter/genmeth_test.go b/src/go/internal/gcimporter/genmeth_test.go index 63b8858af3c0d2..2bbb7bde26391c 100644 --- a/src/go/internal/gcimporter/genmeth_test.go +++ b/src/go/internal/gcimporter/genmeth_test.go @@ -45,7 +45,7 @@ func TestGenMeth(t *testing.T) { } check := func(pkgname, src string, imports importMap) (*types.Package, error) { - f, err := parser.ParseFile(fset, "genmeth.go", src, 0) + f, err := parser.ParseFile(fset, "genmeth.go", src, parser.SkipObjectResolution) if err != nil { return nil, err } diff --git a/src/go/parser/example_test.go b/src/go/parser/example_test.go index c2f7f293bc1467..b4e2a1b34df66a 100644 --- a/src/go/parser/example_test.go +++ b/src/go/parser/example_test.go @@ -25,7 +25,7 @@ func bar() { }` // Parse src but stop after processing the imports. - f, err := parser.ParseFile(fset, "", src, parser.ImportsOnly) + f, err := parser.ParseFile(fset, "", src, parser.ImportsOnly|parser.SkipObjectResolution) if err != nil { fmt.Println(err) return diff --git a/src/go/printer/example_test.go b/src/go/printer/example_test.go index f7d72d136d7aea..947f2c8788981e 100644 --- a/src/go/printer/example_test.go +++ b/src/go/printer/example_test.go @@ -16,7 +16,7 @@ import ( func parseFunc(filename, functionname string) (fun *ast.FuncDecl, fset *token.FileSet) { fset = token.NewFileSet() - if file, err := parser.ParseFile(fset, filename, nil, 0); err == nil { + if file, err := parser.ParseFile(fset, filename, nil, parser.SkipObjectResolution); err == nil { for _, d := range file.Decls { if f, ok := d.(*ast.FuncDecl); ok && f.Name.Name == functionname { fun = f diff --git a/src/go/printer/performance_test.go b/src/go/printer/performance_test.go index c58f6d470610a8..19a2cb7af98fcb 100644 --- a/src/go/printer/performance_test.go +++ b/src/go/printer/performance_test.go @@ -41,7 +41,7 @@ func initialize() { log.Fatalf("%s", err) } - file, err := parser.ParseFile(fset, filename, src, parser.ParseComments) + file, err := parser.ParseFile(fset, filename, src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { log.Fatalf("%s", err) } diff --git a/src/go/printer/printer_test.go b/src/go/printer/printer_test.go index 97940f480306dc..806d796c0cb688 100644 --- a/src/go/printer/printer_test.go +++ b/src/go/printer/printer_test.go @@ -44,7 +44,7 @@ const ( // if any. func format(src []byte, mode checkMode) ([]byte, error) { // parse src - f, err := parser.ParseFile(fset, "", src, parser.ParseComments) + f, err := parser.ParseFile(fset, "", src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { return nil, fmt.Errorf("parse: %s\n%s", err, src) } @@ -72,7 +72,7 @@ func format(src []byte, mode checkMode) ([]byte, error) { // make sure formatted output is syntactically correct res := buf.Bytes() - if _, err := parser.ParseFile(fset, "", res, parser.ParseComments); err != nil { + if _, err := parser.ParseFile(fset, "", res, parser.ParseComments|parser.SkipObjectResolution); err != nil { return nil, fmt.Errorf("re-parse: %s\n%s", err, buf.Bytes()) } @@ -220,7 +220,7 @@ func TestLineComments(t *testing.T) { ` fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "", src, parser.ParseComments) + f, err := parser.ParseFile(fset, "", src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { panic(err) // error in test } @@ -261,7 +261,7 @@ func init() { func TestBadNodes(t *testing.T) { const src = "package p\n(" const res = "package p\nBadDecl\n" - f, err := parser.ParseFile(fset, "", src, parser.ParseComments) + f, err := parser.ParseFile(fset, "", src, parser.ParseComments|parser.SkipObjectResolution) if err == nil { t.Error("expected illegal program") // error in test } @@ -284,7 +284,7 @@ func testComment(t *testing.T, f *ast.File, srclen int, comment *ast.Comment) { if err := Fprint(&buf, fset, f); err != nil { t.Error(err) } - if _, err := parser.ParseFile(fset, "", buf.Bytes(), 0); err != nil { + if _, err := parser.ParseFile(fset, "", buf.Bytes(), parser.SkipObjectResolution); err != nil { t.Fatalf("incorrect program for pos = %d:\n%s", comment.Slash, buf.String()) } // Position information is just an offset. @@ -315,7 +315,7 @@ func fibo(n int) { } ` - f, err := parser.ParseFile(fset, "", src, parser.ParseComments) + f, err := parser.ParseFile(fset, "", src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Error(err) // error in test } @@ -378,7 +378,7 @@ func (t *t) foo(a, b, c int) int { ` // parse original - f1, err := parser.ParseFile(fset, "src", src, parser.ParseComments) + f1, err := parser.ParseFile(fset, "src", src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Fatal(err) } @@ -392,7 +392,7 @@ func (t *t) foo(a, b, c int) int { // parse pretty printed original // (//line directives must be interpreted even w/o parser.ParseComments set) - f2, err := parser.ParseFile(fset, "", buf.Bytes(), 0) + f2, err := parser.ParseFile(fset, "", buf.Bytes(), parser.SkipObjectResolution) if err != nil { t.Fatalf("%s\n%s", err, buf.Bytes()) } @@ -460,7 +460,7 @@ func g() { ` // parse original - f1, err := parser.ParseFile(fset, "src.go", orig, 0) + f1, err := parser.ParseFile(fset, "src.go", orig, parser.SkipObjectResolution) if err != nil { t.Fatal(err) } @@ -502,7 +502,7 @@ type T struct { } ` - f, err := parser.ParseFile(fset, "src.go", orig, parser.ParseComments) + f, err := parser.ParseFile(fset, "src.go", orig, parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Fatal(err) } @@ -527,7 +527,7 @@ var decls = []string{ func TestDeclLists(t *testing.T) { for _, src := range decls { - file, err := parser.ParseFile(fset, "", "package p;"+src, parser.ParseComments) + file, err := parser.ParseFile(fset, "", "package p;"+src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { panic(err) // error in test } @@ -553,7 +553,7 @@ var stmts = []string{ func TestStmtLists(t *testing.T) { for _, src := range stmts { - file, err := parser.ParseFile(fset, "", "package p; func _() {"+src+"}", parser.ParseComments) + file, err := parser.ParseFile(fset, "", "package p; func _() {"+src+"}", parser.ParseComments|parser.SkipObjectResolution) if err != nil { panic(err) // error in test } @@ -582,7 +582,7 @@ func TestBaseIndent(t *testing.T) { panic(err) // error in test } - file, err := parser.ParseFile(fset, filename, src, 0) + file, err := parser.ParseFile(fset, filename, src, parser.SkipObjectResolution) if err != nil { panic(err) // error in test } @@ -690,7 +690,7 @@ func TestWriteErrors(t *testing.T) { if err != nil { panic(err) // error in test } - file, err := parser.ParseFile(fset, filename, src, 0) + file, err := parser.ParseFile(fset, filename, src, parser.SkipObjectResolution) if err != nil { panic(err) // error in test } @@ -743,7 +743,7 @@ type bar int // comment2 ) fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "input.go", input, parser.ParseComments) + f, err := parser.ParseFile(fset, "input.go", input, parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Fatal(err) } @@ -774,7 +774,7 @@ type bar int // comment2 func TestIssue11151(t *testing.T) { const src = "package p\t/*\r/1\r*\r/2*\r\r\r\r/3*\r\r+\r\r/4*/\n" fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "", src, parser.ParseComments) + f, err := parser.ParseFile(fset, "", src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Fatal(err) } @@ -788,7 +788,7 @@ func TestIssue11151(t *testing.T) { } // the resulting program must be valid - _, err = parser.ParseFile(fset, "", got, 0) + _, err = parser.ParseFile(fset, "", got, parser.SkipObjectResolution) if err != nil { t.Errorf("%v\norig: %q\ngot : %q", err, src, got) } @@ -800,7 +800,7 @@ func TestParenthesizedDecl(t *testing.T) { // a package with multiple specs in a single declaration const src = "package p; var ( a float64; b int )" fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "", src, 0) + f, err := parser.ParseFile(fset, "", src, parser.SkipObjectResolution) if err != nil { t.Fatal(err) } @@ -840,7 +840,7 @@ func f() { } }` fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "", src, 0) + file, err := parser.ParseFile(fset, "", src, parser.SkipObjectResolution) if err != nil { panic(err) } diff --git a/src/go/token/example_test.go b/src/go/token/example_test.go index 7e5017e13c8ba1..80c2cdee63b19a 100644 --- a/src/go/token/example_test.go +++ b/src/go/token/example_test.go @@ -35,7 +35,7 @@ func ok(pos p) bool { } ` - f, err := parser.ParseFile(fset, "main.go", src, 0) + f, err := parser.ParseFile(fset, "main.go", src, parser.SkipObjectResolution) if err != nil { fmt.Println(err) return diff --git a/src/go/types/alias_test.go b/src/go/types/alias_test.go index 007f0c29998702..e0c52fbe78372d 100644 --- a/src/go/types/alias_test.go +++ b/src/go/types/alias_test.go @@ -59,7 +59,7 @@ func TestPartialTypeCheckUndeclaredAliasPanic(t *testing.T) { type A = B // undeclared` fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "p.go", src, parser.ParseComments) + file, err := parser.ParseFile(fset, "p.go", src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Fatalf("could not parse: %v", err) } diff --git a/src/go/types/api_test.go b/src/go/types/api_test.go index bb9e2f47be0870..e8e4f502313b5b 100644 --- a/src/go/types/api_test.go +++ b/src/go/types/api_test.go @@ -31,7 +31,7 @@ func defaultImporter(fset *token.FileSet) Importer { } func mustParse(fset *token.FileSet, src string) *ast.File { - f, err := parser.ParseFile(fset, pkgName(src), src, parser.ParseComments) + f, err := parser.ParseFile(fset, pkgName(src), src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { panic(err) // so we don't need to pass *testing.T } @@ -3055,7 +3055,7 @@ type C = int // to compute which file it is "in" based on syntax position. func TestVersionIssue69477(t *testing.T) { fset := token.NewFileSet() - f, _ := parser.ParseFile(fset, "a.go", "package p; const k = 123", 0) + f, _ := parser.ParseFile(fset, "a.go", "package p; const k = 123", parser.SkipObjectResolution) // Set an invalid Pos on the BasicLit. ast.Inspect(f, func(n ast.Node) bool { @@ -3085,10 +3085,10 @@ func TestVersionIssue69477(t *testing.T) { // The Checker now holds the effective version in a state variable. func TestVersionWithoutPos(t *testing.T) { fset := token.NewFileSet() - f, _ := parser.ParseFile(fset, "a.go", "//go:build go1.22\n\npackage p; var _ int", 0) + f, _ := parser.ParseFile(fset, "a.go", "//go:build go1.22\n\npackage p; var _ int", parser.SkipObjectResolution) // Splice in a decl from another file. Its pos will be wrong. - f2, _ := parser.ParseFile(fset, "a.go", "package q; func _(s func(func() bool)) { for range s {} }", 0) + f2, _ := parser.ParseFile(fset, "a.go", "package q; func _(s func(func() bool)) { for range s {} }", parser.SkipObjectResolution) f.Decls[0] = f2.Decls[0] // Type check. The checker will consult the effective @@ -3122,7 +3122,7 @@ func (recv T) f(param int) (result int) { } return local2 } -`, 0) +`, parser.SkipObjectResolution) pkg := NewPackage("p", "p") info := &Info{Defs: make(map[*ast.Ident]Object)} @@ -3160,7 +3160,7 @@ type B []byte var _ = f[B] ` fset := token.NewFileSet() - f, _ := parser.ParseFile(fset, "p.go", src, 0) + f, _ := parser.ParseFile(fset, "p.go", src, parser.SkipObjectResolution) pkg := NewPackage("p", "p") info := &Info{Types: make(map[ast.Expr]TypeAndValue)} diff --git a/src/go/types/check_test.go b/src/go/types/check_test.go index 142d221502d527..2fb6b9de4458ec 100644 --- a/src/go/types/check_test.go +++ b/src/go/types/check_test.go @@ -139,7 +139,7 @@ func testFiles(t *testing.T, filenames []string, srcs [][]byte, manual bool, opt } // parse files - files, errlist := parseFiles(t, filenames, srcs, parser.AllErrors) + files, errlist := parseFiles(t, filenames, srcs, parser.AllErrors|parser.SkipObjectResolution) pkgName := "" if len(files) > 0 { pkgName = files[0].Name.Name diff --git a/src/go/types/eval_test.go b/src/go/types/eval_test.go index fd62b7ab88bdf5..2e0624e65ee2b9 100644 --- a/src/go/types/eval_test.go +++ b/src/go/types/eval_test.go @@ -168,7 +168,7 @@ func TestEvalPos(t *testing.T) { fset := token.NewFileSet() var files []*ast.File for i, src := range sources { - file, err := parser.ParseFile(fset, "p", src, parser.ParseComments) + file, err := parser.ParseFile(fset, "p", src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Fatalf("could not parse file %d: %s", i, err) } @@ -241,7 +241,7 @@ func f(a int, s string) S { }` fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "p", src, parser.ParseComments) + f, err := parser.ParseFile(fset, "p", src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Fatal(err) } diff --git a/src/go/types/example_test.go b/src/go/types/example_test.go index d8e5de7476c81b..cec01457355148 100644 --- a/src/go/types/example_test.go +++ b/src/go/types/example_test.go @@ -117,7 +117,7 @@ type S struct { I; m int } type I interface { m() byte } ` fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "celsius.go", input, 0) + f, err := parser.ParseFile(fset, "celsius.go", input, parser.SkipObjectResolution) if err != nil { log.Fatal(err) } diff --git a/src/go/types/generate_test.go b/src/go/types/generate_test.go index 4b49d6d4a1c190..7e090c909bf559 100644 --- a/src/go/types/generate_test.go +++ b/src/go/types/generate_test.go @@ -54,7 +54,7 @@ func TestGenerate(t *testing.T) { func generate(t *testing.T, filename string, write bool) { // parse src (cmd/compile/internal/types2) srcFilename := filepath.FromSlash(runtime.GOROOT() + srcDir + filename) - file, err := parser.ParseFile(fset, srcFilename, nil, parser.ParseComments) + file, err := parser.ParseFile(fset, srcFilename, nil, parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Fatal(err) } diff --git a/src/go/types/gotype.go b/src/go/types/gotype.go index ee9c12c447a414..7228916c0c4539 100644 --- a/src/go/types/gotype.go +++ b/src/go/types/gotype.go @@ -120,6 +120,7 @@ var ( ) func initParserMode() { + parserMode = parser.SkipObjectResolution if *allErrors { parserMode |= parser.AllErrors } diff --git a/src/go/types/hash_test.go b/src/go/types/hash_test.go index 98b1ae23df59b5..4b040d26472146 100644 --- a/src/go/types/hash_test.go +++ b/src/go/types/hash_test.go @@ -95,7 +95,7 @@ type Tagged2 struct { F int "tag2" } ` fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "p.go", src, 0) + file, err := parser.ParseFile(fset, "p.go", src, parser.SkipObjectResolution) if err != nil { t.Fatal(err) } diff --git a/src/go/types/issues_test.go b/src/go/types/issues_test.go index 676bcdb159540b..2403d403262bf4 100644 --- a/src/go/types/issues_test.go +++ b/src/go/types/issues_test.go @@ -950,7 +950,7 @@ func _() { } ` fset := token.NewFileSet() - f, err := parser.ParseFile(fset, pkgName(src), src, 0) + f, err := parser.ParseFile(fset, pkgName(src), src, parser.SkipObjectResolution) if err == nil { t.Fatal("expected syntax error") } diff --git a/src/go/types/methodset_test.go b/src/go/types/methodset_test.go index 37529fea4a985d..d0ec1f1d881a6e 100644 --- a/src/go/types/methodset_test.go +++ b/src/go/types/methodset_test.go @@ -144,7 +144,7 @@ type Instance = *Tree[int] ` fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "foo.go", src, 0) + f, err := parser.ParseFile(fset, "foo.go", src, parser.SkipObjectResolution) if err != nil { panic(err) } @@ -165,7 +165,7 @@ func (T) m() {} // expected error: invalid receiver type ` fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "p.go", src, 0) + f, err := parser.ParseFile(fset, "p.go", src, parser.SkipObjectResolution) if err != nil { t.Fatal(err) } diff --git a/src/go/types/self_test.go b/src/go/types/self_test.go index b4cc6286a18f81..130c2c7b65e4e7 100644 --- a/src/go/types/self_test.go +++ b/src/go/types/self_test.go @@ -110,7 +110,7 @@ func pkgFiles(fset *token.FileSet, path string) ([]*ast.File, error) { var files []*ast.File for _, filename := range filenames { - file, err := parser.ParseFile(fset, filename, nil, 0) + file, err := parser.ParseFile(fset, filename, nil, parser.SkipObjectResolution) if err != nil { return nil, err } diff --git a/src/go/types/stdlib_test.go b/src/go/types/stdlib_test.go index 74b456d66e8e7c..cf25db2bfe47b3 100644 --- a/src/go/types/stdlib_test.go +++ b/src/go/types/stdlib_test.go @@ -263,7 +263,7 @@ func testTestDir(t *testing.T, path string, ignore ...string) { } // parse and type-check file - file, err := parser.ParseFile(fset, filename, nil, 0) + file, err := parser.ParseFile(fset, filename, nil, parser.SkipObjectResolution) if err == nil { conf := Config{ GoVersion: goVersion, @@ -377,7 +377,7 @@ func typecheckFiles(path string, filenames []string, importer Importer) (*Packag // Parse package files. var files []*ast.File for _, filename := range filenames { - file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors) + file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors|parser.SkipObjectResolution) if err != nil { return nil, err } diff --git a/src/go/types/typeset_test.go b/src/go/types/typeset_test.go index a4feb7469b0637..d2ff6c6cbd207f 100644 --- a/src/go/types/typeset_test.go +++ b/src/go/types/typeset_test.go @@ -48,7 +48,7 @@ func TestTypeSetString(t *testing.T) { // parse src := "package p; type T interface" + body fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "p.go", src, parser.AllErrors) + file, err := parser.ParseFile(fset, "p.go", src, parser.AllErrors|parser.SkipObjectResolution) if file == nil { t.Fatalf("%s: %v (invalid test case)", body, err) } diff --git a/src/internal/types/errors/codes_test.go b/src/internal/types/errors/codes_test.go index 2490ade5c36975..dd9e7f695bec4a 100644 --- a/src/internal/types/errors/codes_test.go +++ b/src/internal/types/errors/codes_test.go @@ -47,7 +47,7 @@ func TestErrorCodeExamples(t *testing.T) { func walkCodes(t *testing.T, f func(string, int, *ast.ValueSpec)) { t.Helper() fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "codes.go", nil, parser.ParseComments) + file, err := parser.ParseFile(fset, "codes.go", nil, parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Fatal(err) } @@ -95,7 +95,7 @@ func checkExample(t *testing.T, example string) error { if !strings.HasPrefix(example, "package") { example = "package p\n\n" + example } - file, err := parser.ParseFile(fset, "example.go", example, 0) + file, err := parser.ParseFile(fset, "example.go", example, parser.SkipObjectResolution) if err != nil { t.Fatal(err) } diff --git a/src/internal/types/errors/generrordocs.go b/src/internal/types/errors/generrordocs.go index 613c77426928de..4df53e182796fd 100644 --- a/src/internal/types/errors/generrordocs.go +++ b/src/internal/types/errors/generrordocs.go @@ -67,7 +67,7 @@ func main() { func walkCodes(f func(string, *ast.ValueSpec)) { fset := token.NewFileSet() - file, err := parser.ParseFile(fset, "codes.go", nil, parser.ParseComments) + file, err := parser.ParseFile(fset, "codes.go", nil, parser.ParseComments|parser.SkipObjectResolution) if err != nil { log.Fatalf("ParseFile failed: %s", err) } diff --git a/src/runtime/_mkmalloc/mkmalloc.go b/src/runtime/_mkmalloc/mkmalloc.go index 3ecae4d92cdcea..02b7bda048a511 100644 --- a/src/runtime/_mkmalloc/mkmalloc.go +++ b/src/runtime/_mkmalloc/mkmalloc.go @@ -298,7 +298,7 @@ func inline(config generatorConfig) []byte { // Read the template file in. fset := token.NewFileSet() - f, err := parser.ParseFile(fset, config.file, nil, 0) + f, err := parser.ParseFile(fset, config.file, nil, parser.SkipObjectResolution) if err != nil { log.Fatalf("parsing %s: %v", config.file, err) } diff --git a/src/runtime/metrics/description_test.go b/src/runtime/metrics/description_test.go index 71c7b00b02ed28..a9dae4d761a32f 100644 --- a/src/runtime/metrics/description_test.go +++ b/src/runtime/metrics/description_test.go @@ -94,7 +94,7 @@ func TestDocs(t *testing.T) { t.Fatal(err) } fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "doc.go", src, parser.ParseComments) + f, err := parser.ParseFile(fset, "doc.go", src, parser.ParseComments|parser.SkipObjectResolution) if err != nil { t.Fatal(err) } From 2a935769659cece63d8b77cd3d1c9babda3a2839 Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Mon, 4 May 2026 16:42:36 -0400 Subject: [PATCH 31/32] interrnal/buildcfg: enable SizeSpecializedMalloc by default For #79286 Change-Id: I0c201e1b68416a8adccc9aae5308657a6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/773941 TryBot-Bypass: Michael Matloob Reviewed-by: Michael Pratt Reviewed-by: Michael Matloob --- src/internal/buildcfg/exp.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/internal/buildcfg/exp.go b/src/internal/buildcfg/exp.go index f45dc62f2a4882..8eeef17c1f470e 100644 --- a/src/internal/buildcfg/exp.go +++ b/src/internal/buildcfg/exp.go @@ -79,13 +79,14 @@ func ParseGOEXPERIMENT(goos, goarch, goexp string) (*ExperimentFlags, error) { dwarf5Supported := (goos != "darwin" && goos != "ios" && goos != "aix") baseline := goexperiment.Flags{ - RegabiWrappers: regabiSupported, - RegabiArgs: regabiSupported, - Dwarf5: dwarf5Supported, - RandomizedHeapBase64: true, - GreenTeaGC: true, - JSONv2: true, - GenericMethods: true, + RegabiWrappers: regabiSupported, + RegabiArgs: regabiSupported, + Dwarf5: dwarf5Supported, + RandomizedHeapBase64: true, + GreenTeaGC: true, + JSONv2: true, + GenericMethods: true, + SizeSpecializedMalloc: true, } flags := &ExperimentFlags{ Flags: baseline, From 15f44ffcc38ca18ae44f003bf87254b43c6d7368 Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Tue, 19 May 2026 16:11:23 -0400 Subject: [PATCH 32/32] runtime: skip gcBlackenEnabled check and gcmarknewobject in fast path We check earlier that it's zero before entering the fast path. For #79286 Change-Id: I1f5d9d6c04bcf3a30e550423d1a47c236a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/780080 Reviewed-by: Michael Pratt LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Michael Matloob --- src/runtime/_mkmalloc/mkmalloc.go | 7 ++ src/runtime/malloc_generated.go | 160 ++++-------------------------- src/runtime/malloc_stubs.go | 4 +- 3 files changed, 29 insertions(+), 142 deletions(-) diff --git a/src/runtime/_mkmalloc/mkmalloc.go b/src/runtime/_mkmalloc/mkmalloc.go index 02b7bda048a511..727e17aa506ace 100644 --- a/src/runtime/_mkmalloc/mkmalloc.go +++ b/src/runtime/_mkmalloc/mkmalloc.go @@ -464,6 +464,13 @@ func foldIfCondition(node ast.Node, from, to string) ast.Node { cursor.InsertBefore(stmt) } } + if n.Else != nil { + if block, ok := n.Else.(*ast.BlockStmt); ok { + for i := len(block.List) - 1; i >= 0; i-- { + cursor.InsertAfter(block.List[i]) + } + } + } cursor.Delete() } } diff --git a/src/runtime/malloc_generated.go b/src/runtime/malloc_generated.go index 66420c0f9f8eeb..e2efa7168a9b5b 100644 --- a/src/runtime/malloc_generated.go +++ b/src/runtime/malloc_generated.go @@ -135,13 +135,7 @@ func mallocgcSmallScanNoHeaderSC1(size uintptr, typ *_type, needzero bool) unsaf publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -284,13 +278,7 @@ func mallocgcSmallScanNoHeaderSC2(size uintptr, typ *_type, needzero bool) unsaf publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -433,13 +421,7 @@ func mallocgcSmallScanNoHeaderSC3(size uintptr, typ *_type, needzero bool) unsaf publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -582,13 +564,7 @@ func mallocgcSmallScanNoHeaderSC4(size uintptr, typ *_type, needzero bool) unsaf publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -731,13 +707,7 @@ func mallocgcSmallScanNoHeaderSC5(size uintptr, typ *_type, needzero bool) unsaf publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -880,13 +850,7 @@ func mallocgcSmallScanNoHeaderSC6(size uintptr, typ *_type, needzero bool) unsaf publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -1029,13 +993,7 @@ func mallocgcSmallScanNoHeaderSC7(size uintptr, typ *_type, needzero bool) unsaf publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -1178,13 +1136,7 @@ func mallocgcSmallScanNoHeaderSC8(size uintptr, typ *_type, needzero bool) unsaf publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -1327,13 +1279,7 @@ func mallocgcSmallScanNoHeaderSC9(size uintptr, typ *_type, needzero bool) unsaf publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -1476,13 +1422,7 @@ func mallocgcSmallScanNoHeaderSC10(size uintptr, typ *_type, needzero bool) unsa publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -1589,13 +1529,7 @@ func mallocgcTinySC2(size uintptr, typ *_type, needzero bool) unsafe.Pointer { publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -1683,13 +1617,7 @@ func mallocgcSmallNoScanSC2(size uintptr, typ *_type, needzero bool) unsafe.Poin publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -1777,13 +1705,7 @@ func mallocgcSmallNoScanSC3(size uintptr, typ *_type, needzero bool) unsafe.Poin publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -1871,13 +1793,7 @@ func mallocgcSmallNoScanSC4(size uintptr, typ *_type, needzero bool) unsafe.Poin publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -1965,13 +1881,7 @@ func mallocgcSmallNoScanSC5(size uintptr, typ *_type, needzero bool) unsafe.Poin publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -2059,13 +1969,7 @@ func mallocgcSmallNoScanSC6(size uintptr, typ *_type, needzero bool) unsafe.Poin publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -2153,13 +2057,7 @@ func mallocgcSmallNoScanSC7(size uintptr, typ *_type, needzero bool) unsafe.Poin publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -2247,13 +2145,7 @@ func mallocgcSmallNoScanSC8(size uintptr, typ *_type, needzero bool) unsafe.Poin publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -2341,13 +2233,7 @@ func mallocgcSmallNoScanSC9(size uintptr, typ *_type, needzero bool) unsafe.Poin publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { @@ -2435,13 +2321,7 @@ func mallocgcSmallNoScanSC10(size uintptr, typ *_type, needzero bool) unsafe.Poi publicationBarrier() - if writeBarrier.enabled { - - gcmarknewobject(span, uintptr(x)) - } else { - - span.freeIndexForScan = span.freeindex - } + span.freeIndexForScan = span.freeindex c.nextSample -= int64(elemsize) if c.nextSample < 0 || MemProfileRate != c.memProfRate { diff --git a/src/runtime/malloc_stubs.go b/src/runtime/malloc_stubs.go index c19846b47a99ce..8320da3e4c7f49 100644 --- a/src/runtime/malloc_stubs.go +++ b/src/runtime/malloc_stubs.go @@ -256,7 +256,7 @@ func smallStub(size uintptr, typ *_type, needzero bool) unsafe.Pointer { // but see uninitialized memory or stale heap bits. publicationBarrier() - if writeBarrier.enabled { + if isSlowPath_ && writeBarrier.enabled { // Allocate black during GC. // All slots hold nil so no scanning is needed. // This may be racing with GC so do it atomically if there can be @@ -419,7 +419,7 @@ func tinyStub(size uintptr, typ *_type, needzero bool) unsafe.Pointer { // but see uninitialized memory or stale heap bits. publicationBarrier() - if writeBarrier.enabled { + if isSlowPath_ && writeBarrier.enabled { // Allocate black during GC. // All slots hold nil so no scanning is needed. // This may be racing with GC so do it atomically if there can be