From 0bc192368ac603614dc3f240e751f539d91a1db2 Mon Sep 17 00:00:00 2001 From: Michael Anthony Knyszek Date: Wed, 19 Nov 2025 03:51:20 +0000 Subject: [PATCH 001/140] runtime: don't write unique string to trace if it's length zero While we're here, document that ID 0 is implicitly assigned to an empty set of data for both stacks and strings. Change-Id: Ic52ff3a1132abc5a8f6f6c4e4357e31e6e7799fc Reviewed-on: https://go-review.googlesource.com/c/go/+/723061 LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Pratt Auto-Submit: Michael Knyszek --- src/runtime/tracemap.go | 7 +++++++ src/runtime/tracestack.go | 9 ++++++--- src/runtime/tracestring.go | 5 +++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/runtime/tracemap.go b/src/runtime/tracemap.go index 9efa325c112..1d6aabb4310 100644 --- a/src/runtime/tracemap.go +++ b/src/runtime/tracemap.go @@ -23,6 +23,13 @@ import ( "unsafe" ) +// traceMap is a map of a variable-sized array of bytes to a unique ID. +// +// Because traceMap just operates on raw bytes, this type is used as the +// backing store for both the trace string table and trace stack table, +// the latter of which is just an array of PCs. +// +// ID 0 is reserved for arrays of bytes of size zero. type traceMap struct { root atomic.UnsafePointer // *traceMapNode (can't use generics because it's notinheap) _ cpu.CacheLinePad diff --git a/src/runtime/tracestack.go b/src/runtime/tracestack.go index 51f3c29445c..0da217fba98 100644 --- a/src/runtime/tracestack.go +++ b/src/runtime/tracestack.go @@ -136,8 +136,9 @@ func traceStack(skip int, gp *g, tab *traceStackTable) uint64 { return id } -// traceStackTable maps stack traces (arrays of PC's) to unique uint32 ids. -// It is lock-free for reading. +// traceStackTable maps stack traces (arrays of PC's) to unique IDs. +// +// ID 0 is reserved for a zero-length stack. type traceStackTable struct { tab traceMap } @@ -145,8 +146,10 @@ type traceStackTable struct { // put returns a unique id for the stack trace pcs and caches it in the table, // if it sees the trace for the first time. func (t *traceStackTable) put(pcs []uintptr) uint64 { + // Even though put will handle this for us, taking the address of pcs forces a bounds check + // that will fail if len(pcs) == 0. if len(pcs) == 0 { - return 0 + return 0 // ID 0 is reserved for zero-length stacks. } id, _ := t.tab.put(noescape(unsafe.Pointer(&pcs[0])), uintptr(len(pcs))*unsafe.Sizeof(uintptr(0))) return id diff --git a/src/runtime/tracestring.go b/src/runtime/tracestring.go index d486f9efbdf..bd31f06a670 100644 --- a/src/runtime/tracestring.go +++ b/src/runtime/tracestring.go @@ -12,6 +12,8 @@ import "internal/trace/tracev2" // traceStringTable is map of string -> unique ID that also manages // writing strings out into the trace. +// +// ID 0 is reserved for the empty string. type traceStringTable struct { // lock protects buf. lock mutex @@ -37,6 +39,9 @@ func (t *traceStringTable) put(gen uintptr, s string) uint64 { // emit emits a string and creates an ID for it, but doesn't add it to the table. Returns the ID. func (t *traceStringTable) emit(gen uintptr, s string) uint64 { + if len(s) == 0 { + return 0 // Empty strings are implicitly assigned ID 0 already. + } // Grab an ID and write the string to the buffer. id := t.tab.stealID() systemstack(func() { From 7fbd141de506e331ef3f5910b505ece91a012e4a Mon Sep 17 00:00:00 2001 From: Michael Anthony Knyszek Date: Wed, 19 Nov 2025 23:42:06 +0000 Subject: [PATCH 002/140] runtime: use m.profStack in traceStack Turns out we spend a few percent of the trace event writing path in just zero-initializing the stack space for pcBuf. We don't need zero initialization, since we're going to write over whatever we actually use. Use m.profStack instead, which is already sized correctly. A side-effect of this change is that trace stacks now obey the GODEBUG profstackdepth where they previously ignored it. The name clearly doesn't match, but this is a positive: there's no reason the maximum stack depth shouldn't apply to every diagnostic. Change-Id: Ia654d3d708f15cbb2e1d95af196ae10b07a65df2 Reviewed-on: https://go-review.googlesource.com/c/go/+/723062 LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Pratt Auto-Submit: Michael Knyszek --- src/runtime/tracestack.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/tracestack.go b/src/runtime/tracestack.go index 0da217fba98..d3f217bbace 100644 --- a/src/runtime/tracestack.go +++ b/src/runtime/tracestack.go @@ -30,7 +30,7 @@ const ( // // Avoid calling this function directly. Prefer traceEventWriter.stack. func traceStack(skip int, gp *g, tab *traceStackTable) uint64 { - var pcBuf [tracev2.MaxFramesPerStack]uintptr + pcBuf := getg().m.profStack // Figure out gp and mp for the backtrace. var mp *m From f1e376f342af82d6f5bdba23cdc5c35b5bfd9064 Mon Sep 17 00:00:00 2001 From: Jes Cok Date: Fri, 21 Nov 2025 15:59:20 +0000 Subject: [PATCH 003/140] cmd/go/internal/auth: fix typo Change-Id: Ic113d59144aa2d37c8988559fbc086f5c29c0b69 GitHub-Last-Rev: e2623be0a00464d9be845da53fb1a67520fc1716 GitHub-Pull-Request: golang/go#76397 Reviewed-on: https://go-review.googlesource.com/c/go/+/722861 Reviewed-by: Michael Matloob Reviewed-by: Cherry Mui Reviewed-by: Michael Matloob LUCI-TryBot-Result: Go LUCI --- src/cmd/go/internal/auth/userauth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/go/internal/auth/userauth.go b/src/cmd/go/internal/auth/userauth.go index 2649a9c271d..44c0b3cff09 100644 --- a/src/cmd/go/internal/auth/userauth.go +++ b/src/cmd/go/internal/auth/userauth.go @@ -48,7 +48,7 @@ func runAuthCommand(command string, url string, res *http.Response) (map[string] // parseUserAuth parses the output from a GOAUTH command and // returns a mapping of prefix → http.Header without the leading "https://" // or an error if the data does not follow the expected format. -// Returns an nil error and an empty map if the data is empty. +// Returns a nil error and an empty map if the data is empty. // See the expected format in 'go help goauth'. func parseUserAuth(data string) (map[string]http.Header, error) { credentials := make(map[string]http.Header) From 62cd044a79b9f2ba889bca59b3b12400dc41dd85 Mon Sep 17 00:00:00 2001 From: David Chase Date: Mon, 24 Nov 2025 15:00:11 -0500 Subject: [PATCH 004/140] cmd/compile: add cases for StringLen to prove Tricky index-offset logic had been added for slices, but not for strings. This fixes that, and also adds tests for same behavior in string/slice cases, and adds a new test for code in prove that had been added but not explicitly tested. Fixes #76270. Change-Id: Ibd92b89e944d86b7f30b4486a9008e6f1ac6af7d Reviewed-on: https://go-review.googlesource.com/c/go/+/723980 LUCI-TryBot-Result: Go LUCI Reviewed-by: Keith Randall Reviewed-by: Keith Randall --- src/cmd/compile/internal/ssa/prove.go | 14 +++--- test/loopbce.go | 2 +- test/prove.go | 65 +++++++++++++++++++++++---- 3 files changed, 65 insertions(+), 16 deletions(-) diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go index 5581da445d8..536965a0a0a 100644 --- a/src/cmd/compile/internal/ssa/prove.go +++ b/src/cmd/compile/internal/ssa/prove.go @@ -2040,14 +2040,14 @@ func (ft *factsTable) flowLimit(v *Value) { // // slicecap - index >= slicelen - index >= K // -// Note that "index" is not useed for indexing in this pattern, but +// Note that "index" is not used for indexing in this pattern, but // in the motivating example (chunked slice iteration) it is. func (ft *factsTable) detectSliceLenRelation(v *Value) { if v.Op != OpSub64 { return } - if !(v.Args[0].Op == OpSliceLen || v.Args[0].Op == OpSliceCap) { + if !(v.Args[0].Op == OpSliceLen || v.Args[0].Op == OpStringLen || v.Args[0].Op == OpSliceCap) { return } @@ -2070,9 +2070,9 @@ func (ft *factsTable) detectSliceLenRelation(v *Value) { continue } var lenOffset *Value - if bound := ow.Args[0]; bound.Op == OpSliceLen && bound.Args[0] == slice { + if bound := ow.Args[0]; (bound.Op == OpSliceLen || bound.Op == OpStringLen) && bound.Args[0] == slice { lenOffset = ow.Args[1] - } else if bound := ow.Args[1]; bound.Op == OpSliceLen && bound.Args[0] == slice { + } else if bound := ow.Args[1]; (bound.Op == OpSliceLen || bound.Op == OpStringLen) && bound.Args[0] == slice { lenOffset = ow.Args[0] } if lenOffset == nil || lenOffset.Op != OpConst64 { @@ -2332,7 +2332,7 @@ func unsignedSubUnderflows(a, b uint64) bool { // iteration where the index is not directly compared to the length. // if isReslice, then delta can be equal to K. func checkForChunkedIndexBounds(ft *factsTable, b *Block, index, bound *Value, isReslice bool) bool { - if bound.Op != OpSliceLen && bound.Op != OpSliceCap { + if bound.Op != OpSliceLen && bound.Op != OpStringLen && bound.Op != OpSliceCap { return false } @@ -2367,9 +2367,9 @@ func checkForChunkedIndexBounds(ft *factsTable, b *Block, index, bound *Value, i } if ow := o.w; ow.Op == OpAdd64 { var lenOffset *Value - if bound := ow.Args[0]; bound.Op == OpSliceLen && bound.Args[0] == slice { + if bound := ow.Args[0]; (bound.Op == OpSliceLen || bound.Op == OpStringLen) && bound.Args[0] == slice { lenOffset = ow.Args[1] - } else if bound := ow.Args[1]; bound.Op == OpSliceLen && bound.Args[0] == slice { + } else if bound := ow.Args[1]; (bound.Op == OpSliceLen || bound.Op == OpStringLen) && bound.Args[0] == slice { lenOffset = ow.Args[0] } if lenOffset == nil || lenOffset.Op != OpConst64 { diff --git a/test/loopbce.go b/test/loopbce.go index 8a58d942361..aabd56c682b 100644 --- a/test/loopbce.go +++ b/test/loopbce.go @@ -420,7 +420,7 @@ func nobce2(a string) { useString(a[i:]) // ERROR "Proved IsSliceInBounds$" } for i := int64(0); i < int64(len(a))-31337; i++ { // ERROR "Induction variable: limits \[0,\?\), increment 1$" - useString(a[i:]) // ERROR "Proved IsSliceInBounds$" + useString(a[i:]) // ERROR "Proved IsSliceInBounds$" "Proved slicemask not needed" } for i := int64(0); i < int64(len(a))+int64(-1<<63); i++ { // ERROR "Disproved Less64$" "Induction variable: limits \[0,\?\), increment 1$" useString(a[i:]) diff --git a/test/prove.go b/test/prove.go index e440634813e..1b50317fe3e 100644 --- a/test/prove.go +++ b/test/prove.go @@ -2659,14 +2659,63 @@ func subLengths2(b []byte, i int) { } func issue76355(s []int, i int) int { - var a [10]int - if i <= len(s)-1 { - v := len(s) - i - if v < 10 { - return a[v] - } - } - return 0 + var a [10]int + if i <= len(s)-1 { + v := len(s) - i + if v < 10 { + return a[v] + } + } + return 0 +} + +func stringDotDotDot(s string) bool { + for i := 0; i < len(s)-2; i++ { // ERROR "Induction variable: limits \[0,[?][)], increment 1" + if s[i] == '.' && // ERROR "Proved IsInBounds" + s[i+1] == '.' && // ERROR "Proved IsInBounds" + s[i+2] == '.' { // ERROR "Proved IsInBounds" + return true + } + } + return false +} + +func bytesDotDotDot(s []byte) bool { + for i := 0; i < len(s)-2; i++ { // ERROR "Induction variable" + if s[i] == '.' && // ERROR "Proved IsInBounds" + s[i+1] == '.' && // ERROR "Proved IsInBounds" + s[i+2] == '.' { // ERROR "Proved IsInBounds" + return true + } + } + return false +} + +// detectSliceLenRelation matches the pattern where +// 1. v := slicelen - index, OR v := slicecap - index +// AND +// 2. index <= slicelen - K +// THEN +// +// slicecap - index >= slicelen - index >= K +func detectSliceLenRelation(s []byte) bool { + for i := 0; i <= len(s)-3; i++ { // ERROR "Induction variable" + v := len(s) - i + if v >= 3 { // ERROR "Proved Leq" + return true + } + } + return false +} + +func detectStringLenRelation(s string) bool { + for i := 0; i <= len(s)-3; i++ { // ERROR "Induction variable" + v := len(s) - i + if v >= 3 { // ERROR "Proved Leq" + return true + } + } + return false } //go:noinline From 97d5295f6fcbc4c24225096900c22773d6672cce Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 7 Nov 2025 11:36:05 -0500 Subject: [PATCH 005/140] crypto/internal/fips140test: add ML-DSA coverage This commit integrates ML-DSA ACVP test coverage, describing the capabilities of the crypto/internal/fips140/mldsa package and adding the required command handlers to our ACVP module wrapper. Change-Id: I2aee6f169752a6c6fec3a68591dde33e4f308081 Reviewed-on: https://go-review.googlesource.com/c/go/+/719703 LUCI-TryBot-Result: Go LUCI Reviewed-by: Roland Shoemaker Auto-Submit: Daniel McCarney Reviewed-by: Cherry Mui Reviewed-by: Filippo Valsorda --- ...son => acvp_capabilities_fips140v1.0.json} | 0 .../acvp_capabilities_fips140v2.0.json | 86 ++++++++++++ .../fips140test/acvp_fips140v1.0_test.go | 14 ++ .../fips140test/acvp_fips140v2.0_test.go | 126 ++++++++++++++++++ src/crypto/internal/fips140test/acvp_test.go | 46 +------ ...json => acvp_test_fips140v1.0.config.json} | 0 .../acvp_test_fips140v2.0.config.json | 58 ++++++++ 7 files changed, 287 insertions(+), 43 deletions(-) rename src/crypto/internal/fips140test/{acvp_capabilities.json => acvp_capabilities_fips140v1.0.json} (100%) create mode 100644 src/crypto/internal/fips140test/acvp_capabilities_fips140v2.0.json create mode 100644 src/crypto/internal/fips140test/acvp_fips140v1.0_test.go create mode 100644 src/crypto/internal/fips140test/acvp_fips140v2.0_test.go rename src/crypto/internal/fips140test/{acvp_test.config.json => acvp_test_fips140v1.0.config.json} (100%) create mode 100644 src/crypto/internal/fips140test/acvp_test_fips140v2.0.config.json diff --git a/src/crypto/internal/fips140test/acvp_capabilities.json b/src/crypto/internal/fips140test/acvp_capabilities_fips140v1.0.json similarity index 100% rename from src/crypto/internal/fips140test/acvp_capabilities.json rename to src/crypto/internal/fips140test/acvp_capabilities_fips140v1.0.json diff --git a/src/crypto/internal/fips140test/acvp_capabilities_fips140v2.0.json b/src/crypto/internal/fips140test/acvp_capabilities_fips140v2.0.json new file mode 100644 index 00000000000..33c8aa235b4 --- /dev/null +++ b/src/crypto/internal/fips140test/acvp_capabilities_fips140v2.0.json @@ -0,0 +1,86 @@ +[ + {"algorithm":"SHA2-224","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"1.0"}, + {"algorithm":"SHA2-256","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"1.0"}, + {"algorithm":"SHA2-384","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"1.0"}, + {"algorithm":"SHA2-512","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"1.0"}, + {"algorithm":"SHA2-512/224","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"1.0"}, + {"algorithm":"SHA2-512/256","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"1.0"}, + + {"algorithm":"SHA3-224","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"2.0"}, + {"algorithm":"SHA3-256","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"2.0"}, + {"algorithm":"SHA3-384","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"2.0"}, + {"algorithm":"SHA3-512","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"2.0"}, + + {"algorithm":"SHAKE-128","inBit":false,"outBit":false,"inEmpty":true,"outputLen":[{"min":16,"max":65536,"increment":8}],"revision":"1.0"}, + {"algorithm":"SHAKE-256","inBit":false,"outBit":false,"inEmpty":true,"outputLen":[{"min":16,"max":65536,"increment":8}],"revision":"1.0"}, + {"algorithm":"cSHAKE-128","hexCustomization":false,"outputLen":[{"min":16,"max":65536,"increment":8}],"msgLen":[{"min":0,"max":65536,"increment":8}],"revision":"1.0"}, + {"algorithm":"cSHAKE-256","hexCustomization":false,"outputLen":[{"min":16,"max":65536,"increment":8}],"msgLen":[{"min":0,"max":65536,"increment":8}],"revision":"1.0"}, + + {"algorithm":"HMAC-SHA2-224","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[224],"revision":"1.0"}, + {"algorithm":"HMAC-SHA2-256","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[256],"revision":"1.0"}, + {"algorithm":"HMAC-SHA2-384","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[384],"revision":"1.0"}, + {"algorithm":"HMAC-SHA2-512","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[512],"revision":"1.0"}, + {"algorithm":"HMAC-SHA2-512/224","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[224],"revision":"1.0"}, + {"algorithm":"HMAC-SHA2-512/256","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[256],"revision":"1.0"}, + + {"algorithm":"HMAC-SHA3-224","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[224],"revision":"1.0"}, + {"algorithm":"HMAC-SHA3-256","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[256],"revision":"1.0"}, + {"algorithm":"HMAC-SHA3-384","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[384],"revision":"1.0"}, + {"algorithm":"HMAC-SHA3-512","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[512],"revision":"1.0"}, + + {"algorithm":"KDA","mode":"HKDF","revision":"Sp800-56Cr1","fixedInfoPattern":"uPartyInfo||vPartyInfo","encoding":["concatenation"],"hmacAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"],"macSaltMethods":["default","random"],"l":2048,"z":[{"min":224,"max":65336,"increment":8}]}, + {"algorithm":"KDA","mode":"OneStepNoCounter","revision":"Sp800-56Cr2","auxFunctions":[{"auxFunctionName":"HMAC-SHA2-224","l":224,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA2-256","l":256,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA2-384","l":384,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA2-512","l":512,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA2-512/224","l":224,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA2-512/256","l":256,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA3-224","l":224,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA3-256","l":256,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA3-384","l":384,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA3-512","l":512,"macSaltMethods":["default","random"]}],"fixedInfoPattern":"uPartyInfo||vPartyInfo","encoding":["concatenation"],"z":[{"min":224,"max":65336,"increment":8}]}, + + {"algorithm":"PBKDF","capabilities":[{"iterationCount":[{"min":1,"max":10000,"increment":1}],"keyLen":[{"min":112,"max":4096,"increment":8}],"passwordLen":[{"min":8,"max":64,"increment":1}],"saltLen":[{"min":128,"max":512,"increment":8}],"hmacAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"]}],"revision":"1.0"}, + + {"algorithm":"ML-KEM","mode":"keyGen","revision":"FIPS203","parameterSets":["ML-KEM-768","ML-KEM-1024"]}, + {"algorithm":"ML-KEM","mode":"encapDecap","revision":"FIPS203","parameterSets":["ML-KEM-768","ML-KEM-1024"],"functions":["encapsulation","decapsulation"]}, + + {"algorithm":"ML-DSA","mode":"keyGen","revision":"FIPS204","parameterSets":["ML-DSA-44","ML-DSA-65","ML-DSA-87"]}, + {"algorithm":"ML-DSA","mode":"sigGen","revision":"FIPS204","signatureInterfaces":["internal","external"],"preHash":["pure"],"deterministic":[true,false],"externalMu":[true],"capabilities":[{"parameterSets":["ML-DSA-44","ML-DSA-65","ML-DSA-87"],"messageLength":[{"min":8,"max":65536,"increment":8}],"contextLength":[{"min":0,"max":2040,"increment":8}]}]}, + {"algorithm":"ML-DSA","mode":"sigVer","revision":"FIPS204","signatureInterfaces":["internal","external"],"externalMu":[true],"preHash":["pure"],"capabilities":[{"parameterSets":["ML-DSA-44","ML-DSA-65","ML-DSA-87"],"messageLength":[{"min":8,"max":65536,"increment":8}],"contextLength":[{"min":0,"max":2040,"increment":8}]}]}, + + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA2-224","derFuncEnabled":false,"entropyInputLen":[192],"nonceLen":[96],"persoStringLen":[192],"additionalInputLen":[0],"returnedBitsLen":224}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA2-256","derFuncEnabled":false,"entropyInputLen":[256],"nonceLen":[128],"persoStringLen":[256],"additionalInputLen":[0],"returnedBitsLen":256}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA2-384","derFuncEnabled":false,"entropyInputLen":[256],"nonceLen":[128],"persoStringLen":[256],"additionalInputLen":[0],"returnedBitsLen":384}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA2-512","derFuncEnabled":false,"entropyInputLen":[256],"nonceLen":[128],"persoStringLen":[256],"additionalInputLen":[0],"returnedBitsLen":512}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA2-512/224","derFuncEnabled":false,"entropyInputLen":[192],"nonceLen":[96],"persoStringLen":[192],"additionalInputLen":[0],"returnedBitsLen":224}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA2-512/256","derFuncEnabled":false,"entropyInputLen":[256],"nonceLen":[128],"persoStringLen":[256],"additionalInputLen":[0],"returnedBitsLen":256}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA3-224","derFuncEnabled":false,"entropyInputLen":[192],"nonceLen":[96],"persoStringLen":[192],"additionalInputLen":[0],"returnedBitsLen":224}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA3-256","derFuncEnabled":false,"entropyInputLen":[256],"nonceLen":[128],"persoStringLen":[256],"additionalInputLen":[0],"returnedBitsLen":256}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA3-384","derFuncEnabled":false,"entropyInputLen":[256],"nonceLen":[128],"persoStringLen":[256],"additionalInputLen":[0],"returnedBitsLen":384}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA3-512","derFuncEnabled":false,"entropyInputLen":[256],"nonceLen":[128],"persoStringLen":[256],"additionalInputLen":[0],"returnedBitsLen":512}]}, + + {"algorithm":"ctrDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":true,"capabilities":[{"mode":"AES-256","derFuncEnabled":false,"entropyInputLen":[384],"nonceLen":[0],"persoStringLen":[0],"additionalInputLen":[384],"returnedBitsLen":128}]}, + + {"algorithm":"EDDSA","mode":"keyGen","revision":"1.0","curve":["ED-25519"]}, + {"algorithm":"EDDSA","mode":"keyVer","revision":"1.0","curve":["ED-25519"]}, + {"algorithm":"EDDSA","mode":"sigGen","revision":"1.0","pure":true,"preHash":true,"contextLength":[{"min":0,"max":255,"increment":1}],"curve":["ED-25519"]}, + {"algorithm":"EDDSA","mode":"sigVer","revision":"1.0","pure":true,"preHash":true,"curve":["ED-25519"]}, + + {"algorithm":"ECDSA","mode":"keyGen","revision":"FIPS186-5","curve":["P-224","P-256","P-384","P-521"],"secretGenerationMode":["testing candidates"]}, + {"algorithm":"ECDSA","mode":"keyVer","revision":"FIPS186-5","curve":["P-224","P-256","P-384","P-521"]}, + {"algorithm":"ECDSA","mode":"sigGen","revision":"FIPS186-5","capabilities":[{"curve":["P-224","P-256","P-384","P-521"],"hashAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"]}]}, + {"algorithm":"ECDSA","mode":"sigVer","revision":"FIPS186-5","capabilities":[{"curve":["P-224","P-256","P-384","P-521"],"hashAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"]}]}, + {"algorithm":"DetECDSA","mode":"sigGen","revision":"FIPS186-5","capabilities":[{"curve":["P-224","P-256","P-384","P-521"],"hashAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"]}]}, + + {"algorithm":"ACVP-AES-CBC","direction":["encrypt","decrypt"],"keyLen":[128,192,256],"revision":"1.0"}, + {"algorithm":"ACVP-AES-CTR","direction":["encrypt","decrypt"],"keyLen":[128,192,256],"payloadLen":[{"min":8,"max":128,"increment":8}],"incrementalCounter":true,"overflowCounter":true,"performCounterTests":true,"revision":"1.0"}, + {"algorithm":"ACVP-AES-GCM","direction":["encrypt","decrypt"],"keyLen":[128,192,256],"payloadLen":[{"min":0,"max":65536,"increment":8}],"aadLen":[{"min":0,"max":65536,"increment":8}],"tagLen":[96,104,112,120,128],"ivLen":[96],"ivGen":"external","revision":"1.0"}, + {"algorithm":"ACVP-AES-GCM","direction":["encrypt","decrypt"],"keyLen":[128,192,256],"payloadLen":[{"min":0,"max":65536,"increment":8}],"aadLen":[{"min":0,"max":65536,"increment":8}],"tagLen":[128],"ivLen":[96],"ivGen":"internal","ivGenMode":"8.2.2","revision":"1.0"}, + {"algorithm":"CMAC-AES","capabilities":[{"direction":["gen","ver"],"msgLen":[{"min":0,"max":524288,"increment":8}],"keyLen":[128,256],"macLen":[128]}],"revision":"1.0"}, + + {"algorithm":"TLS-v1.2","mode":"KDF","revision":"RFC7627","hashAlg":["SHA2-256","SHA2-384","SHA2-512"]}, + {"algorithm":"TLS-v1.3","mode":"KDF","revision":"RFC8446","hmacAlg":["SHA2-256","SHA2-384"],"runningMode":["DHE","PSK","PSK-DHE"]}, + {"algorithm":"kdf-components","mode":"ssh","revision":"1.0","hashAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512"],"cipher":["AES-128","AES-192","AES-256"]}, + + {"algorithm":"KAS-ECC-SSC","revision":"Sp800-56Ar3","scheme":{"ephemeralUnified":{"kasRole":["initiator","responder"]},"staticUnified":{"kasRole":["initiator","responder"]}},"domainParameterGenerationMethods":["P-224","P-256","P-384","P-521"]}, + + {"algorithm":"KDF","revision":"1.0","capabilities":[{"kdfMode":"counter","macMode":["CMAC-AES128","CMAC-AES192","CMAC-AES256"],"supportedLengths":[256],"fixedDataOrder":["before fixed data"],"counterLength":[16]},{"kdfMode":"feedback","macMode":["HMAC-SHA2-224","HMAC-SHA2-256","HMAC-SHA2-384","HMAC-SHA2-512","HMAC-SHA2-512/224","HMAC-SHA2-512/256","HMAC-SHA3-224","HMAC-SHA3-256","HMAC-SHA3-384","HMAC-SHA3-512"],"customKeyInLength":0,"supportedLengths":[{"min":8,"max":4096,"increment":8}],"fixedDataOrder":["after fixed data"],"counterLength":[8],"supportsEmptyIv":true,"requiresEmptyIv":true}]}, + + {"algorithm":"RSA","mode":"keyGen","revision":"FIPS186-5","infoGeneratedByServer":true,"pubExpMode":"fixed","fixedPubExp":"010001","keyFormat":"standard","capabilities":[{"randPQ":"probable","properties":[{"modulo":2048,"primeTest":["2powSecStr"]},{"modulo":3072,"primeTest":["2powSecStr"]},{"modulo":4096,"primeTest":["2powSecStr"]}]}]}, + {"algorithm":"RSA","mode":"sigGen","revision":"FIPS186-5","capabilities":[{"sigType":"pkcs1v1.5","properties":[{"modulo":2048,"hashPair":[{"hashAlg":"SHA2-224"},{"hashAlg":"SHA2-256"},{"hashAlg":"SHA2-384"},{"hashAlg":"SHA2-512"}]},{"modulo":3072,"hashPair":[{"hashAlg":"SHA2-224"},{"hashAlg":"SHA2-256"},{"hashAlg":"SHA2-384"},{"hashAlg":"SHA2-512"}]},{"modulo":4096,"hashPair":[{"hashAlg":"SHA2-224"},{"hashAlg":"SHA2-256"},{"hashAlg":"SHA2-384"},{"hashAlg":"SHA2-512"}]}]},{"sigType":"pss","properties":[{"maskFunction":["mgf1"],"modulo":2048,"hashPair":[{"hashAlg":"SHA2-224","saltLen":28},{"hashAlg":"SHA2-256","saltLen":32},{"hashAlg":"SHA2-384","saltLen":48},{"hashAlg":"SHA2-512","saltLen":64}]},{"maskFunction":["mgf1"],"modulo":3072,"hashPair":[{"hashAlg":"SHA2-224","saltLen":28},{"hashAlg":"SHA2-256","saltLen":32},{"hashAlg":"SHA2-384","saltLen":48},{"hashAlg":"SHA2-512","saltLen":64}]},{"maskFunction":["mgf1"],"modulo":4096,"hashPair":[{"hashAlg":"SHA2-224","saltLen":28},{"hashAlg":"SHA2-256","saltLen":32},{"hashAlg":"SHA2-384","saltLen":48},{"hashAlg":"SHA2-512","saltLen":64}]}]}]}, + {"algorithm":"RSA","mode":"sigVer","revision":"FIPS186-5","pubExpMode":"fixed","fixedPubExp":"010001","capabilities":[{"sigType":"pkcs1v1.5","properties":[{"modulo":2048,"hashPair":[{"hashAlg":"SHA2-224"},{"hashAlg":"SHA2-256"},{"hashAlg":"SHA2-384"},{"hashAlg":"SHA2-512"}]}]},{"sigType":"pkcs1v1.5","properties":[{"modulo":3072,"hashPair":[{"hashAlg":"SHA2-224"},{"hashAlg":"SHA2-256"},{"hashAlg":"SHA2-384"},{"hashAlg":"SHA2-512"}]}]},{"sigType":"pkcs1v1.5","properties":[{"modulo":4096,"hashPair":[{"hashAlg":"SHA2-224"},{"hashAlg":"SHA2-256"},{"hashAlg":"SHA2-384"},{"hashAlg":"SHA2-512"}]}]},{"sigType":"pss","properties":[{"maskFunction":["mgf1"],"modulo":2048,"hashPair":[{"hashAlg":"SHA2-224","saltLen":28},{"hashAlg":"SHA2-256","saltLen":32},{"hashAlg":"SHA2-384","saltLen":48},{"hashAlg":"SHA2-512","saltLen":64}]}]},{"sigType":"pss","properties":[{"maskFunction":["mgf1"],"modulo":3072,"hashPair":[{"hashAlg":"SHA2-224","saltLen":28},{"hashAlg":"SHA2-256","saltLen":32},{"hashAlg":"SHA2-384","saltLen":48},{"hashAlg":"SHA2-512","saltLen":64}]}]},{"sigType":"pss","properties":[{"maskFunction":["mgf1"],"modulo":4096,"hashPair":[{"hashAlg":"SHA2-224","saltLen":28},{"hashAlg":"SHA2-256","saltLen":32},{"hashAlg":"SHA2-384","saltLen":48},{"hashAlg":"SHA2-512","saltLen":64}]}]}]}, + + {"algorithm":"KTS-IFC","revision":"Sp800-56Br2","fixedPubExp":"010001","iutId":"C0FFEE","modulo":[2048,3072,4096],"keyGenerationMethods":["rsakpg1-basic"],"scheme":{"KTS-OAEP-basic":{"l":1024,"kasRole":["responder","initiator"],"ktsMethod":{"hashAlgs":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"],"supportsNullAssociatedData":true,"encoding":["concatenation"]}}}} +] diff --git a/src/crypto/internal/fips140test/acvp_fips140v1.0_test.go b/src/crypto/internal/fips140test/acvp_fips140v1.0_test.go new file mode 100644 index 00000000000..4beff3094da --- /dev/null +++ b/src/crypto/internal/fips140test/acvp_fips140v1.0_test.go @@ -0,0 +1,14 @@ +// Copyright 2025 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 fips140v1.0 + +package fipstest + +import _ "embed" + +//go:embed acvp_capabilities_fips140v1.0.json +var capabilitiesJson []byte + +var testConfigFile = "acvp_test_fips140v1.0.config.json" diff --git a/src/crypto/internal/fips140test/acvp_fips140v2.0_test.go b/src/crypto/internal/fips140test/acvp_fips140v2.0_test.go new file mode 100644 index 00000000000..e9ef91537a9 --- /dev/null +++ b/src/crypto/internal/fips140test/acvp_fips140v2.0_test.go @@ -0,0 +1,126 @@ +// Copyright 2025 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 !fips140v1.0 + +package fipstest + +import ( + "crypto/internal/fips140/mldsa" + _ "embed" + "fmt" +) + +//go:embed acvp_capabilities_fips140v2.0.json +var capabilitiesJson []byte + +var testConfigFile = "acvp_test_fips140v2.0.config.json" + +func init() { + commands["ML-DSA-44/keyGen"] = cmdMlDsaKeyGenAft(mldsa.NewPrivateKey44) + commands["ML-DSA-65/keyGen"] = cmdMlDsaKeyGenAft(mldsa.NewPrivateKey65) + commands["ML-DSA-87/keyGen"] = cmdMlDsaKeyGenAft(mldsa.NewPrivateKey87) + commands["ML-DSA-44/sigGen"] = cmdMlDsaSigGenAft() + commands["ML-DSA-65/sigGen"] = cmdMlDsaSigGenAft() + commands["ML-DSA-87/sigGen"] = cmdMlDsaSigGenAft() + commands["ML-DSA-44/sigVer"] = cmdMlDsaSigVerAft(mldsa.NewPublicKey44) + commands["ML-DSA-65/sigVer"] = cmdMlDsaSigVerAft(mldsa.NewPublicKey65) + commands["ML-DSA-87/sigVer"] = cmdMlDsaSigVerAft(mldsa.NewPublicKey87) +} + +func cmdMlDsaKeyGenAft(keyGen func([]byte) (*mldsa.PrivateKey, error)) command { + return command{ + requiredArgs: 1, // Seed + handler: func(args [][]byte) ([][]byte, error) { + seed := args[0] + + sk, err := keyGen(seed) + if err != nil { + return nil, fmt.Errorf("generating ML-DSA 44 private key: %w", err) + } + + // Important: we must return the full encoding of sk, not the seed. + return [][]byte{sk.PublicKey().Bytes(), mldsa.TestingOnlyPrivateKeySemiExpandedBytes(sk)}, nil + }, + } +} + +func cmdMlDsaSigGenAft() command { + return command{ + requiredArgs: 5, // secret key, message, randomizer, mu, context + handler: func(args [][]byte) ([][]byte, error) { + skSmiExpanded := args[0] + message := args[1] // Optional, exclusive with mu + randomizer := args[2] // Optional + context := string(args[3]) // Optional + mu := args[4] // Optional, exclusive with message + + sk, err := mldsa.TestingOnlyNewPrivateKeyFromSemiExpanded(skSmiExpanded) + if err != nil { + return nil, fmt.Errorf("making ML-DSA private key from semi-expanded form: %w", err) + } + + haveMessage := len(message) != 0 + haveRandomizer := len(randomizer) != 0 + haveMu := len(mu) != 0 + + var sig []byte + if haveMessage && !haveRandomizer && !haveMu { + sig, err = mldsa.SignDeterministic(sk, message, context) + } else if haveMessage && haveRandomizer && !haveMu { + sig, err = mldsa.TestingOnlySignWithRandom(sk, message, context, randomizer) + } else if !haveMessage && !haveRandomizer && haveMu { + sig, err = mldsa.SignExternalMuDeterministic(sk, mu) + } else if !haveMessage && haveRandomizer && haveMu { + sig, err = mldsa.TestingOnlySignExternalMuWithRandom(sk, mu, randomizer) + } else { + return nil, fmt.Errorf( + "unsupported ML-DSA sigGen args: have message=%v have randomizer=%v haveMu=%v haveContext=%v", + haveMessage, haveRandomizer, haveMu, len(context) != 0) + } + + if err != nil { + return nil, fmt.Errorf("creating deterministic ML-DSA signature: %w", err) + } + + return [][]byte{sig}, nil + }, + } +} + +func cmdMlDsaSigVerAft(pubKey func([]byte) (*mldsa.PublicKey, error)) command { + return command{ + requiredArgs: 5, // public key, message, signature, context, mu + handler: func(args [][]byte) ([][]byte, error) { + pkRaw := args[0] + message := args[1] // Optional, exclusive with mu + signature := args[2] + context := string(args[3]) // Optional + mu := args[4] // Optional, exclusive with message + + pk, err := pubKey(pkRaw) + if err != nil { + return nil, fmt.Errorf("loading ML-DSA public key: %w", err) + } + + haveMessage := len(message) != 0 + haveMu := len(mu) != 0 + if haveMessage && !haveMu { + err = mldsa.Verify(pk, message, signature, context) + } else if !haveMessage && haveMu { + err = mldsa.VerifyExternalMu(pk, mu, signature) + } else { + return nil, fmt.Errorf( + "unsupported ML-DSA sigVer args: have message=%v haveMu=%v haveContext=%v", + haveMessage, haveMu, len(context) != 0) + } + + if err != nil { + return [][]byte{{0}}, nil + } + + return [][]byte{{1}}, nil + }, + } +} diff --git a/src/crypto/internal/fips140test/acvp_test.go b/src/crypto/internal/fips140test/acvp_test.go index 5affb8ce2b6..05dde352191 100644 --- a/src/crypto/internal/fips140test/acvp_test.go +++ b/src/crypto/internal/fips140test/acvp_test.go @@ -120,46 +120,6 @@ const ( ) var ( - // SHA2 algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html#section-7.2 - // SHA3 and SHAKE algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-celi-acvp-sha3.html#name-sha3-and-shake-algorithm-ca - // cSHAKE algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-celi-acvp-xof.html#section-7.2 - // HMAC algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-fussell-acvp-mac.html#section-7 - // PBKDF2 algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-celi-acvp-pbkdf.html#section-7.3 - // ML-KEM algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-celi-acvp-ml-kem.html#section-7.3 - // HMAC DRBG algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#section-7.2 - // EDDSA algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-celi-acvp-eddsa.html#section-7 - // ECDSA and DetECDSA algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-fussell-acvp-ecdsa.html#section-7 - // AES algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-celi-acvp-symmetric.html#section-7.3 - // HKDF KDA algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-hammett-acvp-kas-kdf-hkdf.html#section-7.3 - // OneStepNoCounter KDA algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-hammett-acvp-kas-kdf-onestepnocounter.html#section-7.2 - // TLS 1.2 KDF algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-celi-acvp-kdf-tls.html#section-7.2 - // TLS 1.3 KDF algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-hammett-acvp-kdf-tls-v1.3.html#section-7.2 - // SSH KDF algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-celi-acvp-kdf-ssh.html#section-7.2 - // ECDH algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-hammett-acvp-kas-ssc-ecc.html#section-7.3 - // HMAC DRBG and CTR DRBG algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#section-7.2 - // KDF-Counter and KDF-Feedback algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-celi-acvp-kbkdf.html#section-7.3 - // RSA algorithm capabilities: - // https://pages.nist.gov/ACVP/draft-celi-acvp-rsa.html#section-7.3 - //go:embed acvp_capabilities.json - capabilitiesJson []byte // Separate capabilities specific to testing the entropy source's SHA2-384 implementation. // This implementation differs from the FIPS module's SHA2-384 in its supported input sizes. @@ -2157,7 +2117,7 @@ func TestACVP(t *testing.T) { // Stat the acvp test config file so the test will be re-run if it changes, invalidating cached results // from the old config. - if _, err := os.Stat("acvp_test.config.json"); err != nil { + if _, err := os.Stat(testConfigFile); err != nil { t.Fatalf("failed to stat config file: %s", err) } @@ -2187,7 +2147,7 @@ func TestACVP(t *testing.T) { if err != nil { t.Fatalf("failed to fetch cwd: %s", err) } - configPath := filepath.Join(cwd, "acvp_test.config.json") + configPath := filepath.Join(cwd, testConfigFile) t.Logf("running check_expected.go\ncwd: %q\ndata_dir: %q\nconfig: %q\ntool: %q\nmodule-wrapper: %q\n", cwd, dataDir, configPath, toolPath, os.Args[0]) @@ -2199,7 +2159,7 @@ func TestACVP(t *testing.T) { filepath.Join(bsslDir, "util/fipstools/acvp/acvptool/test/check_expected.go"), "-tool", toolPath, - // Note: module prefix must match Wrapper value in acvp_test.config.json. + // Note: module prefix must match Wrapper value in testConfigFile. "-module-wrappers", "go:" + os.Args[0], "-tests", configPath, } diff --git a/src/crypto/internal/fips140test/acvp_test.config.json b/src/crypto/internal/fips140test/acvp_test_fips140v1.0.config.json similarity index 100% rename from src/crypto/internal/fips140test/acvp_test.config.json rename to src/crypto/internal/fips140test/acvp_test_fips140v1.0.config.json diff --git a/src/crypto/internal/fips140test/acvp_test_fips140v2.0.config.json b/src/crypto/internal/fips140test/acvp_test_fips140v2.0.config.json new file mode 100644 index 00000000000..51c76d92889 --- /dev/null +++ b/src/crypto/internal/fips140test/acvp_test_fips140v2.0.config.json @@ -0,0 +1,58 @@ +[ + {"Wrapper": "go", "In": "vectors/SHA2-224.bz2", "Out": "expected/SHA2-224.bz2"}, + {"Wrapper": "go", "In": "vectors/SHA2-256.bz2", "Out": "expected/SHA2-256.bz2"}, + {"Wrapper": "go", "In": "vectors/SHA2-384.bz2", "Out": "expected/SHA2-384.bz2"}, + {"Wrapper": "go", "In": "vectors/SHA2-512.bz2", "Out": "expected/SHA2-512.bz2"}, + {"Wrapper": "go", "In": "vectors/SHA2-512-224.bz2", "Out": "expected/SHA2-512-224.bz2"}, + {"Wrapper": "go", "In": "vectors/SHA2-512-256.bz2", "Out": "expected/SHA2-512-256.bz2"}, + + {"Wrapper": "go", "In": "vectors/SHA3-224.bz2", "Out": "expected/SHA3-224.bz2"}, + {"Wrapper": "go", "In": "vectors/SHA3-256.bz2", "Out": "expected/SHA3-256.bz2"}, + {"Wrapper": "go", "In": "vectors/SHA3-384.bz2", "Out": "expected/SHA3-384.bz2"}, + {"Wrapper": "go", "In": "vectors/SHA3-512.bz2", "Out": "expected/SHA3-512.bz2"}, + + {"Wrapper": "go", "In": "vectors/SHAKE-128.bz2", "Out": "expected/SHAKE-128.bz2"}, + {"Wrapper": "go", "In": "vectors/SHAKE-256.bz2", "Out": "expected/SHAKE-256.bz2"}, + {"Wrapper": "go", "In": "vectors/cSHAKE-128.bz2", "Out": "expected/cSHAKE-128.bz2"}, + {"Wrapper": "go", "In": "vectors/cSHAKE-256.bz2", "Out": "expected/cSHAKE-256.bz2"}, + + {"Wrapper": "go", "In": "vectors/HMAC-SHA2-224.bz2", "Out": "expected/HMAC-SHA2-224.bz2"}, + {"Wrapper": "go", "In": "vectors/HMAC-SHA2-256.bz2", "Out": "expected/HMAC-SHA2-256.bz2"}, + {"Wrapper": "go", "In": "vectors/HMAC-SHA2-384.bz2", "Out": "expected/HMAC-SHA2-384.bz2"}, + {"Wrapper": "go", "In": "vectors/HMAC-SHA2-512.bz2", "Out": "expected/HMAC-SHA2-512.bz2"}, + {"Wrapper": "go", "In": "vectors/HMAC-SHA2-512-224.bz2", "Out": "expected/HMAC-SHA2-512-224.bz2"}, + {"Wrapper": "go", "In": "vectors/HMAC-SHA2-512-256.bz2", "Out": "expected/HMAC-SHA2-512-256.bz2"}, + + {"Wrapper": "go", "In": "vectors/KDA.bz2", "Out": "expected/KDA.bz2"}, + + {"Wrapper": "go", "In": "vectors/HMAC-SHA3-224.bz2", "Out": "expected/HMAC-SHA3-224.bz2"}, + {"Wrapper": "go", "In": "vectors/HMAC-SHA3-256.bz2", "Out": "expected/HMAC-SHA3-256.bz2"}, + {"Wrapper": "go", "In": "vectors/HMAC-SHA3-384.bz2", "Out": "expected/HMAC-SHA3-384.bz2"}, + {"Wrapper": "go", "In": "vectors/HMAC-SHA3-512.bz2", "Out": "expected/HMAC-SHA3-512.bz2"}, + + {"Wrapper": "go", "In": "vectors/PBKDF.bz2", "Out": "expected/PBKDF.bz2"}, + + {"Wrapper": "go", "In": "vectors/ML-KEM.bz2", "Out": "expected/ML-KEM.bz2"}, + {"Wrapper": "go", "In": "vectors/ML-DSA.bz2", "Out": "expected/ML-DSA.bz2"}, + + {"Wrapper": "go", "In": "vectors/hmacDRBG.bz2", "Out": "expected/hmacDRBG.bz2"}, + + {"Wrapper": "go", "In": "vectors/ctrDRBG.bz2", "Out": "expected/ctrDRBG.bz2"}, + + {"Wrapper": "go", "In": "vectors/EDDSA.bz2", "Out": "expected/EDDSA.bz2"}, + + {"Wrapper": "go", "In": "vectors/ECDSA.bz2", "Out": "expected/ECDSA.bz2"}, + + {"Wrapper": "go", "In": "vectors/ACVP-AES-CBC.bz2", "Out": "expected/ACVP-AES-CBC.bz2"}, + {"Wrapper": "go", "In": "vectors/ACVP-AES-CTR.bz2", "Out": "expected/ACVP-AES-CTR.bz2"}, + {"Wrapper": "go", "In": "vectors/ACVP-AES-GCM.bz2", "Out": "expected/ACVP-AES-GCM.bz2"}, + + {"Wrapper": "go", "In": "vectors/CMAC-AES.bz2", "Out": "expected/CMAC-AES.bz2"}, + + {"Wrapper": "go", "In": "vectors/TLS-v1.2.bz2", "Out": "expected/TLS-v1.2.bz2"}, + {"Wrapper": "go", "In": "vectors/TLS-v1.3.bz2", "Out": "expected/TLS-v1.3.bz2"}, + + {"Wrapper": "go", "In": "vectors/kdf-components.bz2", "Out": "expected/kdf-components.bz2"}, + + {"Wrapper": "go", "In": "vectors/RSA.bz2", "Out": "expected/RSA.bz2"} +] From c6f882f6c58ed56fa4bd2d8256ec55d9992c3583 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Sat, 22 Nov 2025 16:26:24 +0100 Subject: [PATCH 006/140] crypto/x509: add ExtKeyUsage.String and KeyUsage.String methods Fixes #56866 Change-Id: Icc8f067820f5d74e0d5073bce160429e6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/723360 Reviewed-by: Daniel McCarney Reviewed-by: Cherry Mui Auto-Submit: Filippo Valsorda LUCI-TryBot-Result: Go LUCI Reviewed-by: Sean Liao Reviewed-by: Roland Shoemaker --- api/next/56866.txt | 2 + .../6-stdlib/99-minor/crypto/x509/56866.md | 2 + src/crypto/x509/verify.go | 2 +- src/crypto/x509/x509.go | 50 ++++++----- src/crypto/x509/x509_string.go | 90 +++++++++++++++++++ 5 files changed, 122 insertions(+), 24 deletions(-) create mode 100644 api/next/56866.txt create mode 100644 doc/next/6-stdlib/99-minor/crypto/x509/56866.md create mode 100644 src/crypto/x509/x509_string.go diff --git a/api/next/56866.txt b/api/next/56866.txt new file mode 100644 index 00000000000..ff6990af886 --- /dev/null +++ b/api/next/56866.txt @@ -0,0 +1,2 @@ +pkg crypto/x509, method (ExtKeyUsage) String() string #56866 +pkg crypto/x509, method (KeyUsage) String() string #56866 diff --git a/doc/next/6-stdlib/99-minor/crypto/x509/56866.md b/doc/next/6-stdlib/99-minor/crypto/x509/56866.md new file mode 100644 index 00000000000..0aa8f06621e --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/x509/56866.md @@ -0,0 +1,2 @@ +The [ExtKeyUsage] and [KeyUsage] types now have String methods that return the +correspodning OID names as defined in RFC 5280 and other registries. diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go index 12e59335b2d..b13e0933456 100644 --- a/src/crypto/x509/verify.go +++ b/src/crypto/x509/verify.go @@ -1157,7 +1157,7 @@ NextCert: } } - const invalidUsage ExtKeyUsage = -1 + const invalidUsage = -1 NextRequestedUsage: for i, requestedUsage := range usages { diff --git a/src/crypto/x509/x509.go b/src/crypto/x509/x509.go index 1f06b4fbc57..afd3d8673a7 100644 --- a/src/crypto/x509/x509.go +++ b/src/crypto/x509/x509.go @@ -582,16 +582,18 @@ func oidFromECDHCurve(curve ecdh.Curve) (asn1.ObjectIdentifier, bool) { // a bitmap of the KeyUsage* constants. type KeyUsage int +//go:generate stringer -linecomment -type=KeyUsage,ExtKeyUsage -output=x509_string.go + const ( - KeyUsageDigitalSignature KeyUsage = 1 << iota - KeyUsageContentCommitment - KeyUsageKeyEncipherment - KeyUsageDataEncipherment - KeyUsageKeyAgreement - KeyUsageCertSign - KeyUsageCRLSign - KeyUsageEncipherOnly - KeyUsageDecipherOnly + KeyUsageDigitalSignature KeyUsage = 1 << iota // digitalSignature + KeyUsageContentCommitment // contentCommitment + KeyUsageKeyEncipherment // keyEncipherment + KeyUsageDataEncipherment // dataEncipherment + KeyUsageKeyAgreement // keyAgreement + KeyUsageCertSign // keyCertSign + KeyUsageCRLSign // cRLSign + KeyUsageEncipherOnly // encipherOnly + KeyUsageDecipherOnly // decipherOnly ) // RFC 5280, 4.2.1.12 Extended Key Usage @@ -606,6 +608,8 @@ const ( // id-kp-emailProtection OBJECT IDENTIFIER ::= { id-kp 4 } // id-kp-timeStamping OBJECT IDENTIFIER ::= { id-kp 8 } // id-kp-OCSPSigning OBJECT IDENTIFIER ::= { id-kp 9 } +// +// https://www.iana.org/assignments/smi-numbers/smi-numbers.xhtml#smi-numbers-1.3.6.1.5.5.7.3 var ( oidExtKeyUsageAny = asn1.ObjectIdentifier{2, 5, 29, 37, 0} oidExtKeyUsageServerAuth = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 3, 1} @@ -628,20 +632,20 @@ var ( type ExtKeyUsage int const ( - ExtKeyUsageAny ExtKeyUsage = iota - ExtKeyUsageServerAuth - ExtKeyUsageClientAuth - ExtKeyUsageCodeSigning - ExtKeyUsageEmailProtection - ExtKeyUsageIPSECEndSystem - ExtKeyUsageIPSECTunnel - ExtKeyUsageIPSECUser - ExtKeyUsageTimeStamping - ExtKeyUsageOCSPSigning - ExtKeyUsageMicrosoftServerGatedCrypto - ExtKeyUsageNetscapeServerGatedCrypto - ExtKeyUsageMicrosoftCommercialCodeSigning - ExtKeyUsageMicrosoftKernelCodeSigning + ExtKeyUsageAny ExtKeyUsage = iota // anyExtendedKeyUsage + ExtKeyUsageServerAuth // serverAuth + ExtKeyUsageClientAuth // clientAuth + ExtKeyUsageCodeSigning // codeSigning + ExtKeyUsageEmailProtection // emailProtection + ExtKeyUsageIPSECEndSystem // ipsecEndSystem + ExtKeyUsageIPSECTunnel // ipsecTunnel + ExtKeyUsageIPSECUser // ipsecUser + ExtKeyUsageTimeStamping // timeStamping + ExtKeyUsageOCSPSigning // OCSPSigning + ExtKeyUsageMicrosoftServerGatedCrypto // msSGC + ExtKeyUsageNetscapeServerGatedCrypto // nsSGC + ExtKeyUsageMicrosoftCommercialCodeSigning // msCodeCom + ExtKeyUsageMicrosoftKernelCodeSigning // msKernelCode ) // extKeyUsageOIDs contains the mapping between an ExtKeyUsage and its OID. diff --git a/src/crypto/x509/x509_string.go b/src/crypto/x509/x509_string.go new file mode 100644 index 00000000000..9670b25bc37 --- /dev/null +++ b/src/crypto/x509/x509_string.go @@ -0,0 +1,90 @@ +// Code generated by "stringer -linecomment -type=KeyUsage,ExtKeyUsage -output=x509_string.go"; DO NOT EDIT. + +package x509 + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[KeyUsageDigitalSignature-1] + _ = x[KeyUsageContentCommitment-2] + _ = x[KeyUsageKeyEncipherment-4] + _ = x[KeyUsageDataEncipherment-8] + _ = x[KeyUsageKeyAgreement-16] + _ = x[KeyUsageCertSign-32] + _ = x[KeyUsageCRLSign-64] + _ = x[KeyUsageEncipherOnly-128] + _ = x[KeyUsageDecipherOnly-256] +} + +const ( + _KeyUsage_name_0 = "digitalSignaturecontentCommitment" + _KeyUsage_name_1 = "keyEncipherment" + _KeyUsage_name_2 = "dataEncipherment" + _KeyUsage_name_3 = "keyAgreement" + _KeyUsage_name_4 = "keyCertSign" + _KeyUsage_name_5 = "cRLSign" + _KeyUsage_name_6 = "encipherOnly" + _KeyUsage_name_7 = "decipherOnly" +) + +var ( + _KeyUsage_index_0 = [...]uint8{0, 16, 33} +) + +func (i KeyUsage) String() string { + switch { + case 1 <= i && i <= 2: + i -= 1 + return _KeyUsage_name_0[_KeyUsage_index_0[i]:_KeyUsage_index_0[i+1]] + case i == 4: + return _KeyUsage_name_1 + case i == 8: + return _KeyUsage_name_2 + case i == 16: + return _KeyUsage_name_3 + case i == 32: + return _KeyUsage_name_4 + case i == 64: + return _KeyUsage_name_5 + case i == 128: + return _KeyUsage_name_6 + case i == 256: + return _KeyUsage_name_7 + default: + return "KeyUsage(" + strconv.FormatInt(int64(i), 10) + ")" + } +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ExtKeyUsageAny-0] + _ = x[ExtKeyUsageServerAuth-1] + _ = x[ExtKeyUsageClientAuth-2] + _ = x[ExtKeyUsageCodeSigning-3] + _ = x[ExtKeyUsageEmailProtection-4] + _ = x[ExtKeyUsageIPSECEndSystem-5] + _ = x[ExtKeyUsageIPSECTunnel-6] + _ = x[ExtKeyUsageIPSECUser-7] + _ = x[ExtKeyUsageTimeStamping-8] + _ = x[ExtKeyUsageOCSPSigning-9] + _ = x[ExtKeyUsageMicrosoftServerGatedCrypto-10] + _ = x[ExtKeyUsageNetscapeServerGatedCrypto-11] + _ = x[ExtKeyUsageMicrosoftCommercialCodeSigning-12] + _ = x[ExtKeyUsageMicrosoftKernelCodeSigning-13] +} + +const _ExtKeyUsage_name = "anyExtendedKeyUsageserverAuthclientAuthcodeSigningemailProtectionipsecEndSystemipsecTunnelipsecUsertimeStampingOCSPSigningmsSGCnsSGCmsCodeCommsKernelCode" + +var _ExtKeyUsage_index = [...]uint8{0, 19, 29, 39, 50, 65, 79, 90, 99, 111, 122, 127, 132, 141, 153} + +func (i ExtKeyUsage) String() string { + idx := int(i) - 0 + if i < 0 || idx >= len(_ExtKeyUsage_index)-1 { + return "ExtKeyUsage(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _ExtKeyUsage_name[_ExtKeyUsage_index[idx]:_ExtKeyUsage_index[idx+1]] +} From 1a53ce9734c0b2a3e2a9814e75949ea77a978143 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Fri, 6 Jun 2025 15:38:28 -0700 Subject: [PATCH 007/140] context: don't return the wrong error when Cause races cancellation Check to see if a context is canceled at all before checking for the cancellaion cause. If we can't find a cause, use the original error. Avoids a data race where we look for a cause, find none (because the context is not canceled), the context is canceled, and we then return ctx.Err() (even though there is now a cause). Fixes #73390 Change-Id: I97f44aef25c6b02871d987970abfb4c215c5c80e Reviewed-on: https://go-review.googlesource.com/c/go/+/679835 Reviewed-by: Sean Liao LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui --- src/context/context.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/context/context.go b/src/context/context.go index 232fc55d875..d00ac67e382 100644 --- a/src/context/context.go +++ b/src/context/context.go @@ -286,6 +286,10 @@ func withCancel(parent Context) *cancelCtx { // Otherwise Cause(c) returns the same value as c.Err(). // Cause returns nil if c has not been canceled yet. func Cause(c Context) error { + err := c.Err() + if err == nil { + return nil + } if cc, ok := c.Value(&cancelCtxKey).(*cancelCtx); ok { cc.mu.Lock() cause := cc.cause @@ -293,16 +297,12 @@ func Cause(c Context) error { if cause != nil { return cause } - // Either this context is not canceled, - // or it is canceled and the cancellation happened in a - // custom context implementation rather than a *cancelCtx. + // The parent cancelCtx doesn't have a cause, + // so c must have been canceled in some custom context implementation. } - // There is no cancelCtxKey value with a cause, so we know that c is - // not a descendant of some canceled Context created by WithCancelCause. - // Therefore, there is no specific cause to return. - // If this is not one of the standard Context types, - // it might still have an error even though it won't have a cause. - return c.Err() + // We don't have a cause to return from a parent cancelCtx, + // so return the context's error. + return err } // AfterFunc arranges to call f in its own goroutine after ctx is canceled. From 64658184354cf963f7813e2f4e1daf8b4ee6f933 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Mon, 24 Nov 2025 11:32:38 -0800 Subject: [PATCH 008/140] all: update to x/net@bff14c52567061031b9761881907c39e24792736 This brings in CL 722200 which adds necessary HTTP/2 support for net/http.Transport.NewClientConn. For #75772 Change-Id: I5489232401096982ed21002f293dd0f87fe2fba6 Reviewed-on: https://go-review.googlesource.com/c/go/+/723901 Reviewed-by: Nicholas Husin LUCI-TryBot-Result: Go LUCI Auto-Submit: Damien Neil Reviewed-by: Nicholas Husin --- src/cmd/go.mod | 2 +- src/cmd/go.sum | 4 +- src/cmd/vendor/modules.txt | 2 +- src/go.mod | 6 +- src/go.sum | 12 +- src/net/http/h2_bundle.go | 413 ++++++++++++++---- .../x/crypto/chacha20/chacha_arm64.s | 2 +- .../chacha20poly1305_amd64.go | 10 +- .../chacha20poly1305_generic.go | 10 +- .../x/net/dns/dnsmessage/message.go | 31 +- .../golang.org/x/net/dns/dnsmessage/svcb.go | 326 ++++++++++++++ src/vendor/modules.txt | 6 +- 12 files changed, 729 insertions(+), 95 deletions(-) create mode 100644 src/vendor/golang.org/x/net/dns/dnsmessage/svcb.go diff --git a/src/cmd/go.mod b/src/cmd/go.mod index 2f13ca0dc96..3915c16da33 100644 --- a/src/cmd/go.mod +++ b/src/cmd/go.mod @@ -16,6 +16,6 @@ require ( require ( github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b // indirect - golang.org/x/text v0.30.0 // indirect + golang.org/x/text v0.31.0 // indirect rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef // indirect ) diff --git a/src/cmd/go.sum b/src/cmd/go.sum index d75d9339fbf..100ea28a7fe 100644 --- a/src/cmd/go.sum +++ b/src/cmd/go.sum @@ -20,8 +20,8 @@ golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxU golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.39.1-0.20251120214200-68724afed209 h1:BGuEUnbWU1H+VhF4Z52lwCvzRT8Q/Z7kJC3okSME58w= golang.org/x/tools v0.39.1-0.20251120214200-68724afed209/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8= diff --git a/src/cmd/vendor/modules.txt b/src/cmd/vendor/modules.txt index 1c7bf37a3b4..a3c2d201774 100644 --- a/src/cmd/vendor/modules.txt +++ b/src/cmd/vendor/modules.txt @@ -63,7 +63,7 @@ golang.org/x/telemetry/internal/upload # golang.org/x/term v0.34.0 ## explicit; go 1.23.0 golang.org/x/term -# golang.org/x/text v0.30.0 +# golang.org/x/text v0.31.0 ## explicit; go 1.24.0 golang.org/x/text/cases golang.org/x/text/internal diff --git a/src/go.mod b/src/go.mod index e6cb3d5b43d..77ca25331ae 100644 --- a/src/go.mod +++ b/src/go.mod @@ -3,11 +3,11 @@ module std go 1.26 require ( - golang.org/x/crypto v0.43.0 - golang.org/x/net v0.46.0 + golang.org/x/crypto v0.44.0 + golang.org/x/net v0.47.1-0.20251124223553-bff14c525670 ) require ( golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.30.0 // indirect + golang.org/x/text v0.31.0 // indirect ) diff --git a/src/go.sum b/src/go.sum index fe184a86471..b33582c5941 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,8 +1,8 @@ -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= +golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/net v0.47.1-0.20251124223553-bff14c525670 h1:6OE5meBQStq9OFgbyv9VH3wiSVw9HDJ7GBz2L5pkhuo= +golang.org/x/net v0.47.1-0.20251124223553-bff14c525670/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= diff --git a/src/net/http/h2_bundle.go b/src/net/http/h2_bundle.go index 0df276321cf..5a7420bc7e1 100644 --- a/src/net/http/h2_bundle.go +++ b/src/net/http/h2_bundle.go @@ -19,6 +19,7 @@ package http import ( "bufio" "bytes" + "compress/flate" "compress/gzip" "context" "crypto/rand" @@ -1842,6 +1843,8 @@ type http2Framer struct { // lastHeaderStream is non-zero if the last frame was an // unfinished HEADERS/CONTINUATION. lastHeaderStream uint32 + // lastFrameType holds the type of the last frame for verifying frame order. + lastFrameType http2FrameType maxReadSize uint32 headerBuf [http2frameHeaderLen]byte @@ -2053,30 +2056,41 @@ func http2terminalReadFrameError(err error) bool { return err != nil } -// ReadFrame reads a single frame. The returned Frame is only valid -// until the next call to ReadFrame. +// ReadFrameHeader reads the header of the next frame. +// It reads the 9-byte fixed frame header, and does not read any portion of the +// frame payload. The caller is responsible for consuming the payload, either +// with ReadFrameForHeader or directly from the Framer's io.Reader. // -// If the frame is larger than previously set with SetMaxReadFrameSize, the -// returned error is ErrFrameTooLarge. Other errors may be of type -// ConnectionError, StreamError, or anything else from the underlying -// reader. +// If the frame is larger than previously set with SetMaxReadFrameSize, it +// returns the frame header and ErrFrameTooLarge. // -// If ReadFrame returns an error and a non-nil Frame, the Frame's StreamID -// indicates the stream responsible for the error. -func (fr *http2Framer) ReadFrame() (http2Frame, error) { +// If the returned FrameHeader.StreamID is non-zero, it indicates the stream +// responsible for the error. +func (fr *http2Framer) ReadFrameHeader() (http2FrameHeader, error) { fr.errDetail = nil - if fr.lastFrame != nil { - fr.lastFrame.invalidate() - } fh, err := http2readFrameHeader(fr.headerBuf[:], fr.r) if err != nil { - return nil, err + return fh, err } if fh.Length > fr.maxReadSize { if fh == http2invalidHTTP1LookingFrameHeader() { - return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", http2ErrFrameTooLarge) + return fh, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", http2ErrFrameTooLarge) } - return nil, http2ErrFrameTooLarge + return fh, http2ErrFrameTooLarge + } + if err := fr.checkFrameOrder(fh); err != nil { + return fh, err + } + return fh, nil +} + +// ReadFrameForHeader reads the payload for the frame with the given FrameHeader. +// +// It behaves identically to ReadFrame, other than not checking the maximum +// frame size. +func (fr *http2Framer) ReadFrameForHeader(fh http2FrameHeader) (http2Frame, error) { + if fr.lastFrame != nil { + fr.lastFrame.invalidate() } payload := fr.getReadBuf(fh.Length) if _, err := io.ReadFull(fr.r, payload); err != nil { @@ -2092,9 +2106,7 @@ func (fr *http2Framer) ReadFrame() (http2Frame, error) { } return nil, err } - if err := fr.checkFrameOrder(f); err != nil { - return nil, err - } + fr.lastFrame = f if fr.logReads { fr.debugReadLoggerf("http2: Framer %p: read %v", fr, http2summarizeFrame(f)) } @@ -2104,6 +2116,24 @@ func (fr *http2Framer) ReadFrame() (http2Frame, error) { return f, nil } +// ReadFrame reads a single frame. The returned Frame is only valid +// until the next call to ReadFrame or ReadFrameBodyForHeader. +// +// If the frame is larger than previously set with SetMaxReadFrameSize, the +// returned error is ErrFrameTooLarge. Other errors may be of type +// ConnectionError, StreamError, or anything else from the underlying +// reader. +// +// If ReadFrame returns an error and a non-nil Frame, the Frame's StreamID +// indicates the stream responsible for the error. +func (fr *http2Framer) ReadFrame() (http2Frame, error) { + fh, err := fr.ReadFrameHeader() + if err != nil { + return nil, err + } + return fr.ReadFrameForHeader(fh) +} + // connError returns ConnectionError(code) but first // stashes away a public reason to the caller can optionally relay it // to the peer before hanging up on them. This might help others debug @@ -2116,20 +2146,19 @@ func (fr *http2Framer) connError(code http2ErrCode, reason string) error { // checkFrameOrder reports an error if f is an invalid frame to return // next from ReadFrame. Mostly it checks whether HEADERS and // CONTINUATION frames are contiguous. -func (fr *http2Framer) checkFrameOrder(f http2Frame) error { - last := fr.lastFrame - fr.lastFrame = f +func (fr *http2Framer) checkFrameOrder(fh http2FrameHeader) error { + lastType := fr.lastFrameType + fr.lastFrameType = fh.Type if fr.AllowIllegalReads { return nil } - fh := f.Header() if fr.lastHeaderStream != 0 { if fh.Type != http2FrameContinuation { return fr.connError(http2ErrCodeProtocol, fmt.Sprintf("got %s for stream %d; expected CONTINUATION following %s for stream %d", fh.Type, fh.StreamID, - last.Header().Type, fr.lastHeaderStream)) + lastType, fr.lastHeaderStream)) } if fh.StreamID != fr.lastHeaderStream { return fr.connError(http2ErrCodeProtocol, @@ -2726,7 +2755,7 @@ var http2defaultRFC9218Priority = http2PriorityParam{ // PriorityParam struct below is a superset of both schemes. The exported // symbols are from RFC 7540 and the non-exported ones are from RFC 9218. -// PriorityParam are the stream prioritzation parameters. +// PriorityParam are the stream prioritization parameters. type http2PriorityParam struct { // StreamDep is a 31-bit stream identifier for the // stream that this stream depends on. Zero means no @@ -7637,11 +7666,24 @@ type http2ClientConn struct { // completely unresponsive connection. pendingResets int + // readBeforeStreamID is the smallest stream ID that has not been followed by + // a frame read from the peer. We use this to determine when a request may + // have been sent to a completely unresponsive connection: + // If the request ID is less than readBeforeStreamID, then we have had some + // indication of life on the connection since sending the request. + readBeforeStreamID uint32 + // reqHeaderMu is a 1-element semaphore channel controlling access to sending new requests. // Write to reqHeaderMu to lock it, read from it to unlock. // Lock reqmu BEFORE mu or wmu. reqHeaderMu chan struct{} + // internalStateHook reports state changes back to the net/http.ClientConn. + // Note that this is different from the user state hook registered by + // net/http.ClientConn.SetStateHook: The internal hook calls ClientConn, + // which calls the user hook. + internalStateHook func() + // wmu is held while writing. // Acquire BEFORE mu when holding both, to avoid blocking mu on network writes. // Only acquire both at the same time when changing peer settings. @@ -7972,7 +8014,7 @@ func http2canRetryError(err error) bool { func (t *http2Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*http2ClientConn, error) { if t.http2transportTestHooks != nil { - return t.newClientConn(nil, singleUse) + return t.newClientConn(nil, singleUse, nil) } host, _, err := net.SplitHostPort(addr) if err != nil { @@ -7982,7 +8024,7 @@ func (t *http2Transport) dialClientConn(ctx context.Context, addr string, single if err != nil { return nil, err } - return t.newClientConn(tconn, singleUse) + return t.newClientConn(tconn, singleUse, nil) } func (t *http2Transport) newTLSConfig(host string) *tls.Config { @@ -8034,10 +8076,10 @@ func (t *http2Transport) expectContinueTimeout() time.Duration { } func (t *http2Transport) NewClientConn(c net.Conn) (*http2ClientConn, error) { - return t.newClientConn(c, t.disableKeepAlives()) + return t.newClientConn(c, t.disableKeepAlives(), nil) } -func (t *http2Transport) newClientConn(c net.Conn, singleUse bool) (*http2ClientConn, error) { +func (t *http2Transport) newClientConn(c net.Conn, singleUse bool, internalStateHook func()) (*http2ClientConn, error) { conf := http2configFromTransport(t) cc := &http2ClientConn{ t: t, @@ -8059,6 +8101,7 @@ func (t *http2Transport) newClientConn(c net.Conn, singleUse bool) (*http2Client pings: make(map[[8]byte]chan struct{}), reqHeaderMu: make(chan struct{}, 1), lastActive: time.Now(), + internalStateHook: internalStateHook, } if t.http2transportTestHooks != nil { t.http2transportTestHooks.newclientconn(cc) @@ -8299,10 +8342,7 @@ func (cc *http2ClientConn) idleStateLocked() (st http2clientConnIdleState) { maxConcurrentOkay = cc.currentRequestCountLocked() < int(cc.maxConcurrentStreams) } - st.canTakeNewRequest = cc.goAway == nil && !cc.closed && !cc.closing && maxConcurrentOkay && - !cc.doNotReuse && - int64(cc.nextStreamID)+2*int64(cc.pendingRequests) < math.MaxInt32 && - !cc.tooIdleLocked() + st.canTakeNewRequest = maxConcurrentOkay && cc.isUsableLocked() // If this connection has never been used for a request and is closed, // then let it take a request (which will fail). @@ -8318,6 +8358,31 @@ func (cc *http2ClientConn) idleStateLocked() (st http2clientConnIdleState) { return } +func (cc *http2ClientConn) isUsableLocked() bool { + return cc.goAway == nil && + !cc.closed && + !cc.closing && + !cc.doNotReuse && + int64(cc.nextStreamID)+2*int64(cc.pendingRequests) < math.MaxInt32 && + !cc.tooIdleLocked() +} + +// canReserveLocked reports whether a net/http.ClientConn can reserve a slot on this conn. +// +// This follows slightly different rules than clientConnIdleState.canTakeNewRequest. +// We only permit reservations up to the conn's concurrency limit. +// This differs from ClientConn.ReserveNewRequest, which permits reservations +// past the limit when StrictMaxConcurrentStreams is set. +func (cc *http2ClientConn) canReserveLocked() bool { + if cc.currentRequestCountLocked() >= int(cc.maxConcurrentStreams) { + return false + } + if !cc.isUsableLocked() { + return false + } + return true +} + // currentRequestCountLocked reports the number of concurrency slots currently in use, // including active streams, reserved slots, and reset streams waiting for acknowledgement. func (cc *http2ClientConn) currentRequestCountLocked() int { @@ -8329,6 +8394,14 @@ func (cc *http2ClientConn) canTakeNewRequestLocked() bool { return st.canTakeNewRequest } +// availableLocked reports the number of concurrency slots available. +func (cc *http2ClientConn) availableLocked() int { + if !cc.canTakeNewRequestLocked() { + return 0 + } + return max(0, int(cc.maxConcurrentStreams)-cc.currentRequestCountLocked()) +} + // tooIdleLocked reports whether this connection has been been sitting idle // for too much wall time. func (cc *http2ClientConn) tooIdleLocked() bool { @@ -8353,6 +8426,7 @@ func (cc *http2ClientConn) closeConn() { t := time.AfterFunc(250*time.Millisecond, cc.forceCloseConn) defer t.Stop() cc.tconn.Close() + cc.maybeCallStateHook() } // A tls.Conn.Close can hang for a long time if the peer is unresponsive. @@ -8878,6 +8952,8 @@ func (cs *http2clientStream) cleanupWriteRequest(err error) { } bodyClosed := cs.reqBodyClosed closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil + // Have we read any frames from the connection since sending this request? + readSinceStream := cc.readBeforeStreamID > cs.ID cc.mu.Unlock() if mustCloseBody { cs.reqBody.Close() @@ -8909,8 +8985,10 @@ func (cs *http2clientStream) cleanupWriteRequest(err error) { // // This could be due to the server becoming unresponsive. // To avoid sending too many requests on a dead connection, - // we let the request continue to consume a concurrency slot - // until we can confirm the server is still responding. + // if we haven't read any frames from the connection since + // sending this request, we let it continue to consume + // a concurrency slot until we can confirm the server is + // still responding. // We do this by sending a PING frame along with the RST_STREAM // (unless a ping is already in flight). // @@ -8921,7 +8999,7 @@ func (cs *http2clientStream) cleanupWriteRequest(err error) { // because it's short lived and will probably be closed before // we get the ping response. ping := false - if !closeOnIdle { + if !closeOnIdle && !readSinceStream { cc.mu.Lock() // rstStreamPingsBlocked works around a gRPC behavior: // see comment on the field for details. @@ -8955,6 +9033,7 @@ func (cs *http2clientStream) cleanupWriteRequest(err error) { } close(cs.donec) + cc.maybeCallStateHook() } // awaitOpenSlotForStreamLocked waits until len(streams) < maxConcurrentStreams. @@ -10008,6 +10087,7 @@ func (rl *http2clientConnReadLoop) streamByID(id uint32, headerOrData bool) *htt // See comment on ClientConn.rstStreamPingsBlocked for details. rl.cc.rstStreamPingsBlocked = false } + rl.cc.readBeforeStreamID = rl.cc.nextStreamID cs := rl.cc.streams[id] if cs != nil && !cs.readAborted { return cs @@ -10058,6 +10138,7 @@ func (rl *http2clientConnReadLoop) processSettings(f *http2SettingsFrame) error func (rl *http2clientConnReadLoop) processSettingsNoWrite(f *http2SettingsFrame) error { cc := rl.cc + defer cc.maybeCallStateHook() cc.mu.Lock() defer cc.mu.Unlock() @@ -10238,6 +10319,7 @@ func (cc *http2ClientConn) Ping(ctx context.Context) error { func (rl *http2clientConnReadLoop) processPing(f *http2PingFrame) error { if f.IsAck() { cc := rl.cc + defer cc.maybeCallStateHook() cc.mu.Lock() defer cc.mu.Unlock() // If ack, notify listener if any @@ -10343,35 +10425,103 @@ func (rt http2erringRoundTripper) RoundTripErr() error { return rt.err } func (rt http2erringRoundTripper) RoundTrip(*Request) (*Response, error) { return nil, rt.err } +var http2errConcurrentReadOnResBody = errors.New("http2: concurrent read on response body") + // gzipReader wraps a response body so it can lazily -// call gzip.NewReader on the first call to Read +// get gzip.Reader from the pool on the first call to Read. +// After Close is called it puts gzip.Reader to the pool immediately +// if there is no Read in progress or later when Read completes. type http2gzipReader struct { _ http2incomparable body io.ReadCloser // underlying Response.Body - zr *gzip.Reader // lazily-initialized gzip reader - zerr error // sticky error + mu sync.Mutex // guards zr and zerr + zr *gzip.Reader // stores gzip reader from the pool between reads + zerr error // sticky gzip reader init error or sentinel value to detect concurrent read and read after close +} + +type http2eofReader struct{} + +func (http2eofReader) Read([]byte) (int, error) { return 0, io.EOF } + +func (http2eofReader) ReadByte() (byte, error) { return 0, io.EOF } + +var http2gzipPool = sync.Pool{New: func() any { return new(gzip.Reader) }} + +// gzipPoolGet gets a gzip.Reader from the pool and resets it to read from r. +func http2gzipPoolGet(r io.Reader) (*gzip.Reader, error) { + zr := http2gzipPool.Get().(*gzip.Reader) + if err := zr.Reset(r); err != nil { + http2gzipPoolPut(zr) + return nil, err + } + return zr, nil +} + +// gzipPoolPut puts a gzip.Reader back into the pool. +func http2gzipPoolPut(zr *gzip.Reader) { + // Reset will allocate bufio.Reader if we pass it anything + // other than a flate.Reader, so ensure that it's getting one. + var r flate.Reader = http2eofReader{} + zr.Reset(r) + http2gzipPool.Put(zr) +} + +// acquire returns a gzip.Reader for reading response body. +// The reader must be released after use. +func (gz *http2gzipReader) acquire() (*gzip.Reader, error) { + gz.mu.Lock() + defer gz.mu.Unlock() + if gz.zerr != nil { + return nil, gz.zerr + } + if gz.zr == nil { + gz.zr, gz.zerr = http2gzipPoolGet(gz.body) + if gz.zerr != nil { + return nil, gz.zerr + } + } + ret := gz.zr + gz.zr, gz.zerr = nil, http2errConcurrentReadOnResBody + return ret, nil +} + +// release returns the gzip.Reader to the pool if Close was called during Read. +func (gz *http2gzipReader) release(zr *gzip.Reader) { + gz.mu.Lock() + defer gz.mu.Unlock() + if gz.zerr == http2errConcurrentReadOnResBody { + gz.zr, gz.zerr = zr, nil + } else { // fs.ErrClosed + http2gzipPoolPut(zr) + } +} + +// close returns the gzip.Reader to the pool immediately or +// signals release to do so after Read completes. +func (gz *http2gzipReader) close() { + gz.mu.Lock() + defer gz.mu.Unlock() + if gz.zerr == nil && gz.zr != nil { + http2gzipPoolPut(gz.zr) + gz.zr = nil + } + gz.zerr = fs.ErrClosed } func (gz *http2gzipReader) Read(p []byte) (n int, err error) { - if gz.zerr != nil { - return 0, gz.zerr + zr, err := gz.acquire() + if err != nil { + return 0, err } - if gz.zr == nil { - gz.zr, err = gzip.NewReader(gz.body) - if err != nil { - gz.zerr = err - return 0, err - } - } - return gz.zr.Read(p) + defer gz.release(zr) + + return zr.Read(p) } func (gz *http2gzipReader) Close() error { - if err := gz.body.Close(); err != nil { - return err - } - gz.zerr = fs.ErrClosed - return nil + gz.close() + + return gz.body.Close() } type http2errorReader struct{ err error } @@ -10397,9 +10547,13 @@ func http2registerHTTPSProtocol(t *Transport, rt http2noDialH2RoundTripper) (err } // noDialH2RoundTripper is a RoundTripper which only tries to complete the request -// if there's already has a cached connection to the host. +// if there's already a cached connection to the host. // (The field is exported so it can be accessed via reflect from net/http; tested // by TestNoDialH2RoundTripperType) +// +// A noDialH2RoundTripper is registered with http1.Transport.RegisterProtocol, +// and the http1.Transport can use type assertions to call non-RoundTrip methods on it. +// This lets us expose, for example, NewClientConn to net/http. type http2noDialH2RoundTripper struct{ *http2Transport } func (rt http2noDialH2RoundTripper) RoundTrip(req *Request) (*Response, error) { @@ -10410,6 +10564,85 @@ func (rt http2noDialH2RoundTripper) RoundTrip(req *Request) (*Response, error) { return res, err } +func (rt http2noDialH2RoundTripper) NewClientConn(conn net.Conn, internalStateHook func()) (RoundTripper, error) { + tr := rt.http2Transport + cc, err := tr.newClientConn(conn, tr.disableKeepAlives(), internalStateHook) + if err != nil { + return nil, err + } + + // RoundTrip should block when the conn is at its concurrency limit, + // not return an error. Setting strictMaxConcurrentStreams enables this. + cc.strictMaxConcurrentStreams = true + + return http2netHTTPClientConn{cc}, nil +} + +// netHTTPClientConn wraps ClientConn and implements the interface net/http expects from +// the RoundTripper returned by NewClientConn. +type http2netHTTPClientConn struct { + cc *http2ClientConn +} + +func (cc http2netHTTPClientConn) RoundTrip(req *Request) (*Response, error) { + return cc.cc.RoundTrip(req) +} + +func (cc http2netHTTPClientConn) Close() error { + return cc.cc.Close() +} + +func (cc http2netHTTPClientConn) Err() error { + cc.cc.mu.Lock() + defer cc.cc.mu.Unlock() + if cc.cc.closed { + return errors.New("connection closed") + } + return nil +} + +func (cc http2netHTTPClientConn) Reserve() error { + defer cc.cc.maybeCallStateHook() + cc.cc.mu.Lock() + defer cc.cc.mu.Unlock() + if !cc.cc.canReserveLocked() { + return errors.New("connection is unavailable") + } + cc.cc.streamsReserved++ + return nil +} + +func (cc http2netHTTPClientConn) Release() { + defer cc.cc.maybeCallStateHook() + cc.cc.mu.Lock() + defer cc.cc.mu.Unlock() + // We don't complain if streamsReserved is 0. + // + // This is consistent with RoundTrip: both Release and RoundTrip will + // consume a reservation iff one exists. + if cc.cc.streamsReserved > 0 { + cc.cc.streamsReserved-- + } +} + +func (cc http2netHTTPClientConn) Available() int { + cc.cc.mu.Lock() + defer cc.cc.mu.Unlock() + return cc.cc.availableLocked() +} + +func (cc http2netHTTPClientConn) InFlight() int { + cc.cc.mu.Lock() + defer cc.cc.mu.Unlock() + return cc.cc.currentRequestCountLocked() +} + +func (cc *http2ClientConn) maybeCallStateHook() { + if cc.internalStateHook != nil { + cc.internalStateHook() + } +} + func (t *http2Transport) idleConnTimeout() time.Duration { // to keep things backwards compatible, we use non-zero values of // IdleConnTimeout, followed by using the IdleConnTimeout on the underlying @@ -11069,45 +11302,75 @@ func (wr *http2FrameWriteRequest) replyToWriter(err error) { } // writeQueue is used by implementations of WriteScheduler. +// +// Each writeQueue contains a queue of FrameWriteRequests, meant to store all +// FrameWriteRequests associated with a given stream. This is implemented as a +// two-stage queue: currQueue[currPos:] and nextQueue. Removing an item is done +// by incrementing currPos of currQueue. Adding an item is done by appending it +// to the nextQueue. If currQueue is empty when trying to remove an item, we +// can swap currQueue and nextQueue to remedy the situation. +// This two-stage queue is analogous to the use of two lists in Okasaki's +// purely functional queue but without the overhead of reversing the list when +// swapping stages. +// +// writeQueue also contains prev and next, this can be used by implementations +// of WriteScheduler to construct data structures that represent the order of +// writing between different streams (e.g. circular linked list). type http2writeQueue struct { - s []http2FrameWriteRequest + currQueue []http2FrameWriteRequest + nextQueue []http2FrameWriteRequest + currPos int + prev, next *http2writeQueue } -func (q *http2writeQueue) empty() bool { return len(q.s) == 0 } +func (q *http2writeQueue) empty() bool { + return (len(q.currQueue) - q.currPos + len(q.nextQueue)) == 0 +} func (q *http2writeQueue) push(wr http2FrameWriteRequest) { - q.s = append(q.s, wr) + q.nextQueue = append(q.nextQueue, wr) } func (q *http2writeQueue) shift() http2FrameWriteRequest { - if len(q.s) == 0 { + if q.empty() { panic("invalid use of queue") } - wr := q.s[0] - // TODO: less copy-happy queue. - copy(q.s, q.s[1:]) - q.s[len(q.s)-1] = http2FrameWriteRequest{} - q.s = q.s[:len(q.s)-1] + if q.currPos >= len(q.currQueue) { + q.currQueue, q.currPos, q.nextQueue = q.nextQueue, 0, q.currQueue[:0] + } + wr := q.currQueue[q.currPos] + q.currQueue[q.currPos] = http2FrameWriteRequest{} + q.currPos++ return wr } +func (q *http2writeQueue) peek() *http2FrameWriteRequest { + if q.currPos < len(q.currQueue) { + return &q.currQueue[q.currPos] + } + if len(q.nextQueue) > 0 { + return &q.nextQueue[0] + } + return nil +} + // consume consumes up to n bytes from q.s[0]. If the frame is // entirely consumed, it is removed from the queue. If the frame // is partially consumed, the frame is kept with the consumed // bytes removed. Returns true iff any bytes were consumed. func (q *http2writeQueue) consume(n int32) (http2FrameWriteRequest, bool) { - if len(q.s) == 0 { + if q.empty() { return http2FrameWriteRequest{}, false } - consumed, rest, numresult := q.s[0].Consume(n) + consumed, rest, numresult := q.peek().Consume(n) switch numresult { case 0: return http2FrameWriteRequest{}, false case 1: q.shift() case 2: - q.s[0] = rest + *q.peek() = rest } return consumed, true } @@ -11118,10 +11381,15 @@ type http2writeQueuePool []*http2writeQueue // put inserts an unused writeQueue into the pool. func (p *http2writeQueuePool) put(q *http2writeQueue) { - for i := range q.s { - q.s[i] = http2FrameWriteRequest{} + for i := range q.currQueue { + q.currQueue[i] = http2FrameWriteRequest{} } - q.s = q.s[:0] + for i := range q.nextQueue { + q.nextQueue[i] = http2FrameWriteRequest{} + } + q.currQueue = q.currQueue[:0] + q.nextQueue = q.nextQueue[:0] + q.currPos = 0 *p = append(*p, q) } @@ -11344,8 +11612,8 @@ func (z http2sortPriorityNodeSiblingsRFC7540) Swap(i, k int) { z[i], z[k] = z[k] func (z http2sortPriorityNodeSiblingsRFC7540) Less(i, k int) bool { // Prefer the subtree that has sent fewer bytes relative to its weight. // See sections 5.3.2 and 5.3.4. - wi, bi := float64(z[i].weight+1), float64(z[i].subtreeBytes) - wk, bk := float64(z[k].weight+1), float64(z[k].subtreeBytes) + wi, bi := float64(z[i].weight)+1, float64(z[i].subtreeBytes) + wk, bk := float64(z[k].weight)+1, float64(z[k].subtreeBytes) if bi == 0 && bk == 0 { return wi >= wk } @@ -11432,7 +11700,6 @@ func (ws *http2priorityWriteSchedulerRFC7540) CloseStream(streamID uint32) { q := n.q ws.queuePool.put(&q) - n.q.s = nil if ws.maxClosedNodesInTree > 0 { ws.addClosedOrIdleNode(&ws.closedNodes, ws.maxClosedNodesInTree, n) } else { @@ -11610,7 +11877,7 @@ type http2priorityWriteSchedulerRFC9218 struct { prioritizeIncremental bool } -func http2newPriorityWriteSchedulerRFC9128() http2WriteScheduler { +func http2newPriorityWriteSchedulerRFC9218() http2WriteScheduler { ws := &http2priorityWriteSchedulerRFC9218{ streams: make(map[uint32]http2streamMetadata), } diff --git a/src/vendor/golang.org/x/crypto/chacha20/chacha_arm64.s b/src/vendor/golang.org/x/crypto/chacha20/chacha_arm64.s index 7dd2638e88a..769af387e2e 100644 --- a/src/vendor/golang.org/x/crypto/chacha20/chacha_arm64.s +++ b/src/vendor/golang.org/x/crypto/chacha20/chacha_arm64.s @@ -29,7 +29,7 @@ loop: MOVD $NUM_ROUNDS, R21 VLD1 (R11), [V30.S4, V31.S4] - // load contants + // load constants // VLD4R (R10), [V0.S4, V1.S4, V2.S4, V3.S4] WORD $0x4D60E940 diff --git a/src/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_amd64.go b/src/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_amd64.go index 50695a14f62..b850e772e16 100644 --- a/src/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_amd64.go +++ b/src/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_amd64.go @@ -56,7 +56,10 @@ func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) [] ret, out := sliceForAppend(dst, len(plaintext)+16) if alias.InexactOverlap(out, plaintext) { - panic("chacha20poly1305: invalid buffer overlap") + panic("chacha20poly1305: invalid buffer overlap of output and input") + } + if alias.AnyOverlap(out, additionalData) { + panic("chacha20poly1305: invalid buffer overlap of output and additional data") } chacha20Poly1305Seal(out[:], state[:], plaintext, additionalData) return ret @@ -73,7 +76,10 @@ func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) ( ciphertext = ciphertext[:len(ciphertext)-16] ret, out := sliceForAppend(dst, len(ciphertext)) if alias.InexactOverlap(out, ciphertext) { - panic("chacha20poly1305: invalid buffer overlap") + panic("chacha20poly1305: invalid buffer overlap of output and input") + } + if alias.AnyOverlap(out, additionalData) { + panic("chacha20poly1305: invalid buffer overlap of output and additional data") } if !chacha20Poly1305Open(out, state[:], ciphertext, additionalData) { for i := range out { diff --git a/src/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_generic.go b/src/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_generic.go index 6313898f0a7..2ecc840fca2 100644 --- a/src/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_generic.go +++ b/src/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_generic.go @@ -31,7 +31,10 @@ func (c *chacha20poly1305) sealGeneric(dst, nonce, plaintext, additionalData []b ret, out := sliceForAppend(dst, len(plaintext)+poly1305.TagSize) ciphertext, tag := out[:len(plaintext)], out[len(plaintext):] if alias.InexactOverlap(out, plaintext) { - panic("chacha20poly1305: invalid buffer overlap") + panic("chacha20poly1305: invalid buffer overlap of output and input") + } + if alias.AnyOverlap(out, additionalData) { + panic("chacha20poly1305: invalid buffer overlap of output and additional data") } var polyKey [32]byte @@ -67,7 +70,10 @@ func (c *chacha20poly1305) openGeneric(dst, nonce, ciphertext, additionalData [] ret, out := sliceForAppend(dst, len(ciphertext)) if alias.InexactOverlap(out, ciphertext) { - panic("chacha20poly1305: invalid buffer overlap") + panic("chacha20poly1305: invalid buffer overlap of output and input") + } + if alias.AnyOverlap(out, additionalData) { + panic("chacha20poly1305: invalid buffer overlap of output and additional data") } if !p.Verify(tag) { for i := range out { diff --git a/src/vendor/golang.org/x/net/dns/dnsmessage/message.go b/src/vendor/golang.org/x/net/dns/dnsmessage/message.go index a656efc128a..7a978b47f66 100644 --- a/src/vendor/golang.org/x/net/dns/dnsmessage/message.go +++ b/src/vendor/golang.org/x/net/dns/dnsmessage/message.go @@ -17,8 +17,21 @@ import ( ) // Message formats +// +// To add a new Resource Record type: +// 1. Create Resource Record types +// 1.1. Add a Type constant named "Type" +// 1.2. Add the corresponding entry to the typeNames map +// 1.3. Add a [ResourceBody] implementation named "Resource" +// 2. Implement packing +// 2.1. Implement Builder.Resource() +// 3. Implement unpacking +// 3.1. Add the unpacking code to unpackResourceBody() +// 3.2. Implement Parser.Resource() -// A Type is a type of DNS request and response. +// A Type is the type of a DNS Resource Record, as defined in the [IANA registry]. +// +// [IANA registry]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4 type Type uint16 const ( @@ -33,6 +46,8 @@ const ( TypeAAAA Type = 28 TypeSRV Type = 33 TypeOPT Type = 41 + TypeSVCB Type = 64 + TypeHTTPS Type = 65 // Question.Type TypeWKS Type = 11 @@ -53,6 +68,8 @@ var typeNames = map[Type]string{ TypeAAAA: "TypeAAAA", TypeSRV: "TypeSRV", TypeOPT: "TypeOPT", + TypeSVCB: "TypeSVCB", + TypeHTTPS: "TypeHTTPS", TypeWKS: "TypeWKS", TypeHINFO: "TypeHINFO", TypeMINFO: "TypeMINFO", @@ -273,6 +290,8 @@ var ( errTooManyAdditionals = errors.New("too many Additionals to pack (>65535)") errNonCanonicalName = errors.New("name is not in canonical format (it must end with a .)") errStringTooLong = errors.New("character string exceeds maximum length (255)") + errParamOutOfOrder = errors.New("parameter out of order") + errTooLongSVCBValue = errors.New("value too long (>65535 bytes)") ) // Internal constants. @@ -2220,6 +2239,16 @@ func unpackResourceBody(msg []byte, off int, hdr ResourceHeader) (ResourceBody, rb, err = unpackSRVResource(msg, off) r = &rb name = "SRV" + case TypeSVCB: + var rb SVCBResource + rb, err = unpackSVCBResource(msg, off, hdr.Length) + r = &rb + name = "SVCB" + case TypeHTTPS: + var rb HTTPSResource + rb.SVCBResource, err = unpackSVCBResource(msg, off, hdr.Length) + r = &rb + name = "HTTPS" case TypeOPT: var rb OPTResource rb, err = unpackOPTResource(msg, off, hdr.Length) diff --git a/src/vendor/golang.org/x/net/dns/dnsmessage/svcb.go b/src/vendor/golang.org/x/net/dns/dnsmessage/svcb.go new file mode 100644 index 00000000000..4840516a7f8 --- /dev/null +++ b/src/vendor/golang.org/x/net/dns/dnsmessage/svcb.go @@ -0,0 +1,326 @@ +// Copyright 2025 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 dnsmessage + +import ( + "slices" +) + +// An SVCBResource is an SVCB Resource record. +type SVCBResource struct { + Priority uint16 + Target Name + Params []SVCParam // Must be in strict increasing order by Key. +} + +func (r *SVCBResource) realType() Type { + return TypeSVCB +} + +// GoString implements fmt.GoStringer.GoString. +func (r *SVCBResource) GoString() string { + b := []byte("dnsmessage.SVCBResource{" + + "Priority: " + printUint16(r.Priority) + ", " + + "Target: " + r.Target.GoString() + ", " + + "Params: []dnsmessage.SVCParam{") + if len(r.Params) > 0 { + b = append(b, r.Params[0].GoString()...) + for _, p := range r.Params[1:] { + b = append(b, ", "+p.GoString()...) + } + } + b = append(b, "}}"...) + return string(b) +} + +// An HTTPSResource is an HTTPS Resource record. +// It has the same format as the SVCB record. +type HTTPSResource struct { + // Alias for SVCB resource record. + SVCBResource +} + +func (r *HTTPSResource) realType() Type { + return TypeHTTPS +} + +// GoString implements fmt.GoStringer.GoString. +func (r *HTTPSResource) GoString() string { + return "dnsmessage.HTTPSResource{SVCBResource: " + r.SVCBResource.GoString() + "}" +} + +// GetParam returns a parameter value by key. +func (r *SVCBResource) GetParam(key SVCParamKey) (value []byte, ok bool) { + for i := range r.Params { + if r.Params[i].Key == key { + return r.Params[i].Value, true + } + if r.Params[i].Key > key { + break + } + } + return nil, false +} + +// SetParam sets a parameter value by key. +// The Params list is kept sorted by key. +func (r *SVCBResource) SetParam(key SVCParamKey, value []byte) { + i := 0 + for i < len(r.Params) { + if r.Params[i].Key >= key { + break + } + i++ + } + + if i < len(r.Params) && r.Params[i].Key == key { + r.Params[i].Value = value + return + } + + r.Params = slices.Insert(r.Params, i, SVCParam{Key: key, Value: value}) +} + +// DeleteParam deletes a parameter by key. +// It returns true if the parameter was present. +func (r *SVCBResource) DeleteParam(key SVCParamKey) bool { + for i := range r.Params { + if r.Params[i].Key == key { + r.Params = slices.Delete(r.Params, i, i+1) + return true + } + if r.Params[i].Key > key { + break + } + } + return false +} + +// A SVCParam is a service parameter. +type SVCParam struct { + Key SVCParamKey + Value []byte +} + +// GoString implements fmt.GoStringer.GoString. +func (p SVCParam) GoString() string { + return "dnsmessage.SVCParam{" + + "Key: " + p.Key.GoString() + ", " + + "Value: []byte{" + printByteSlice(p.Value) + "}}" +} + +// A SVCParamKey is a key for a service parameter. +type SVCParamKey uint16 + +// Values defined at https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml#dns-svcparamkeys. +const ( + SVCParamMandatory SVCParamKey = 0 + SVCParamALPN SVCParamKey = 1 + SVCParamNoDefaultALPN SVCParamKey = 2 + SVCParamPort SVCParamKey = 3 + SVCParamIPv4Hint SVCParamKey = 4 + SVCParamECH SVCParamKey = 5 + SVCParamIPv6Hint SVCParamKey = 6 + SVCParamDOHPath SVCParamKey = 7 + SVCParamOHTTP SVCParamKey = 8 + SVCParamTLSSupportedGroups SVCParamKey = 9 +) + +var svcParamKeyNames = map[SVCParamKey]string{ + SVCParamMandatory: "Mandatory", + SVCParamALPN: "ALPN", + SVCParamNoDefaultALPN: "NoDefaultALPN", + SVCParamPort: "Port", + SVCParamIPv4Hint: "IPv4Hint", + SVCParamECH: "ECH", + SVCParamIPv6Hint: "IPv6Hint", + SVCParamDOHPath: "DOHPath", + SVCParamOHTTP: "OHTTP", + SVCParamTLSSupportedGroups: "TLSSupportedGroups", +} + +// String implements fmt.Stringer.String. +func (k SVCParamKey) String() string { + if n, ok := svcParamKeyNames[k]; ok { + return n + } + return printUint16(uint16(k)) +} + +// GoString implements fmt.GoStringer.GoString. +func (k SVCParamKey) GoString() string { + if n, ok := svcParamKeyNames[k]; ok { + return "dnsmessage.SVCParam" + n + } + return printUint16(uint16(k)) +} + +func (r *SVCBResource) pack(msg []byte, _ map[string]uint16, _ int) ([]byte, error) { + oldMsg := msg + msg = packUint16(msg, r.Priority) + // https://datatracker.ietf.org/doc/html/rfc3597#section-4 prohibits name + // compression for RR types that are not "well-known". + // https://datatracker.ietf.org/doc/html/rfc9460#section-2.2 explicitly states that + // compression of the Target is prohibited, following RFC 3597. + msg, err := r.Target.pack(msg, nil, 0) + if err != nil { + return oldMsg, &nestedError{"SVCBResource.Target", err} + } + var previousKey SVCParamKey + for i, param := range r.Params { + if i > 0 && param.Key <= previousKey { + return oldMsg, &nestedError{"SVCBResource.Params", errParamOutOfOrder} + } + if len(param.Value) > (1<<16)-1 { + return oldMsg, &nestedError{"SVCBResource.Params", errTooLongSVCBValue} + } + msg = packUint16(msg, uint16(param.Key)) + msg = packUint16(msg, uint16(len(param.Value))) + msg = append(msg, param.Value...) + } + return msg, nil +} + +func unpackSVCBResource(msg []byte, off int, length uint16) (SVCBResource, error) { + // Wire format reference: https://www.rfc-editor.org/rfc/rfc9460.html#section-2.2. + r := SVCBResource{} + paramsOff := off + bodyEnd := off + int(length) + + var err error + if r.Priority, paramsOff, err = unpackUint16(msg, paramsOff); err != nil { + return SVCBResource{}, &nestedError{"Priority", err} + } + + if paramsOff, err = r.Target.unpack(msg, paramsOff); err != nil { + return SVCBResource{}, &nestedError{"Target", err} + } + + // Two-pass parsing to avoid allocations. + // First, count the number of params. + n := 0 + var totalValueLen uint16 + off = paramsOff + var previousKey uint16 + for off < bodyEnd { + var key, len uint16 + if key, off, err = unpackUint16(msg, off); err != nil { + return SVCBResource{}, &nestedError{"Params key", err} + } + if n > 0 && key <= previousKey { + // As per https://www.rfc-editor.org/rfc/rfc9460.html#section-2.2, clients MUST + // consider the RR malformed if the SvcParamKeys are not in strictly increasing numeric order + return SVCBResource{}, &nestedError{"Params", errParamOutOfOrder} + } + if len, off, err = unpackUint16(msg, off); err != nil { + return SVCBResource{}, &nestedError{"Params value length", err} + } + if off+int(len) > bodyEnd { + return SVCBResource{}, errResourceLen + } + totalValueLen += len + off += int(len) + n++ + } + if off != bodyEnd { + return SVCBResource{}, errResourceLen + } + + // Second, fill in the params. + r.Params = make([]SVCParam, n) + // valuesBuf is used to hold all param values to reduce allocations. + // Each param's Value slice will point into this buffer. + valuesBuf := make([]byte, totalValueLen) + off = paramsOff + for i := 0; i < n; i++ { + p := &r.Params[i] + var key, len uint16 + if key, off, err = unpackUint16(msg, off); err != nil { + return SVCBResource{}, &nestedError{"param key", err} + } + p.Key = SVCParamKey(key) + if len, off, err = unpackUint16(msg, off); err != nil { + return SVCBResource{}, &nestedError{"param length", err} + } + if copy(valuesBuf, msg[off:off+int(len)]) != int(len) { + return SVCBResource{}, &nestedError{"param value", errCalcLen} + } + p.Value = valuesBuf[:len:len] + valuesBuf = valuesBuf[len:] + off += int(len) + } + + return r, nil +} + +// genericSVCBResource parses a single Resource Record compatible with SVCB. +func (p *Parser) genericSVCBResource(svcbType Type) (SVCBResource, error) { + if !p.resHeaderValid || p.resHeaderType != svcbType { + return SVCBResource{}, ErrNotStarted + } + r, err := unpackSVCBResource(p.msg, p.off, p.resHeaderLength) + if err != nil { + return SVCBResource{}, err + } + p.off += int(p.resHeaderLength) + p.resHeaderValid = false + p.index++ + return r, nil +} + +// SVCBResource parses a single SVCBResource. +// +// One of the XXXHeader methods must have been called before calling this +// method. +func (p *Parser) SVCBResource() (SVCBResource, error) { + return p.genericSVCBResource(TypeSVCB) +} + +// HTTPSResource parses a single HTTPSResource. +// +// One of the XXXHeader methods must have been called before calling this +// method. +func (p *Parser) HTTPSResource() (HTTPSResource, error) { + svcb, err := p.genericSVCBResource(TypeHTTPS) + if err != nil { + return HTTPSResource{}, err + } + return HTTPSResource{svcb}, nil +} + +// genericSVCBResource is the generic implementation for adding SVCB-like resources. +func (b *Builder) genericSVCBResource(h ResourceHeader, r SVCBResource) error { + if err := b.checkResourceSection(); err != nil { + return err + } + msg, lenOff, err := h.pack(b.msg, b.compression, b.start) + if err != nil { + return &nestedError{"ResourceHeader", err} + } + preLen := len(msg) + if msg, err = r.pack(msg, b.compression, b.start); err != nil { + return &nestedError{"ResourceBody", err} + } + if err := h.fixLen(msg, lenOff, preLen); err != nil { + return err + } + if err := b.incrementSectionCount(); err != nil { + return err + } + b.msg = msg + return nil +} + +// SVCBResource adds a single SVCBResource. +func (b *Builder) SVCBResource(h ResourceHeader, r SVCBResource) error { + h.Type = r.realType() + return b.genericSVCBResource(h, r) +} + +// HTTPSResource adds a single HTTPSResource. +func (b *Builder) HTTPSResource(h ResourceHeader, r HTTPSResource) error { + h.Type = r.realType() + return b.genericSVCBResource(h, r.SVCBResource) +} diff --git a/src/vendor/modules.txt b/src/vendor/modules.txt index bf7a7979660..27f1ea7edf1 100644 --- a/src/vendor/modules.txt +++ b/src/vendor/modules.txt @@ -1,4 +1,4 @@ -# golang.org/x/crypto v0.43.0 +# golang.org/x/crypto v0.44.0 ## explicit; go 1.24.0 golang.org/x/crypto/chacha20 golang.org/x/crypto/chacha20poly1305 @@ -6,7 +6,7 @@ golang.org/x/crypto/cryptobyte golang.org/x/crypto/cryptobyte/asn1 golang.org/x/crypto/internal/alias golang.org/x/crypto/internal/poly1305 -# golang.org/x/net v0.46.0 +# golang.org/x/net v0.47.1-0.20251124223553-bff14c525670 ## explicit; go 1.24.0 golang.org/x/net/dns/dnsmessage golang.org/x/net/http/httpguts @@ -18,7 +18,7 @@ golang.org/x/net/nettest # golang.org/x/sys v0.38.0 ## explicit; go 1.24.0 golang.org/x/sys/cpu -# golang.org/x/text v0.30.0 +# golang.org/x/text v0.31.0 ## explicit; go 1.24.0 golang.org/x/text/secure/bidirule golang.org/x/text/transform From 0921e1db83d3e67032999b5a2f54f5ede8ba39b5 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Tue, 18 Nov 2025 14:15:05 -0800 Subject: [PATCH 009/140] net/http: add Transport.NewClientConn For #75772 Change-Id: Iad7607b40636bab1faf8653455e92e9700309003 Reviewed-on: https://go-review.googlesource.com/c/go/+/722223 Reviewed-by: Nicholas Husin Reviewed-by: Nicholas Husin Auto-Submit: Damien Neil LUCI-TryBot-Result: Go LUCI --- api/next/75772.txt | 10 + doc/next/6-stdlib/99-minor/net/http/75772.md | 5 + src/net/http/clientconn.go | 456 +++++++++++++++++++ src/net/http/clientconn_test.go | 374 +++++++++++++++ src/net/http/clientserver_test.go | 10 + src/net/http/transport.go | 119 ++++- 6 files changed, 949 insertions(+), 25 deletions(-) create mode 100644 api/next/75772.txt create mode 100644 doc/next/6-stdlib/99-minor/net/http/75772.md create mode 100644 src/net/http/clientconn.go create mode 100644 src/net/http/clientconn_test.go diff --git a/api/next/75772.txt b/api/next/75772.txt new file mode 100644 index 00000000000..18c0f865618 --- /dev/null +++ b/api/next/75772.txt @@ -0,0 +1,10 @@ +pkg net/http, method (*ClientConn) Available() int #75772 +pkg net/http, method (*ClientConn) Close() error #75772 +pkg net/http, method (*ClientConn) Err() error #75772 +pkg net/http, method (*ClientConn) InFlight() int #75772 +pkg net/http, method (*ClientConn) Release() #75772 +pkg net/http, method (*ClientConn) Reserve() error #75772 +pkg net/http, method (*ClientConn) RoundTrip(*Request) (*Response, error) #75772 +pkg net/http, method (*ClientConn) SetStateHook(func(*ClientConn)) #75772 +pkg net/http, method (*Transport) NewClientConn(context.Context, string, string) (*ClientConn, error) #75772 +pkg net/http, type ClientConn struct #75772 diff --git a/doc/next/6-stdlib/99-minor/net/http/75772.md b/doc/next/6-stdlib/99-minor/net/http/75772.md new file mode 100644 index 00000000000..59d3e87e26d --- /dev/null +++ b/doc/next/6-stdlib/99-minor/net/http/75772.md @@ -0,0 +1,5 @@ +The new [Transport.NewClientConn] method returns a client connection +to an HTTP server. +Most users should continue to use [Transport.RoundTrip] to make requests, +which manages a pool of connection. +`NewClientConn` is useful for users who need to implement their own conection management. diff --git a/src/net/http/clientconn.go b/src/net/http/clientconn.go new file mode 100644 index 00000000000..2e1f33e21b1 --- /dev/null +++ b/src/net/http/clientconn.go @@ -0,0 +1,456 @@ +// Copyright 2025 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 http + +import ( + "context" + "errors" + "fmt" + "net" + "net/http/httptrace" + "net/url" + "sync" +) + +// A ClientConn is a client connection to an HTTP server. +// +// Unlike a [Transport], a ClientConn represents a single connection. +// Most users should use a Transport rather than creating client connections directly. +type ClientConn struct { + cc genericClientConn + + stateHookMu sync.Mutex + userStateHook func(*ClientConn) + stateHookRunning bool + lastAvailable int + lastInFlight int + lastClosed bool +} + +// newClientConner is the interface implemented by HTTP/2 transports to create new client conns. +// +// The http package (this package) needs a way to ask the http2 package to +// create a client connection. +// +// Transport.TLSNextProto["h2"] contains a function which appears to do this, +// but for historical reasons it does not: The TLSNextProto function adds a +// *tls.Conn to the http2.Transport's connection pool and returns a RoundTripper +// which is backed by that connection pool. NewClientConn needs a way to get a +// single client connection out of the http2 package. +// +// The http2 package registers a RoundTripper with Transport.RegisterProtocol. +// If this RoundTripper implements newClientConner, then Transport.NewClientConn will use +// it to create new HTTP/2 client connections. +type newClientConner interface { + // NewClientConn creates a new client connection from a net.Conn. + // + // The RoundTripper returned by NewClientConn must implement genericClientConn. + // (We don't define NewClientConn as returning genericClientConn, + // because either we'd need to make genericClientConn an exported type + // or define it as a type alias. Neither is particularly appealing.) + // + // The state hook passed here is the internal state hook + // (ClientConn.maybeRunStateHook). The internal state hook calls + // the user state hook (if any), which is set by the user with + // ClientConn.SetStateHook. + // + // The client connection should arrange to call the internal state hook + // when the connection closes, when requests complete, and when the + // connection concurrency limit changes. + // + // The client connection must call the internal state hook when the connection state + // changes asynchronously, such as when a request completes. + // + // The internal state hook need not be called after synchronous changes to the state: + // Close, Reserve, Release, and RoundTrip calls which don't start a request + // do not need to call the hook. + // + // The general idea is that if we call (for example) Close, + // we know that the connection state has probably changed and we + // don't need the state hook to tell us that. + // However, if the connection closes asynchronously + // (because, for example, the other end of the conn closed it), + // the state hook needs to inform us. + NewClientConn(nc net.Conn, internalStateHook func()) (RoundTripper, error) +} + +// genericClientConn is an interface implemented by HTTP/2 client conns +// returned from newClientConner.NewClientConn. +// +// See the newClientConner doc comment for more information. +type genericClientConn interface { + Close() error + Err() error + RoundTrip(req *Request) (*Response, error) + Reserve() error + Release() + Available() int + InFlight() int +} + +// NewClientConn creates a new client connection to the given address. +// +// If scheme is "http", the connection is unencrypted. +// If scheme is "https", the connection uses TLS. +// +// The protocol used for the new connection is determined by the scheme, +// Transport.Protocols configuration field, and protocols supported by the +// server. See Transport.Protocols for more details. +// +// If Transport.Proxy is set and indicates that a request sent to the given +// address should use a proxy, the new connection uses that proxy. +// +// NewClientConn always creates a new connection, +// even if the Transport has an existing cached connection to the given host. +// +// The new connection is not added to the Transport's connection cache, +// and will not be used by [Transport.RoundTrip]. +// It does not count against the MaxIdleConns and MaxConnsPerHost limits. +// +// The caller is responsible for closing the new connection. +func (t *Transport) NewClientConn(ctx context.Context, scheme, address string) (*ClientConn, error) { + t.nextProtoOnce.Do(t.onceSetNextProtoDefaults) + + switch scheme { + case "http", "https": + default: + return nil, fmt.Errorf("net/http: invalid scheme %q", scheme) + } + + host, port, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + if port == "" { + port = schemePort(scheme) + } + + var proxyURL *url.URL + if t.Proxy != nil { + // Transport.Proxy takes a *Request, so create a fake one to pass it. + req := &Request{ + ctx: ctx, + Method: "GET", + URL: &url.URL{ + Scheme: scheme, + Host: host, + Path: "/", + }, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: make(Header), + Body: NoBody, + Host: host, + } + var err error + proxyURL, err = t.Proxy(req) + if err != nil { + return nil, err + } + } + + cm := connectMethod{ + targetScheme: scheme, + targetAddr: net.JoinHostPort(host, port), + proxyURL: proxyURL, + } + + // The state hook is a bit tricky: + // The persistConn has a state hook which calls ClientConn.maybeRunStateHook, + // which in turn calls the user-provided state hook (if any). + // + // ClientConn.maybeRunStateHook handles debouncing hook calls for both + // HTTP/1 and HTTP/2. + // + // Since there's no need to change the persistConn's hook, we set it at creation time. + cc := &ClientConn{} + const isClientConn = true + pconn, err := t.dialConn(ctx, cm, isClientConn, cc.maybeRunStateHook) + if err != nil { + return nil, err + } + + // Note that cc.maybeRunStateHook may have been called + // in the short window between dialConn and now. + // This is fine. + cc.stateHookMu.Lock() + defer cc.stateHookMu.Unlock() + if pconn.alt != nil { + // If pconn.alt is set, this is a connection implemented in another package + // (probably x/net/http2) or the bundled copy in h2_bundle.go. + gc, ok := pconn.alt.(genericClientConn) + if !ok { + return nil, errors.New("http: NewClientConn returned something that is not a ClientConn") + } + cc.cc = gc + cc.lastAvailable = gc.Available() + } else { + // This is an HTTP/1 connection. + pconn.availch = make(chan struct{}, 1) + pconn.availch <- struct{}{} + cc.cc = http1ClientConn{pconn} + cc.lastAvailable = 1 + } + return cc, nil +} + +// Close closes the connection. +// Outstanding RoundTrip calls are interrupted. +func (cc *ClientConn) Close() error { + defer cc.maybeRunStateHook() + return cc.cc.Close() +} + +// Err reports any fatal connection errors. +// It returns nil if the connection is usable. +// If it returns non-nil, the connection can no longer be used. +func (cc *ClientConn) Err() error { + return cc.cc.Err() +} + +func validateClientConnRequest(req *Request) error { + if req.URL == nil { + return errors.New("http: nil Request.URL") + } + if req.Header == nil { + return errors.New("http: nil Request.Header") + } + // Validate the outgoing headers. + if err := validateHeaders(req.Header); err != "" { + return fmt.Errorf("http: invalid header %s", err) + } + // Validate the outgoing trailers too. + if err := validateHeaders(req.Trailer); err != "" { + return fmt.Errorf("http: invalid trailer %s", err) + } + if req.Method != "" && !validMethod(req.Method) { + return fmt.Errorf("http: invalid method %q", req.Method) + } + if req.URL.Host == "" { + return errors.New("http: no Host in request URL") + } + return nil +} + +// RoundTrip implements the [RoundTripper] interface. +// +// The request is sent on the client connection, +// regardless of the URL being requested or any proxy settings. +// +// If the connection is at its concurrency limit, +// RoundTrip waits for the connection to become available +// before sending the request. +func (cc *ClientConn) RoundTrip(req *Request) (*Response, error) { + defer cc.maybeRunStateHook() + if err := validateClientConnRequest(req); err != nil { + cc.Release() + return nil, err + } + return cc.cc.RoundTrip(req) +} + +// Available reports the number of requests that may be sent +// to the connection without blocking. +// It returns 0 if the connection is closed. +func (cc *ClientConn) Available() int { + return cc.cc.Available() +} + +// InFlight reports the number of requests in flight, +// including reserved requests. +// It returns 0 if the connection is closed. +func (cc *ClientConn) InFlight() int { + return cc.cc.InFlight() +} + +// Reserve reserves a concurrency slot on the connection. +// If Reserve returns nil, one additional RoundTrip call may be made +// without waiting for an existing request to complete. +// +// The reserved concurrency slot is accounted as an in-flight request. +// A successful call to RoundTrip will decrement the Available count +// and increment the InFlight count. +// +// Each successful call to Reserve should be followed by exactly one call +// to RoundTrip or Release, which will consume or release the reservation. +// +// If the connection is closed or at its concurrency limit, +// Reserve returns an error. +func (cc *ClientConn) Reserve() error { + defer cc.maybeRunStateHook() + return cc.cc.Reserve() +} + +// Release releases an unused concurrency slot reserved by Reserve. +// If there are no reserved concurrency slots, it has no effect. +func (cc *ClientConn) Release() { + defer cc.maybeRunStateHook() + cc.cc.Release() +} + +// shouldRunStateHook returns the user's state hook if we should call it, +// or nil if we don't need to call it at this time. +func (cc *ClientConn) shouldRunStateHook(stopRunning bool) func(*ClientConn) { + cc.stateHookMu.Lock() + defer cc.stateHookMu.Unlock() + if cc.cc == nil { + return nil + } + if stopRunning { + cc.stateHookRunning = false + } + if cc.userStateHook == nil { + return nil + } + if cc.stateHookRunning { + return nil + } + var ( + available = cc.Available() + inFlight = cc.InFlight() + closed = cc.Err() != nil + ) + var hook func(*ClientConn) + if available > cc.lastAvailable || inFlight < cc.lastInFlight || closed != cc.lastClosed { + hook = cc.userStateHook + cc.stateHookRunning = true + } + cc.lastAvailable = available + cc.lastInFlight = inFlight + cc.lastClosed = closed + return hook +} + +func (cc *ClientConn) maybeRunStateHook() { + hook := cc.shouldRunStateHook(false) + if hook == nil { + return + } + // Run the hook synchronously. + // + // This means that if, for example, the user calls resp.Body.Close to finish a request, + // the Close call will synchronously run the hook, giving the hook the chance to + // return the ClientConn to a connection pool before the next request is made. + hook(cc) + // The connection state may have changed while the hook was running, + // in which case we need to run it again. + // + // If we do need to run the hook again, do so in a new goroutine to avoid blocking + // the current goroutine indefinitely. + hook = cc.shouldRunStateHook(true) + if hook != nil { + go func() { + for hook != nil { + hook(cc) + hook = cc.shouldRunStateHook(true) + } + }() + } +} + +// SetStateHook arranges for f to be called when the state of the connection changes. +// At most one call to f is made at a time. +// If the connection's state has changed since it was created, +// f is called immediately in a separate goroutine. +// f may be called synchronously from RoundTrip or Response.Body.Close. +// +// If SetStateHook is called multiple times, the new hook replaces the old one. +// If f is nil, no further calls will be made to f after SetStateHook returns. +// +// f is called when Available increases (more requests may be sent on the connection), +// InFlight decreases (existing requests complete), or Err begins returning non-nil +// (the connection is no longer usable). +func (cc *ClientConn) SetStateHook(f func(*ClientConn)) { + cc.stateHookMu.Lock() + cc.userStateHook = f + cc.stateHookMu.Unlock() + cc.maybeRunStateHook() +} + +// http1ClientConn is a genericClientConn implementation backed by +// an HTTP/1 *persistConn (pconn.alt is nil). +type http1ClientConn struct { + pconn *persistConn +} + +func (cc http1ClientConn) RoundTrip(req *Request) (*Response, error) { + ctx := req.Context() + trace := httptrace.ContextClientTrace(ctx) + + // Convert Request.Cancel into context cancelation. + ctx, cancel := context.WithCancelCause(req.Context()) + if req.Cancel != nil { + go awaitLegacyCancel(ctx, cancel, req) + } + + treq := &transportRequest{Request: req, trace: trace, ctx: ctx, cancel: cancel} + resp, err := cc.pconn.roundTrip(treq) + if err != nil { + return nil, err + } + resp.Request = req + return resp, nil +} + +func (cc http1ClientConn) Close() error { + cc.pconn.close(errors.New("ClientConn closed")) + return nil +} + +func (cc http1ClientConn) Err() error { + select { + case <-cc.pconn.closech: + return cc.pconn.closed + default: + return nil + } +} + +func (cc http1ClientConn) Available() int { + cc.pconn.mu.Lock() + defer cc.pconn.mu.Unlock() + if cc.pconn.closed != nil || cc.pconn.reserved || cc.pconn.inFlight { + return 0 + } + return 1 +} + +func (cc http1ClientConn) InFlight() int { + cc.pconn.mu.Lock() + defer cc.pconn.mu.Unlock() + if cc.pconn.closed == nil && (cc.pconn.reserved || cc.pconn.inFlight) { + return 1 + } + return 0 +} + +func (cc http1ClientConn) Reserve() error { + cc.pconn.mu.Lock() + defer cc.pconn.mu.Unlock() + if cc.pconn.closed != nil { + return cc.pconn.closed + } + select { + case <-cc.pconn.availch: + default: + return errors.New("connection is unavailable") + } + cc.pconn.reserved = true + return nil +} + +func (cc http1ClientConn) Release() { + cc.pconn.mu.Lock() + defer cc.pconn.mu.Unlock() + if cc.pconn.reserved { + select { + case cc.pconn.availch <- struct{}{}: + default: + panic("cannot release reservation") + } + cc.pconn.reserved = false + } +} diff --git a/src/net/http/clientconn_test.go b/src/net/http/clientconn_test.go new file mode 100644 index 00000000000..e46f6e6a513 --- /dev/null +++ b/src/net/http/clientconn_test.go @@ -0,0 +1,374 @@ +// Copyright 2025 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 http_test + +import ( + "context" + "fmt" + "io" + "net/http" + "sync" + "sync/atomic" + "testing" + "testing/synctest" +) + +func TestTransportNewClientConnRoundTrip(t *testing.T) { run(t, testTransportNewClientConnRoundTrip) } +func testTransportNewClientConnRoundTrip(t *testing.T, mode testMode) { + cst := newClientServerTest(t, mode, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + io.WriteString(w, req.Host) + }), optFakeNet) + + scheme := mode.Scheme() // http or https + cc, err := cst.tr.NewClientConn(t.Context(), scheme, cst.ts.Listener.Addr().String()) + if err != nil { + t.Fatal(err) + } + defer cc.Close() + + // Send requests for a couple different domains. + // All use the same connection. + for _, host := range []string{"example.tld", "go.dev"} { + req, _ := http.NewRequest("GET", fmt.Sprintf("%v://%v/", scheme, host), nil) + resp, err := cc.RoundTrip(req) + if err != nil { + t.Fatal(err) + } + got, _ := io.ReadAll(resp.Body) + if string(got) != host { + t.Errorf("got response body %q, want %v", got, host) + } + resp.Body.Close() + + // CloseIdleConnections does not close connections created by NewClientConn. + cst.tr.CloseIdleConnections() + } + + if err := cc.Err(); err != nil { + t.Errorf("before close: ClientConn.Err() = %v, want nil", err) + } + + cc.Close() + if err := cc.Err(); err == nil { + t.Errorf("after close: ClientConn.Err() = nil, want error") + } + + req, _ := http.NewRequest("GET", scheme+"://example.tld/", nil) + resp, err := cc.RoundTrip(req) + if err == nil { + resp.Body.Close() + t.Errorf("after close: cc.RoundTrip succeeded, want error") + } + t.Log(err) +} + +func newClientConnTest(t testing.TB, mode testMode, h http.HandlerFunc, opts ...any) (*clientServerTest, *http.ClientConn) { + if h == nil { + h = func(w http.ResponseWriter, req *http.Request) {} + } + cst := newClientServerTest(t, mode, h, opts...) + cc, err := cst.tr.NewClientConn(t.Context(), mode.Scheme(), cst.ts.Listener.Addr().String()) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + cc.Close() + }) + synctest.Wait() + return cst, cc +} + +// TestClientConnReserveAll reserves every concurrency slot on a connection. +func TestClientConnReserveAll(t *testing.T) { runSynctest(t, testClientConnReserveAll) } +func testClientConnReserveAll(t *testing.T, mode testMode) { + cst, cc := newClientConnTest(t, mode, nil, optFakeNet, func(s *http.Server) { + s.HTTP2 = &http.HTTP2Config{ + MaxConcurrentStreams: 3, + } + }) + + want := 1 + switch mode { + case http2Mode, http2UnencryptedMode: + want = cst.ts.Config.HTTP2.MaxConcurrentStreams + } + available := cc.Available() + if available != want { + t.Fatalf("cc.Available() = %v, want %v", available, want) + } + + // Reserve every available concurrency slot on the connection. + for i := range available { + if err := cc.Reserve(); err != nil { + t.Fatalf("cc.Reserve() #%v = %v, want nil", i, err) + } + if got, want := cc.Available(), available-i-1; got != want { + t.Fatalf("cc.Available() = %v, want %v", got, want) + } + if got, want := cc.InFlight(), i+1; got != want { + t.Fatalf("cc.InFlight() = %v, want %v", got, want) + } + } + + // The next reservation attempt should fail, since every slot is consumed. + if err := cc.Reserve(); err == nil { + t.Fatalf("cc.Reserve() = nil, want error") + } +} + +// TestClientConnReserveParallel starts concurrent goroutines which reserve every +// concurrency slot on a connection. +func TestClientConnReserveParallel(t *testing.T) { runSynctest(t, testClientConnReserveParallel) } +func testClientConnReserveParallel(t *testing.T, mode testMode) { + _, cc := newClientConnTest(t, mode, nil, optFakeNet, func(s *http.Server) { + s.HTTP2 = &http.HTTP2Config{ + MaxConcurrentStreams: 3, + } + }) + var ( + wg sync.WaitGroup + mu sync.Mutex + success int + failure int + ) + available := cc.Available() + const extra = 2 + for range available + extra { + wg.Go(func() { + err := cc.Reserve() + mu.Lock() + defer mu.Unlock() + if err == nil { + success++ + } else { + failure++ + } + }) + } + wg.Wait() + + if got, want := success, available; got != want { + t.Errorf("%v successful reservations, want %v", got, want) + } + if got, want := failure, extra; got != want { + t.Errorf("%v failed reservations, want %v", got, want) + } +} + +// TestClientConnReserveRelease repeatedly reserves and releases concurrency slots. +func TestClientConnReserveRelease(t *testing.T) { runSynctest(t, testClientConnReserveRelease) } +func testClientConnReserveRelease(t *testing.T, mode testMode) { + _, cc := newClientConnTest(t, mode, nil, optFakeNet, func(s *http.Server) { + s.HTTP2 = &http.HTTP2Config{ + MaxConcurrentStreams: 3, + } + }) + + available := cc.Available() + for i := range 2 * available { + if err := cc.Reserve(); err != nil { + t.Fatalf("cc.Reserve() #%v = %v, want nil", i, err) + } + cc.Release() + } + + if got, want := cc.Available(), available; got != want { + t.Fatalf("cc.Available() = %v, want %v", available, want) + } +} + +// TestClientConnReserveAndConsume reserves a concurrency slot on a connection, +// and then verifies that various events consume the reservation. +func TestClientConnReserveAndConsume(t *testing.T) { + for _, test := range []struct { + name string + consume func(t *testing.T, cc *http.ClientConn, mode testMode) + handler func(w http.ResponseWriter, req *http.Request, donec chan struct{}) + h1Closed bool + }{{ + // Explicit release. + name: "release", + consume: func(t *testing.T, cc *http.ClientConn, mode testMode) { + cc.Release() + }, + }, { + // Invalid request sent to RoundTrip. + name: "invalid field name", + consume: func(t *testing.T, cc *http.ClientConn, mode testMode) { + req, _ := http.NewRequest("GET", mode.Scheme()+"://example.tld/", nil) + req.Header["invalid field name"] = []string{"x"} + _, err := cc.RoundTrip(req) + if err == nil { + t.Fatalf("RoundTrip succeeded, want failure") + } + }, + }, { + // Successful request/response cycle. + name: "body close", + consume: func(t *testing.T, cc *http.ClientConn, mode testMode) { + req, _ := http.NewRequest("GET", mode.Scheme()+"://example.tld/", nil) + resp, err := cc.RoundTrip(req) + if err != nil { + t.Fatalf("RoundTrip: %v", err) + } + resp.Body.Close() + }, + }, { + // Request context canceled before headers received. + name: "cancel", + consume: func(t *testing.T, cc *http.ClientConn, mode testMode) { + ctx, cancel := context.WithCancel(t.Context()) + go func() { + req, _ := http.NewRequestWithContext(ctx, "GET", mode.Scheme()+"://example.tld/", nil) + _, err := cc.RoundTrip(req) + if err == nil { + t.Errorf("RoundTrip succeeded, want failure") + } + }() + synctest.Wait() + cancel() + }, + handler: func(w http.ResponseWriter, req *http.Request, donec chan struct{}) { + <-donec + }, + // An HTTP/1 connection is closed after a request is canceled on it. + h1Closed: true, + }, { + // Response body closed before full response received. + name: "early body close", + consume: func(t *testing.T, cc *http.ClientConn, mode testMode) { + req, _ := http.NewRequest("GET", mode.Scheme()+"://example.tld/", nil) + resp, err := cc.RoundTrip(req) + if err != nil { + t.Fatalf("RoundTrip: %v", err) + } + t.Logf("%T", resp.Body) + resp.Body.Close() + }, + handler: func(w http.ResponseWriter, req *http.Request, donec chan struct{}) { + w.WriteHeader(200) + http.NewResponseController(w).Flush() + <-donec + }, + // An HTTP/1 connection is closed after a request is canceled on it. + h1Closed: true, + }} { + t.Run(test.name, func(t *testing.T) { + runSynctest(t, func(t *testing.T, mode testMode) { + donec := make(chan struct{}) + defer close(donec) + handler := func(w http.ResponseWriter, req *http.Request) { + if test.handler != nil { + test.handler(w, req, donec) + } + } + + _, cc := newClientConnTest(t, mode, handler, optFakeNet) + stateHookCalls := 0 + cc.SetStateHook(func(cc *http.ClientConn) { + stateHookCalls++ + }) + synctest.Wait() + stateHookCalls = 0 // ignore any initial update call + + avail := cc.Available() + if err := cc.Reserve(); err != nil { + t.Fatalf("cc.Reserve() = %v, want nil", err) + } + synctest.Wait() + if got, want := stateHookCalls, 0; got != want { + t.Errorf("connection state hook calls: %v, want %v", got, want) + } + + test.consume(t, cc, mode) + synctest.Wait() + + // State hook should be called, either to report the + // connection availability increasing or the connection closing. + if got, want := stateHookCalls, 1; got != want { + t.Errorf("connection state hook calls: %v, want %v", got, want) + } + + if test.h1Closed && (mode == http1Mode || mode == https1Mode) { + if got, want := cc.Available(), 0; got != want { + t.Errorf("cc.Available() = %v, want %v", got, want) + } + if got, want := cc.InFlight(), 0; got != want { + t.Errorf("cc.InFlight() = %v, want %v", got, want) + } + if err := cc.Err(); err == nil { + t.Errorf("cc.Err() = nil, want closed connection") + } + } else { + if got, want := cc.Available(), avail; got != want { + t.Errorf("cc.Available() = %v, want %v", got, want) + } + if got, want := cc.InFlight(), 0; got != want { + t.Errorf("cc.InFlight() = %v, want %v", got, want) + } + if err := cc.Err(); err != nil { + t.Errorf("cc.Err() = %v, want nil", err) + } + } + + if cc.Available() > 0 { + if err := cc.Reserve(); err != nil { + t.Errorf("cc.Reserve() = %v, want success", err) + } + } + }) + }) + } + +} + +// TestClientConnRoundTripBlocks verifies that RoundTrip blocks until a concurrency +// slot is available on a connection. +func TestClientConnRoundTripBlocks(t *testing.T) { runSynctest(t, testClientConnRoundTripBlocks) } +func testClientConnRoundTripBlocks(t *testing.T, mode testMode) { + var handlerCalls atomic.Int64 + requestc := make(chan struct{}) + handler := func(w http.ResponseWriter, req *http.Request) { + handlerCalls.Add(1) + <-requestc + } + _, cc := newClientConnTest(t, mode, handler, optFakeNet, func(s *http.Server) { + s.HTTP2 = &http.HTTP2Config{ + MaxConcurrentStreams: 3, + } + }) + + available := cc.Available() + var responses atomic.Int64 + const extra = 2 + for range available + extra { + go func() { + req, _ := http.NewRequest("GET", mode.Scheme()+"://example.tld/", nil) + resp, err := cc.RoundTrip(req) + responses.Add(1) + if err != nil { + t.Errorf("RoundTrip: %v", err) + return + } + resp.Body.Close() + }() + } + + synctest.Wait() + if got, want := int(handlerCalls.Load()), available; got != want { + t.Errorf("got %v handler calls, want %v", got, want) + } + if got, want := int(responses.Load()), 0; got != want { + t.Errorf("got %v responses, want %v", got, want) + } + + for i := range available + extra { + requestc <- struct{}{} + synctest.Wait() + if got, want := int(responses.Load()), i+1; got != want { + t.Errorf("got %v responses, want %v", got, want) + } + } +} diff --git a/src/net/http/clientserver_test.go b/src/net/http/clientserver_test.go index 8665bae38ad..2bca1d32536 100644 --- a/src/net/http/clientserver_test.go +++ b/src/net/http/clientserver_test.go @@ -46,6 +46,16 @@ const ( http2UnencryptedMode = testMode("h2unencrypted") // HTTP/2 ) +func (m testMode) Scheme() string { + switch m { + case http1Mode, http2UnencryptedMode: + return "http" + case https1Mode, http2Mode: + return "https" + } + panic("unknown testMode") +} + type testNotParallelOpt struct{} var ( diff --git a/src/net/http/transport.go b/src/net/http/transport.go index 033eddf1f5b..26a25d2a022 100644 --- a/src/net/http/transport.go +++ b/src/net/http/transport.go @@ -1067,6 +1067,22 @@ func (t *Transport) tryPutIdleConn(pconn *persistConn) error { return errConnBroken } pconn.markReused() + if pconn.isClientConn { + // internalStateHook is always set for conns created by NewClientConn. + defer pconn.internalStateHook() + pconn.mu.Lock() + defer pconn.mu.Unlock() + if !pconn.inFlight { + panic("pconn is not in flight") + } + pconn.inFlight = false + select { + case pconn.availch <- struct{}{}: + default: + panic("unable to make pconn available") + } + return nil + } t.idleMu.Lock() defer t.idleMu.Unlock() @@ -1243,6 +1259,9 @@ func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool) { // removeIdleConn marks pconn as dead. func (t *Transport) removeIdleConn(pconn *persistConn) bool { + if pconn.isClientConn { + return true + } t.idleMu.Lock() defer t.idleMu.Unlock() return t.removeIdleConnLocked(pconn) @@ -1625,7 +1644,8 @@ func (t *Transport) dialConnFor(w *wantConn) { return } - pc, err := t.dialConn(ctx, w.cm) + const isClientConn = false + pc, err := t.dialConn(ctx, w.cm, isClientConn, nil) delivered := w.tryDeliver(pc, err, time.Time{}) if err == nil && (!delivered || pc.alt != nil) { // pconn was not passed to w, @@ -1746,15 +1766,17 @@ type erringRoundTripper interface { var testHookProxyConnectTimeout = context.WithTimeout -func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *persistConn, err error) { +func (t *Transport) dialConn(ctx context.Context, cm connectMethod, isClientConn bool, internalStateHook func()) (pconn *persistConn, err error) { pconn = &persistConn{ - t: t, - cacheKey: cm.key(), - reqch: make(chan requestAndChan, 1), - writech: make(chan writeRequest, 1), - closech: make(chan struct{}), - writeErrCh: make(chan error, 1), - writeLoopDone: make(chan struct{}), + t: t, + cacheKey: cm.key(), + reqch: make(chan requestAndChan, 1), + writech: make(chan writeRequest, 1), + closech: make(chan struct{}), + writeErrCh: make(chan error, 1), + writeLoopDone: make(chan struct{}), + isClientConn: isClientConn, + internalStateHook: internalStateHook, } trace := httptrace.ContextClientTrace(ctx) wrapErr := func(err error) error { @@ -1927,6 +1949,21 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *pers t.Protocols != nil && t.Protocols.UnencryptedHTTP2() && !t.Protocols.HTTP1() + + if isClientConn && (unencryptedHTTP2 || (pconn.tlsState != nil && pconn.tlsState.NegotiatedProtocol == "h2")) { + altProto, _ := t.altProto.Load().(map[string]RoundTripper) + h2, ok := altProto["https"].(newClientConner) + if !ok { + return nil, errors.New("http: HTTP/2 implementation does not support NewClientConn (update golang.org/x/net?)") + } + alt, err := h2.NewClientConn(pconn.conn, internalStateHook) + if err != nil { + pconn.conn.Close() + return nil, err + } + return &persistConn{t: t, cacheKey: pconn.cacheKey, alt: alt, isClientConn: true}, nil + } + if unencryptedHTTP2 { next, ok := t.TLSNextProto[nextProtoUnencryptedHTTP2] if !ok { @@ -2081,19 +2118,21 @@ type persistConn struct { // If it's non-nil, the rest of the fields are unused. alt RoundTripper - t *Transport - cacheKey connectMethodKey - conn net.Conn - tlsState *tls.ConnectionState - br *bufio.Reader // from conn - bw *bufio.Writer // to conn - nwrite int64 // bytes written - reqch chan requestAndChan // written by roundTrip; read by readLoop - writech chan writeRequest // written by roundTrip; read by writeLoop - closech chan struct{} // closed when conn closed - isProxy bool - sawEOF bool // whether we've seen EOF from conn; owned by readLoop - readLimit int64 // bytes allowed to be read; owned by readLoop + t *Transport + cacheKey connectMethodKey + conn net.Conn + tlsState *tls.ConnectionState + br *bufio.Reader // from conn + bw *bufio.Writer // to conn + nwrite int64 // bytes written + reqch chan requestAndChan // written by roundTrip; read by readLoop + writech chan writeRequest // written by roundTrip; read by writeLoop + closech chan struct{} // closed when conn closed + availch chan struct{} // ClientConn only: contains a value when conn is usable + isProxy bool + sawEOF bool // whether we've seen EOF from conn; owned by readLoop + isClientConn bool // whether this is a ClientConn (outside any pool) + readLimit int64 // bytes allowed to be read; owned by readLoop // writeErrCh passes the request write error (usually nil) // from the writeLoop goroutine to the readLoop which passes // it off to the res.Body reader, which then uses it to decide @@ -2108,9 +2147,13 @@ type persistConn struct { mu sync.Mutex // guards following fields numExpectedResponses int - closed error // set non-nil when conn is closed, before closech is closed - canceledErr error // set non-nil if conn is canceled - reused bool // whether conn has had successful request/response and is being reused. + closed error // set non-nil when conn is closed, before closech is closed + canceledErr error // set non-nil if conn is canceled + reused bool // whether conn has had successful request/response and is being reused. + reserved bool // ClientConn only: concurrency slot reserved + inFlight bool // ClientConn only: request is in flight + internalStateHook func() // ClientConn state hook + // mutateHeaderFunc is an optional func to modify extra // headers on each outbound request before it's written. (the // original Request given to RoundTrip is not modified) @@ -2250,6 +2293,9 @@ func (pc *persistConn) readLoop() { defer func() { pc.close(closeErr) pc.t.removeIdleConn(pc) + if pc.internalStateHook != nil { + pc.internalStateHook() + } }() tryPutIdleConn := func(treq *transportRequest) bool { @@ -2753,9 +2799,32 @@ var ( testHookReadLoopBeforeNextRead = nop ) +func (pc *persistConn) waitForAvailability(ctx context.Context) error { + select { + case <-pc.availch: + return nil + case <-pc.closech: + return pc.closed + case <-ctx.Done(): + return ctx.Err() + } +} + func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err error) { testHookEnterRoundTrip() + pc.mu.Lock() + if pc.isClientConn { + if !pc.reserved { + pc.mu.Unlock() + if err := pc.waitForAvailability(req.ctx); err != nil { + return nil, err + } + pc.mu.Lock() + } + pc.reserved = false + pc.inFlight = true + } pc.numExpectedResponses++ headerFn := pc.mutateHeaderFunc pc.mu.Unlock() From 6851795fb6cda61e2c8396c36da187a2bd87b29e Mon Sep 17 00:00:00 2001 From: David Finkel Date: Fri, 23 May 2025 16:04:08 -0400 Subject: [PATCH 010/140] runtime: add GODEBUG=tracebacklabels=1 to include pprof labels in tracebacks Copy LabelSet to an internal package as label.Set, and include (escaped) labels within goroutine stack dumps. Labels are added to the goroutine header as quoted key:value pairs, so the line may get long if there are a lot of labels. To handle escaping, we add a printescaped function to the runtime and hook it up to the print function in the compiler with a new runtime.quoted type that's a sibling to runtime.hex. (in fact, we leverage some of the machinery from printhex to generate escape sequences). The escaping can be improved for printable runes outside basic ASCII (particularly for languages using non-latin stripts). Additionally, invalid UTF-8 can be improved. So we can experiment with the output format make this opt-in via a a new tracebacklabels GODEBUG var. Updates #23458 Updates #76349 Change-Id: I08e78a40c55839a809236fff593ef2090c13c036 Reviewed-on: https://go-review.googlesource.com/c/go/+/694119 LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Pratt Auto-Submit: Michael Pratt Reviewed-by: Alan Donovan --- doc/godebug.md | 6 ++ .../internal/typecheck/_builtin/runtime.go | 1 + src/cmd/compile/internal/typecheck/builtin.go | 1 + src/cmd/compile/internal/walk/builtin.go | 19 +++-- src/cmd/internal/goobj/builtinlist.go | 1 + src/go/build/deps_test.go | 5 +- src/internal/runtime/pprof/label/labelset.go | 25 +++++++ src/runtime/export_test.go | 12 ++++ src/runtime/pprof/label.go | 65 ++++++++--------- src/runtime/pprof/label_test.go | 23 +++--- src/runtime/pprof/pprof.go | 4 +- src/runtime/pprof/pprof_test.go | 7 +- src/runtime/pprof/proto.go | 4 +- src/runtime/pprof/runtime_test.go | 7 +- src/runtime/print.go | 59 ++++++++++++++-- src/runtime/print_quoted_test.go | 38 ++++++++++ src/runtime/runtime1.go | 5 ++ src/runtime/traceback.go | 14 ++++ src/runtime/traceback_test.go | 70 +++++++++++++++++++ 19 files changed, 297 insertions(+), 69 deletions(-) create mode 100644 src/internal/runtime/pprof/label/labelset.go create mode 100644 src/runtime/print_quoted_test.go diff --git a/doc/godebug.md b/doc/godebug.md index d9ae462b980..0d1cd6b6627 100644 --- a/doc/godebug.md +++ b/doc/godebug.md @@ -168,6 +168,12 @@ allows malformed hostnames containing colons outside of a bracketed IPv6 address The default `urlstrictcolons=1` rejects URLs such as `http://localhost:1:2` or `http://::1/`. Colons are permitted as part of a bracketed IPv6 address, such as `http://[::1]/`. +Go 1.26 added a new `tracebacklabels` setting that controls the inclusion of +goroutine labels set through the the `runtime/pprof` package. Setting `tracebacklabels=1` +includes these key/value pairs in the goroutine status header of runtime +tracebacks and debug=2 runtime/pprof stack dumps. This format may change in the future. +(see go.dev/issue/76349) + ### Go 1.25 Go 1.25 added a new `decoratemappings` setting that controls whether the Go diff --git a/src/cmd/compile/internal/typecheck/_builtin/runtime.go b/src/cmd/compile/internal/typecheck/_builtin/runtime.go index d43a9e5bf2d..35fbbb6b120 100644 --- a/src/cmd/compile/internal/typecheck/_builtin/runtime.go +++ b/src/cmd/compile/internal/typecheck/_builtin/runtime.go @@ -57,6 +57,7 @@ func printuint(uint64) func printcomplex128(complex128) func printcomplex64(complex64) func printstring(string) +func printquoted(string) func printpointer(any) func printuintptr(uintptr) func printiface(any) diff --git a/src/cmd/compile/internal/typecheck/builtin.go b/src/cmd/compile/internal/typecheck/builtin.go index dd9f1593f38..8a505073f7a 100644 --- a/src/cmd/compile/internal/typecheck/builtin.go +++ b/src/cmd/compile/internal/typecheck/builtin.go @@ -64,6 +64,7 @@ var runtimeDecls = [...]struct { {"printcomplex128", funcTag, 27}, {"printcomplex64", funcTag, 29}, {"printstring", funcTag, 31}, + {"printquoted", funcTag, 31}, {"printpointer", funcTag, 32}, {"printuintptr", funcTag, 33}, {"printiface", funcTag, 32}, diff --git a/src/cmd/compile/internal/walk/builtin.go b/src/cmd/compile/internal/walk/builtin.go index 2f2a2c62f16..c698caddce9 100644 --- a/src/cmd/compile/internal/walk/builtin.go +++ b/src/cmd/compile/internal/walk/builtin.go @@ -729,13 +729,18 @@ func walkPrint(nn *ir.CallExpr, init *ir.Nodes) ir.Node { if ir.IsConst(n, constant.String) { cs = ir.StringVal(n) } - switch cs { - case " ": - on = typecheck.LookupRuntime("printsp") - case "\n": - on = typecheck.LookupRuntime("printnl") - default: - on = typecheck.LookupRuntime("printstring") + // Print values of the named type `quoted` using printquoted. + if types.RuntimeSymName(n.Type().Sym()) == "quoted" { + on = typecheck.LookupRuntime("printquoted") + } else { + switch cs { + case " ": + on = typecheck.LookupRuntime("printsp") + case "\n": + on = typecheck.LookupRuntime("printnl") + default: + on = typecheck.LookupRuntime("printstring") + } } default: badtype(ir.OPRINT, n.Type(), nil) diff --git a/src/cmd/internal/goobj/builtinlist.go b/src/cmd/internal/goobj/builtinlist.go index b3320808f11..918ade191dd 100644 --- a/src/cmd/internal/goobj/builtinlist.go +++ b/src/cmd/internal/goobj/builtinlist.go @@ -43,6 +43,7 @@ var builtins = [...]struct { {"runtime.printcomplex128", 1}, {"runtime.printcomplex64", 1}, {"runtime.printstring", 1}, + {"runtime.printquoted", 1}, {"runtime.printpointer", 1}, {"runtime.printuintptr", 1}, {"runtime.printiface", 1}, diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 2ee5114fd7d..9a6b86b65c8 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -58,6 +58,7 @@ var depsRules = ` internal/nettrace, internal/platform, internal/profilerecord, + internal/runtime/pprof/label, internal/syslist, internal/trace/tracev2, internal/trace/traceviewer/format, @@ -85,6 +86,7 @@ var depsRules = ` internal/goos, internal/itoa, internal/profilerecord, + internal/runtime/pprof/label, internal/strconv, internal/trace/tracev2, math/bits, @@ -672,7 +674,8 @@ var depsRules = ` < net/http/fcgi; # Profiling - FMT, compress/gzip, encoding/binary, sort, text/tabwriter + internal/runtime/pprof/label, runtime, context < internal/runtime/pprof; + FMT, compress/gzip, encoding/binary, sort, text/tabwriter, internal/runtime/pprof, internal/runtime/pprof/label < runtime/pprof; OS, compress/gzip, internal/lazyregexp diff --git a/src/internal/runtime/pprof/label/labelset.go b/src/internal/runtime/pprof/label/labelset.go new file mode 100644 index 00000000000..d3046d407c2 --- /dev/null +++ b/src/internal/runtime/pprof/label/labelset.go @@ -0,0 +1,25 @@ +// Copyright 2025 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 label provides common declarations used by both the [runtime] and [runtime/pprof] packages. +// The [Set] type is used for goroutine labels, and is duplicated as +// [runtime/pprof.LabelSet]. The type is duplicated due to go.dev/issue/65437 +// preventing the use of a type-alias in an existing public interface. +package label + +// Label is a key/value pair of strings. +type Label struct { + Key string + Value string +} + +// Set is a set of labels. +type Set struct { + List []Label +} + +// NewSet constructs a LabelSet that wraps the provided labels. +func NewSet(list []Label) Set { + return Set{List: list} +} diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go index 6e0360aacab..26341c43001 100644 --- a/src/runtime/export_test.go +++ b/src/runtime/export_test.go @@ -2064,3 +2064,15 @@ func HexdumpWords(p, bytes uintptr) string { } return string(buf[:n]) } + +// DumpPrintQuoted provides access to print(quoted()) for the tests in +// runtime/print_quoted_test.go, allowing us to test that implementation. +func DumpPrintQuoted(s string) string { + gp := getg() + gp.writebuf = make([]byte, 0, 1<<20) + print(quoted(s)) + buf := gp.writebuf + gp.writebuf = nil + + return string(buf) +} diff --git a/src/runtime/pprof/label.go b/src/runtime/pprof/label.go index 4c1d8d38ce5..09dd1de6515 100644 --- a/src/runtime/pprof/label.go +++ b/src/runtime/pprof/label.go @@ -7,18 +7,14 @@ package pprof import ( "context" "fmt" + "internal/runtime/pprof/label" "slices" "strings" ) -type label struct { - key string - value string -} - // LabelSet is a set of labels. type LabelSet struct { - list []label + list []label.Label } // labelContextKey is the type of contextKeys used for profiler labels. @@ -36,7 +32,7 @@ func labelValue(ctx context.Context) labelMap { // This is an initial implementation, but it will be replaced with something // that admits incremental immutable modification more efficiently. type labelMap struct { - LabelSet + label.Set } // String satisfies Stringer and returns key, value pairs in a consistent @@ -45,10 +41,10 @@ func (l *labelMap) String() string { if l == nil { return "" } - keyVals := make([]string, 0, len(l.list)) + keyVals := make([]string, 0, len(l.Set.List)) - for _, lbl := range l.list { - keyVals = append(keyVals, fmt.Sprintf("%q:%q", lbl.key, lbl.value)) + for _, lbl := range l.Set.List { + keyVals = append(keyVals, fmt.Sprintf("%q:%q", lbl.Key, lbl.Value)) } slices.Sort(keyVals) @@ -59,38 +55,39 @@ func (l *labelMap) String() string { // A label overwrites a prior label with the same key. func WithLabels(ctx context.Context, labels LabelSet) context.Context { parentLabels := labelValue(ctx) - return context.WithValue(ctx, labelContextKey{}, &labelMap{mergeLabelSets(parentLabels.LabelSet, labels)}) + return context.WithValue(ctx, labelContextKey{}, &labelMap{mergeLabelSets(parentLabels.Set, labels)}) } -func mergeLabelSets(left, right LabelSet) LabelSet { - if len(left.list) == 0 { - return right +func mergeLabelSets(left label.Set, right LabelSet) label.Set { + if len(left.List) == 0 { + return label.NewSet(right.list) } else if len(right.list) == 0 { return left } + lList, rList := left.List, right.list l, r := 0, 0 - result := make([]label, 0, len(right.list)) - for l < len(left.list) && r < len(right.list) { - switch strings.Compare(left.list[l].key, right.list[r].key) { + result := make([]label.Label, 0, len(rList)) + for l < len(lList) && r < len(rList) { + switch strings.Compare(lList[l].Key, rList[r].Key) { case -1: // left key < right key - result = append(result, left.list[l]) + result = append(result, lList[l]) l++ case 1: // right key < left key - result = append(result, right.list[r]) + result = append(result, rList[r]) r++ case 0: // keys are equal, right value overwrites left value - result = append(result, right.list[r]) + result = append(result, rList[r]) l++ r++ } } // Append the remaining elements - result = append(result, left.list[l:]...) - result = append(result, right.list[r:]...) + result = append(result, lList[l:]...) + result = append(result, rList[r:]...) - return LabelSet{list: result} + return label.NewSet(result) } // Labels takes an even number of strings representing key-value pairs @@ -103,20 +100,20 @@ func Labels(args ...string) LabelSet { if len(args)%2 != 0 { panic("uneven number of arguments to pprof.Labels") } - list := make([]label, 0, len(args)/2) + list := make([]label.Label, 0, len(args)/2) sortedNoDupes := true for i := 0; i+1 < len(args); i += 2 { - list = append(list, label{key: args[i], value: args[i+1]}) + list = append(list, label.Label{Key: args[i], Value: args[i+1]}) sortedNoDupes = sortedNoDupes && (i < 2 || args[i] > args[i-2]) } if !sortedNoDupes { // slow path: keys are unsorted, contain duplicates, or both - slices.SortStableFunc(list, func(a, b label) int { - return strings.Compare(a.key, b.key) + slices.SortStableFunc(list, func(a, b label.Label) int { + return strings.Compare(a.Key, b.Key) }) - deduped := make([]label, 0, len(list)) + deduped := make([]label.Label, 0, len(list)) for i, lbl := range list { - if i == 0 || lbl.key != list[i-1].key { + if i == 0 || lbl.Key != list[i-1].Key { deduped = append(deduped, lbl) } else { deduped[len(deduped)-1] = lbl @@ -131,9 +128,9 @@ func Labels(args ...string) LabelSet { // whether that label exists. func Label(ctx context.Context, key string) (string, bool) { ctxLabels := labelValue(ctx) - for _, lbl := range ctxLabels.list { - if lbl.key == key { - return lbl.value, true + for _, lbl := range ctxLabels.Set.List { + if lbl.Key == key { + return lbl.Value, true } } return "", false @@ -143,8 +140,8 @@ func Label(ctx context.Context, key string) (string, bool) { // The function f should return true to continue iteration or false to stop iteration early. func ForLabels(ctx context.Context, f func(key, value string) bool) { ctxLabels := labelValue(ctx) - for _, lbl := range ctxLabels.list { - if !f(lbl.key, lbl.value) { + for _, lbl := range ctxLabels.Set.List { + if !f(lbl.Key, lbl.Value) { break } } diff --git a/src/runtime/pprof/label_test.go b/src/runtime/pprof/label_test.go index 3018693c247..ded8b295750 100644 --- a/src/runtime/pprof/label_test.go +++ b/src/runtime/pprof/label_test.go @@ -7,19 +7,20 @@ package pprof import ( "context" "fmt" + "internal/runtime/pprof/label" "reflect" "slices" "strings" "testing" ) -func labelsSorted(ctx context.Context) []label { - ls := []label{} +func labelsSorted(ctx context.Context) []label.Label { + ls := []label.Label{} ForLabels(ctx, func(key, value string) bool { - ls = append(ls, label{key, value}) + ls = append(ls, label.Label{Key: key, Value: value}) return true }) - slices.SortFunc(ls, func(a, b label) int { return strings.Compare(a.key, b.key) }) + slices.SortFunc(ls, func(a, b label.Label) int { return strings.Compare(a.Key, b.Key) }) return ls } @@ -39,7 +40,7 @@ func TestContextLabels(t *testing.T) { t.Errorf(`Label(ctx, "key"): got %v, %v; want "value", ok`, v, ok) } gotLabels := labelsSorted(ctx) - wantLabels := []label{{"key", "value"}} + wantLabels := []label.Label{{Key: "key", Value: "value"}} if !reflect.DeepEqual(gotLabels, wantLabels) { t.Errorf("(sorted) labels on context: got %v, want %v", gotLabels, wantLabels) } @@ -51,7 +52,7 @@ func TestContextLabels(t *testing.T) { t.Errorf(`Label(ctx, "key2"): got %v, %v; want "value2", ok`, v, ok) } gotLabels = labelsSorted(ctx) - wantLabels = []label{{"key", "value"}, {"key2", "value2"}} + wantLabels = []label.Label{{Key: "key", Value: "value"}, {Key: "key2", Value: "value2"}} if !reflect.DeepEqual(gotLabels, wantLabels) { t.Errorf("(sorted) labels on context: got %v, want %v", gotLabels, wantLabels) } @@ -63,7 +64,7 @@ func TestContextLabels(t *testing.T) { t.Errorf(`Label(ctx, "key3"): got %v, %v; want "value3", ok`, v, ok) } gotLabels = labelsSorted(ctx) - wantLabels = []label{{"key", "value3"}, {"key2", "value2"}} + wantLabels = []label.Label{{Key: "key", Value: "value3"}, {Key: "key2", Value: "value2"}} if !reflect.DeepEqual(gotLabels, wantLabels) { t.Errorf("(sorted) labels on context: got %v, want %v", gotLabels, wantLabels) } @@ -75,7 +76,7 @@ func TestContextLabels(t *testing.T) { t.Errorf(`Label(ctx, "key4"): got %v, %v; want "value4b", ok`, v, ok) } gotLabels = labelsSorted(ctx) - wantLabels = []label{{"key", "value3"}, {"key2", "value2"}, {"key4", "value4b"}} + wantLabels = []label.Label{{Key: "key", Value: "value3"}, {Key: "key2", Value: "value2"}, {Key: "key4", Value: "value4b"}} if !reflect.DeepEqual(gotLabels, wantLabels) { t.Errorf("(sorted) labels on context: got %v, want %v", gotLabels, wantLabels) } @@ -93,18 +94,18 @@ func TestLabelMapStringer(t *testing.T) { expected: "{}", }, { m: labelMap{ - Labels("foo", "bar"), + label.NewSet(Labels("foo", "bar").list), }, expected: `{"foo":"bar"}`, }, { m: labelMap{ - Labels( + label.NewSet(Labels( "foo", "bar", "key1", "value1", "key2", "value2", "key3", "value3", "key4WithNewline", "\nvalue4", - ), + ).list), }, expected: `{"foo":"bar", "key1":"value1", "key2":"value2", "key3":"value3", "key4WithNewline":"\nvalue4"}`, }, diff --git a/src/runtime/pprof/pprof.go b/src/runtime/pprof/pprof.go index c617a8b26a4..c27df228978 100644 --- a/src/runtime/pprof/pprof.go +++ b/src/runtime/pprof/pprof.go @@ -547,8 +547,8 @@ func printCountProfile(w io.Writer, debug int, name string, p countProfile) erro var labels func() if p.Label(idx) != nil { labels = func() { - for _, lbl := range p.Label(idx).list { - b.pbLabel(tagSample_Label, lbl.key, lbl.value, 0) + for _, lbl := range p.Label(idx).Set.List { + b.pbLabel(tagSample_Label, lbl.Key, lbl.Value, 0) } } } diff --git a/src/runtime/pprof/pprof_test.go b/src/runtime/pprof/pprof_test.go index 4c9279c5a6f..e46e4f9d273 100644 --- a/src/runtime/pprof/pprof_test.go +++ b/src/runtime/pprof/pprof_test.go @@ -12,6 +12,7 @@ import ( "fmt" "internal/abi" "internal/profile" + "internal/runtime/pprof/label" "internal/syscall/unix" "internal/testenv" "io" @@ -1462,11 +1463,11 @@ func TestGoroutineCounts(t *testing.T) { goroutineProf.WriteTo(&w, 1) prof := w.String() - labels := labelMap{Labels("label", "value")} + labels := labelMap{label.NewSet(Labels("label", "value").list)} labelStr := "\n# labels: " + labels.String() - selfLabel := labelMap{Labels("self-label", "self-value")} + selfLabel := labelMap{label.NewSet(Labels("self-label", "self-value").list)} selfLabelStr := "\n# labels: " + selfLabel.String() - fingLabel := labelMap{Labels("fing-label", "fing-value")} + fingLabel := labelMap{label.NewSet(Labels("fing-label", "fing-value").list)} fingLabelStr := "\n# labels: " + fingLabel.String() orderedPrefix := []string{ "\n50 @ ", diff --git a/src/runtime/pprof/proto.go b/src/runtime/pprof/proto.go index 28ceb815421..5ad917f14a7 100644 --- a/src/runtime/pprof/proto.go +++ b/src/runtime/pprof/proto.go @@ -367,8 +367,8 @@ func (b *profileBuilder) build() error { var labels func() if e.tag != nil { labels = func() { - for _, lbl := range (*labelMap)(e.tag).list { - b.pbLabel(tagSample_Label, lbl.key, lbl.value, 0) + for _, lbl := range (*labelMap)(e.tag).Set.List { + b.pbLabel(tagSample_Label, lbl.Key, lbl.Value, 0) } } } diff --git a/src/runtime/pprof/runtime_test.go b/src/runtime/pprof/runtime_test.go index 353ed8a3f1d..acdd4c8d15a 100644 --- a/src/runtime/pprof/runtime_test.go +++ b/src/runtime/pprof/runtime_test.go @@ -92,9 +92,10 @@ func getProfLabel() map[string]string { if l == nil { return map[string]string{} } - m := make(map[string]string, len(l.list)) - for _, lbl := range l.list { - m[lbl.key] = lbl.value + ls := l.Set.List + m := make(map[string]string, len(ls)) + for _, lbl := range ls { + m[lbl.Key] = lbl.Value } return m } diff --git a/src/runtime/print.go b/src/runtime/print.go index d2733fb2661..5d1bc22809b 100644 --- a/src/runtime/print.go +++ b/src/runtime/print.go @@ -13,6 +13,10 @@ import ( // should use printhex instead of printuint (decimal). type hex uint64 +// The compiler knows that a print of a value of this type should use +// printquoted instead of printstring. +type quoted string + func bytes(s string) (ret []byte) { rp := (*slice)(unsafe.Pointer(&ret)) sp := stringStructOf(&s) @@ -169,24 +173,67 @@ func printint(v int64) { var minhexdigits = 0 // protected by printlock -func printhex(v uint64) { +func printhexopts(include0x bool, mindigits int, v uint64) { const dig = "0123456789abcdef" var buf [100]byte i := len(buf) for i--; i > 0; i-- { buf[i] = dig[v%16] - if v < 16 && len(buf)-i >= minhexdigits { + if v < 16 && len(buf)-i >= mindigits { break } v /= 16 } - i-- - buf[i] = 'x' - i-- - buf[i] = '0' + if include0x { + i-- + buf[i] = 'x' + i-- + buf[i] = '0' + } gwrite(buf[i:]) } +func printhex(v uint64) { + printhexopts(true, minhexdigits, v) +} + +func printquoted(s string) { + printlock() + gwrite([]byte(`"`)) + for _, r := range s { + switch r { + case '\n': + gwrite([]byte(`\n`)) + continue + case '\r': + gwrite([]byte(`\r`)) + continue + case '\t': + gwrite([]byte(`\t`)) + print() + continue + case '\\', '"': + gwrite([]byte{byte('\\'), byte(r)}) + continue + } + // For now, only allow basic printable ascii through unescaped + if r >= ' ' && r <= '~' { + gwrite([]byte{byte(r)}) + } else if r < 127 { + gwrite(bytes(`\x`)) + printhexopts(false, 2, uint64(r)) + } else if r < 0x1_0000 { + gwrite(bytes(`\u`)) + printhexopts(false, 4, uint64(r)) + } else { + gwrite(bytes(`\U`)) + printhexopts(false, 8, uint64(r)) + } + } + gwrite([]byte{byte('"')}) + printunlock() +} + func printpointer(p unsafe.Pointer) { printhex(uint64(uintptr(p))) } diff --git a/src/runtime/print_quoted_test.go b/src/runtime/print_quoted_test.go new file mode 100644 index 00000000000..f9e947b569c --- /dev/null +++ b/src/runtime/print_quoted_test.go @@ -0,0 +1,38 @@ +// Copyright 2025 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 runtime_test + +import ( + "runtime" + "testing" +) + +func TestPrintQuoted(t *testing.T) { + for _, tbl := range []struct { + in, expected string + }{ + {in: "baz", expected: `"baz"`}, + {in: "foobar", expected: `"foobar"`}, + // make sure newlines get escaped + {in: "baz\n", expected: `"baz\n"`}, + // make sure null and escape bytes are properly escaped + {in: "b\033it", expected: `"b\x1bit"`}, + {in: "b\000ar", expected: `"b\x00ar"`}, + // verify that simple 16-bit unicode runes are escaped with \u, including a greek upper-case sigma and an arbitrary unicode character. + {in: "\u1234Σ", expected: `"\u1234\u03a3"`}, + // verify that 32-bit unicode runes are escaped with \U along with tabs + {in: "fizz\tle", expected: `"fizz\tle"`}, + {in: "\U00045678boop", expected: `"\U00045678boop"`}, + // verify carriage returns and backslashes get escaped along with our nulls, newlines and a 32-bit unicode character + {in: "fiz\\zl\re", expected: `"fiz\\zl\re"`}, + } { + t.Run(tbl.in, func(t *testing.T) { + out := runtime.DumpPrintQuoted(tbl.in) + if out != tbl.expected { + t.Errorf("unexpected output for print(escaped(%q));\n got: %s\nwant: %s", tbl.in, out, tbl.expected) + } + }) + } +} diff --git a/src/runtime/runtime1.go b/src/runtime/runtime1.go index 64ee4c8d2e9..965ff8ab516 100644 --- a/src/runtime/runtime1.go +++ b/src/runtime/runtime1.go @@ -360,6 +360,10 @@ var debug struct { // but allowing it is convenient for testing and for programs // that do an os.Setenv in main.init or main.main. asynctimerchan atomic.Int32 + + // tracebacklabels controls the inclusion of goroutine labels in the + // goroutine status header line. + tracebacklabels atomic.Int32 } var dbgvars = []*dbgVar{ @@ -394,6 +398,7 @@ var dbgvars = []*dbgVar{ {name: "traceallocfree", atomic: &debug.traceallocfree}, {name: "tracecheckstackownership", value: &debug.traceCheckStackOwnership}, {name: "tracebackancestors", value: &debug.tracebackancestors}, + {name: "tracebacklabels", atomic: &debug.tracebacklabels, def: 0}, {name: "tracefpunwindoff", value: &debug.tracefpunwindoff}, {name: "updatemaxprocs", value: &debug.updatemaxprocs, def: 1}, } diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go index 74aaeba8767..1c6f24c0332 100644 --- a/src/runtime/traceback.go +++ b/src/runtime/traceback.go @@ -8,6 +8,7 @@ import ( "internal/abi" "internal/bytealg" "internal/goarch" + "internal/runtime/pprof/label" "internal/runtime/sys" "internal/stringslite" "unsafe" @@ -1270,6 +1271,19 @@ func goroutineheader(gp *g) { if bubble := gp.bubble; bubble != nil { print(", synctest bubble ", bubble.id) } + if gp.labels != nil && debug.tracebacklabels.Load() == 1 { + labels := (*label.Set)(gp.labels).List + if len(labels) > 0 { + print(" labels:{") + for i, kv := range labels { + print(quoted(kv.Key), ": ", quoted(kv.Value)) + if i < len(labels)-1 { + print(", ") + } + } + print("}") + } + } print("]:\n") } diff --git a/src/runtime/traceback_test.go b/src/runtime/traceback_test.go index 1dac91311ca..d47f4ab7455 100644 --- a/src/runtime/traceback_test.go +++ b/src/runtime/traceback_test.go @@ -6,6 +6,7 @@ package runtime_test import ( "bytes" + "context" "fmt" "internal/abi" "internal/asan" @@ -15,6 +16,7 @@ import ( "regexp" "runtime" "runtime/debug" + "runtime/pprof" "strconv" "strings" "sync" @@ -882,3 +884,71 @@ func TestSetCgoTracebackNoCgo(t *testing.T) { t.Fatalf("want %s, got %s\n", want, output) } } + +func TestTracebackGoroutineLabels(t *testing.T) { + t.Setenv("GODEBUG", "tracebacklabels=1") + for _, tbl := range []struct { + l pprof.LabelSet + expTB string + }{ + {l: pprof.Labels("foobar", "baz"), expTB: `{"foobar": "baz"}`}, + // Make sure the keys are sorted because the runtime/pprof package sorts for consistency + {l: pprof.Labels("foobar", "baz", "fizzle", "bit"), expTB: `{"fizzle": "bit", "foobar": "baz"}`}, + // make sure newlines get escaped + {l: pprof.Labels("fizzle", "bit", "foobar", "baz\n"), expTB: `{"fizzle": "bit", "foobar": "baz\n"}`}, + // make sure null and escape bytes are properly escaped + {l: pprof.Labels("fizzle", "b\033it", "foo\"ba\x00r", "baz\n"), expTB: `{"fizzle": "b\x1bit", "foo\"ba\x00r": "baz\n"}`}, + // verify that simple 16-bit unicode runes are escaped with \u, including a greek upper-case sigma and an arbitrary unicode character. + {l: pprof.Labels("fizzle", "\u1234Σ", "fooba\x00r", "baz\n"), expTB: `{"fizzle": "\u1234\u03a3", "fooba\x00r": "baz\n"}`}, + // verify that 32-bit unicode runes are escaped with \U along with tabs + {l: pprof.Labels("fizz\tle", "\U00045678boop", "fooba\x00r", "baz\n"), expTB: `{"fizz\tle": "\U00045678boop", "fooba\x00r": "baz\n"}`}, + // verify carriage returns and backslashes get escaped along with our nulls, newlines and a 32-bit unicode character + {l: pprof.Labels("fiz\\zl\re", "\U00045678boop", "fooba\x00r", "baz\n"), expTB: `{"fiz\\zl\re": "\U00045678boop", "fooba\x00r": "baz\n"}`}, + } { + t.Run(tbl.expTB, func(t *testing.T) { + verifyLabels := func() { + t.Helper() + buf := make([]byte, 1<<10) + // We collect the stack only for this goroutine (by passing + // false to runtime.Stack). We expect to see the parent's goroutine labels in the traceback. + stack := string(buf[:runtime.Stack(buf, false)]) + if !strings.Contains(stack, "labels:"+tbl.expTB) { + t.Errorf("failed to find goroutine labels with labels %s (as %s) got:\n%s\n---", tbl.l, tbl.expTB, stack) + } + } + // Use a clean context so the testing package can add whatever goroutine labels it wants to the testing.T context. + lblCtx := pprof.WithLabels(context.Background(), tbl.l) + pprof.SetGoroutineLabels(lblCtx) + var wg sync.WaitGroup + // make sure the labels are visible in a child goroutine + wg.Go(verifyLabels) + // and in this parent goroutine + verifyLabels() + wg.Wait() + }) + } +} + +func TestTracebackGoroutineLabelsDisabledGODEBUG(t *testing.T) { + t.Setenv("GODEBUG", "tracebacklabels=0") + lbls := pprof.Labels("foobar", "baz") + verifyLabels := func() { + t.Helper() + buf := make([]byte, 1<<10) + // We collect the stack only for this goroutine (by passing + // false to runtime.Stack). + stack := string(buf[:runtime.Stack(buf, false)]) + if strings.Contains(stack, "labels:") { + t.Errorf("found goroutine labels with labels %s got:\n%s\n---", lbls, stack) + } + } + // Use a clean context so the testing package can add whatever goroutine labels it wants to the testing.T context. + lblCtx := pprof.WithLabels(context.Background(), lbls) + pprof.SetGoroutineLabels(lblCtx) + var wg sync.WaitGroup + // make sure the labels are visible in a child goroutine + wg.Go(verifyLabels) + // and in this parent goroutine + verifyLabels() + wg.Wait() +} From 2f7fd5714f6e7ba095d7322b656e6d65ba4fbeca Mon Sep 17 00:00:00 2001 From: Ian Alexander Date: Sat, 15 Nov 2025 19:19:10 -0500 Subject: [PATCH 011/140] cmd/go: add setters for critical State fields This commit unexports critical State fields and provides setter methods to update their values. [git-generate] cd src/cmd/go/internal/modfetch rf ' add fetch.go:490 var jitsu int = 0 // rf marker mv State.GoSumFile State.GoSumFile_ mv State.WorkspaceGoSumFiles State.WorkspaceGoSumFiles_ add jitsu \ func (s *State) GoSumFile() string { \ return "" } \ \ func (s *State) SetGoSumFile(str string) { \ } \ \ func (s *State) AddWorkspaceGoSumFile(file string) { \ s.WorkspaceGoSumFiles_ = append(s.WorkspaceGoSumFiles_, file) \ } \ ex { var s *State var x string s.GoSumFile_ = x -> s.SetGoSumFile(x) } rm jitsu ' cd ../modload sed -i ' s/modfetch.ModuleFetchState.WorkspaceGoSumFiles_ = append(modfetch.ModuleFetchState.WorkspaceGoSumFiles_, sumFile)/modfetch.ModuleFetchState.AddWorkspaceGoSumFile(sumFile)/ ' init.go for dir in modcmd modload ; do cd ../${dir} rf ' ex { import "cmd/go/internal/modfetch" var s *modfetch.State var x string s.GoSumFile_ = x -> s.SetGoSumFile(x) } ' done cd ../modfetch rf ' mv State.GoSumFile_ State.goSumFile mv State.WorkspaceGoSumFiles_ State.workspaceGoSumFiles add State.GoSumFile: return s.goSumFile rm State.GoSumFile://+1 add State.SetGoSumFile: s.goSumFile = str ' Change-Id: Iff694aad7ad1cc62d2096c210dbaa3cce2b4061d Reviewed-on: https://go-review.googlesource.com/c/go/+/720840 Reviewed-by: Michael Matloob Reviewed-by: Mark Freeman LUCI-TryBot-Result: Go LUCI --- src/cmd/go/internal/modfetch/fetch.go | 40 +++++++++++++++++---------- src/cmd/go/internal/modload/init.go | 6 ++-- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/cmd/go/internal/modfetch/fetch.go b/src/cmd/go/internal/modfetch/fetch.go index 0a84aecd426..87ea829180f 100644 --- a/src/cmd/go/internal/modfetch/fetch.go +++ b/src/cmd/go/internal/modfetch/fetch.go @@ -462,9 +462,9 @@ type modSumStatus struct { // State holds a snapshot of the global state of the modfetch package. type State struct { // path to go.sum; set by package modload - GoSumFile string + goSumFile string // path to module go.sums in workspace; set by package modload - WorkspaceGoSumFiles []string + workspaceGoSumFiles []string // The Lookup cache is used cache the work done by Lookup. // It is important that the global functions of this package that access it do not // do so after they return. @@ -488,6 +488,18 @@ func NewState() *State { return s } +func (s *State) GoSumFile() string { + return s.goSumFile +} + +func (s *State) SetGoSumFile(str string) { + s.goSumFile = str +} + +func (s *State) AddWorkspaceGoSumFile(file string) { + s.workspaceGoSumFiles = append(s.workspaceGoSumFiles, file) +} + // Reset resets globals in the modfetch package, so previous loads don't affect // contents of go.sum files. func Reset() { @@ -510,15 +522,15 @@ func SetState(newState State) (oldState State) { defer goSum.mu.Unlock() oldState = State{ - GoSumFile: ModuleFetchState.GoSumFile, - WorkspaceGoSumFiles: ModuleFetchState.WorkspaceGoSumFiles, + goSumFile: ModuleFetchState.goSumFile, + workspaceGoSumFiles: ModuleFetchState.workspaceGoSumFiles, lookupCache: ModuleFetchState.lookupCache, downloadCache: ModuleFetchState.downloadCache, sumState: goSum.sumState, } - ModuleFetchState.GoSumFile = newState.GoSumFile - ModuleFetchState.WorkspaceGoSumFiles = newState.WorkspaceGoSumFiles + ModuleFetchState.SetGoSumFile(newState.goSumFile) + ModuleFetchState.workspaceGoSumFiles = newState.workspaceGoSumFiles // Uses of lookupCache and downloadCache both can call checkModSum, // which in turn sets the used bit on goSum.status for modules. // Set (or reset) them so used can be computed properly. @@ -535,7 +547,7 @@ func SetState(newState State) (oldState State) { // use of go.sum is now enabled. // The goSum lock must be held. func initGoSum() (bool, error) { - if ModuleFetchState.GoSumFile == "" { + if ModuleFetchState.goSumFile == "" { return false, nil } if goSum.m != nil { @@ -546,7 +558,7 @@ func initGoSum() (bool, error) { goSum.status = make(map[modSum]modSumStatus) goSum.w = make(map[string]map[module.Version][]string) - for _, f := range ModuleFetchState.WorkspaceGoSumFiles { + for _, f := range ModuleFetchState.workspaceGoSumFiles { goSum.w[f] = make(map[module.Version][]string) _, err := readGoSumFile(goSum.w[f], f) if err != nil { @@ -554,7 +566,7 @@ func initGoSum() (bool, error) { } } - enabled, err := readGoSumFile(goSum.m, ModuleFetchState.GoSumFile) + enabled, err := readGoSumFile(goSum.m, ModuleFetchState.goSumFile) goSum.enabled = enabled return enabled, err } @@ -800,7 +812,7 @@ func checkModSum(mod module.Version, h string) error { // goSum.mu must be locked. func haveModSumLocked(mod module.Version, h string) bool { sumFileName := "go.sum" - if strings.HasSuffix(ModuleFetchState.GoSumFile, "go.work.sum") { + if strings.HasSuffix(ModuleFetchState.goSumFile, "go.work.sum") { sumFileName = "go.work.sum" } for _, vh := range goSum.m[mod] { @@ -944,7 +956,7 @@ Outer: if readonly { return ErrGoSumDirty } - if fsys.Replaced(ModuleFetchState.GoSumFile) { + if fsys.Replaced(ModuleFetchState.goSumFile) { base.Fatalf("go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay") } @@ -954,7 +966,7 @@ Outer: defer unlock() } - err := lockedfile.Transform(ModuleFetchState.GoSumFile, func(data []byte) ([]byte, error) { + err := lockedfile.Transform(ModuleFetchState.goSumFile, func(data []byte) ([]byte, error) { tidyGoSum := tidyGoSum(data, keep) return tidyGoSum, nil }) @@ -973,7 +985,7 @@ Outer: func TidyGoSum(keep map[module.Version]bool) (before, after []byte) { goSum.mu.Lock() defer goSum.mu.Unlock() - before, err := lockedfile.Read(ModuleFetchState.GoSumFile) + before, err := lockedfile.Read(ModuleFetchState.goSumFile) if err != nil && !errors.Is(err, fs.ErrNotExist) { base.Fatalf("reading go.sum: %v", err) } @@ -990,7 +1002,7 @@ func tidyGoSum(data []byte, keep map[module.Version]bool) []byte { // truncated the file to remove erroneous hashes, and we shouldn't restore // them without good reason. goSum.m = make(map[module.Version][]string, len(goSum.m)) - readGoSum(goSum.m, ModuleFetchState.GoSumFile, data) + readGoSum(goSum.m, ModuleFetchState.goSumFile, data) for ms, st := range goSum.status { if st.used && !sumInWorkspaceModulesLocked(ms.mod) { addModSumLocked(ms.mod, ms.sum) diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go index c4ff9656694..ad7b08e062a 100644 --- a/src/cmd/go/internal/modload/init.go +++ b/src/cmd/go/internal/modload/init.go @@ -929,9 +929,9 @@ func loadModFile(loaderstate *State, ctx context.Context, opts *PackageOpts) (*R } for _, modRoot := range loaderstate.modRoots { sumFile := strings.TrimSuffix(modFilePath(modRoot), ".mod") + ".sum" - modfetch.ModuleFetchState.WorkspaceGoSumFiles = append(modfetch.ModuleFetchState.WorkspaceGoSumFiles, sumFile) + modfetch.ModuleFetchState.AddWorkspaceGoSumFile(sumFile) } - modfetch.ModuleFetchState.GoSumFile = loaderstate.workFilePath + ".sum" + modfetch.ModuleFetchState.SetGoSumFile(loaderstate.workFilePath + ".sum") } else if len(loaderstate.modRoots) == 0 { // We're in module mode, but not inside a module. // @@ -951,7 +951,7 @@ func loadModFile(loaderstate *State, ctx context.Context, opts *PackageOpts) (*R // // See golang.org/issue/32027. } else { - modfetch.ModuleFetchState.GoSumFile = strings.TrimSuffix(modFilePath(loaderstate.modRoots[0]), ".mod") + ".sum" + modfetch.ModuleFetchState.SetGoSumFile(strings.TrimSuffix(modFilePath(loaderstate.modRoots[0]), ".mod") + ".sum") } if len(loaderstate.modRoots) == 0 { // TODO(#49228): Instead of creating a fake module with an empty modroot, From d3e11b3f9011a08600a14fdbb94a9ca9da970344 Mon Sep 17 00:00:00 2001 From: Ian Alexander Date: Sat, 15 Nov 2025 23:08:02 -0500 Subject: [PATCH 012/140] cmd/go/internal/modload: make State.modfetchState a pointer This change aligns modfetch.State with modload.State by using pointer parameters and receivers. [git-generate] cd src/cmd/go/internal/modload sed -i ' s/oldState/old/ s/old := State{/old = \&State{/ s/func (s \*State) setState(new State) State {/func (s *State) setState(new *State) (old *State) {/ s/setState(State{})/setState(NewState())/ ' init.go cd ../modfetch sed -i ' s/oldState = State{/oldState = \&State{/ s/func SetState(newState State) (oldState State) {/func SetState(newState *State) (oldState *State) {/ s/SetState(State{})/SetState(NewState())/ ' fetch.go cd ../modload sed -i ' s/old.modfetchState = modfetch.SetState(new.modfetchState)/_ = modfetch.SetState(\&new.modfetchState)/ ' init.go rf ' # # Prepare to swap the existing modfetchState field for a pointer type # mv State.modfetchState State.modfetchState_ add State:/modfetchState_/+0 modfetchState *modfetch.State # # Update State.setState to set & restore additional values # add State.setState:/s\.requirements,/+0 workFilePath: s.workFilePath, add State.setState:/s\.workFilePath,/+0 modfetchState: s.modfetchState, # # Swap the existing modfetchState field for a pointer type # add init.go:/.* = modfetch.SetState\(.*\)/-0 s.modfetchState = new.modfetchState add init.go:/.* = modfetch.SetState\(.*\)/-0 old.modfetchState = modfetch.SetState(s.modfetchState) // TODO(jitsu): remove after completing global state elimination rm init.go:/_ = modfetch.SetState\(.*\)/ rm State.modfetchState_ ' sed -i ' s/return &State{}/s := new(State)\ns.modfetchState = modfetch.NewState()\nreturn s/ ' init.go go fmt Change-Id: I0602ecf976fd3ee93844e77989291d729ad71595 Reviewed-on: https://go-review.googlesource.com/c/go/+/720900 Reviewed-by: Michael Matloob LUCI-TryBot-Result: Go LUCI Reviewed-by: Mark Freeman --- src/cmd/go/internal/modfetch/fetch.go | 6 +++--- src/cmd/go/internal/modload/init.go | 24 ++++++++++++++++-------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/cmd/go/internal/modfetch/fetch.go b/src/cmd/go/internal/modfetch/fetch.go index 87ea829180f..8a17a374d0f 100644 --- a/src/cmd/go/internal/modfetch/fetch.go +++ b/src/cmd/go/internal/modfetch/fetch.go @@ -503,14 +503,14 @@ func (s *State) AddWorkspaceGoSumFile(file string) { // Reset resets globals in the modfetch package, so previous loads don't affect // contents of go.sum files. func Reset() { - SetState(State{}) + SetState(NewState()) } // SetState sets the global state of the modfetch package to the newState, and returns the previous // global state. newState should have been returned by SetState, or be an empty State. // There should be no concurrent calls to any of the exported functions of this package with // a call to SetState because it will modify the global state in a non-thread-safe way. -func SetState(newState State) (oldState State) { +func SetState(newState *State) (oldState *State) { if newState.lookupCache == nil { newState.lookupCache = new(par.Cache[lookupCacheKey, Repo]) } @@ -521,7 +521,7 @@ func SetState(newState State) (oldState State) { goSum.mu.Lock() defer goSum.mu.Unlock() - oldState = State{ + oldState = &State{ goSumFile: ModuleFetchState.goSumFile, workspaceGoSumFiles: ModuleFetchState.workspaceGoSumFiles, lookupCache: ModuleFetchState.lookupCache, diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go index ad7b08e062a..4680ea427e9 100644 --- a/src/cmd/go/internal/modload/init.go +++ b/src/cmd/go/internal/modload/init.go @@ -80,7 +80,7 @@ func EnterWorkspace(loaderstate *State, ctx context.Context) (exit func(), err e } // Reset the state to a clean state. - oldstate := loaderstate.setState(State{}) + oldstate := loaderstate.setState(NewState()) loaderstate.ForceUseModules = true // Load in workspace mode. @@ -385,11 +385,11 @@ func WorkFilePath(loaderstate *State) string { // Reset clears all the initialized, cached state about the use of modules, // so that we can start over. func (s *State) Reset() { - s.setState(State{}) + s.setState(NewState()) } -func (s *State) setState(new State) State { - oldState := State{ +func (s *State) setState(new *State) (old *State) { + old = &State{ initialized: s.initialized, ForceUseModules: s.ForceUseModules, RootMode: s.RootMode, @@ -397,6 +397,8 @@ func (s *State) setState(new State) State { modulesEnabled: cfg.ModulesEnabled, MainModules: s.MainModules, requirements: s.requirements, + workFilePath: s.workFilePath, + modfetchState: s.modfetchState, } s.initialized = new.initialized s.ForceUseModules = new.ForceUseModules @@ -409,8 +411,10 @@ func (s *State) setState(new State) State { // The modfetch package's global state is used to compute // the go.sum file, so save and restore it along with the // modload state. - oldState.modfetchState = modfetch.SetState(new.modfetchState) - return oldState + s.modfetchState = new.modfetchState + old.modfetchState = modfetch.SetState(s.modfetchState) // TODO(jitsu): remove after completing global state elimination + + return old } type State struct { @@ -448,10 +452,14 @@ type State struct { // Set to the path to the go.work file, or "" if workspace mode is // disabled workFilePath string - modfetchState modfetch.State + modfetchState *modfetch.State } -func NewState() *State { return &State{} } +func NewState() *State { + s := new(State) + s.modfetchState = modfetch.NewState() + return s +} // Init determines whether module mode is enabled, locates the root of the // current module (if any), sets environment variables for Git subprocesses, and From 1dc1505d4ad0d3a3172d90e16858697b0dca0ab7 Mon Sep 17 00:00:00 2001 From: Ian Alexander Date: Mon, 17 Nov 2025 16:38:22 -0500 Subject: [PATCH 013/140] cmd/go/internal/modfetch: rename State to Fetcher This change renames the type State to Fetcher to better reflect its purpose. The global variable ModuleFetchState is also renamed to Fetcher_, which will continue to be gradually eliminated as with all global state in the modfetch package. [git-generate] cd src/cmd/go/internal/modfetch rf ' mv State Fetcher mv ModuleFetchState Fetcher_ mv NewState NewFetcher mv Fetcher.GoSumFile GoSumFile mv GoSumFile.s GoSumFile.f mv GoSumFile Fetcher.GoSumFile mv Fetcher.SetGoSumFile SetGoSumFile mv SetGoSumFile.s SetGoSumFile.f mv SetGoSumFile Fetcher.SetGoSumFile mv Fetcher.AddWorkspaceGoSumFile AddWorkspaceGoSumFile mv AddWorkspaceGoSumFile.s AddWorkspaceGoSumFile.f mv AddWorkspaceGoSumFile Fetcher.AddWorkspaceGoSumFile ' rf ' add NewFetcher:+0 f := new(Fetcher) \ f.lookupCache = new(par.Cache[lookupCacheKey, Repo]) \ f.downloadCache = new(par.ErrCache[module.Version, string]) \ return f ' rf 'rm NewFetcher:+5,8' cd ../modload rf ' mv State.modfetchState State.fetcher ' Change-Id: I7cb6c945ea0f1d2119e1615064f041e88c81c689 Reviewed-on: https://go-review.googlesource.com/c/go/+/721740 Reviewed-by: Michael Matloob LUCI-TryBot-Result: Go LUCI Reviewed-by: Mark Freeman --- src/cmd/go/internal/modfetch/fetch.go | 70 +++++++++++++-------------- src/cmd/go/internal/modfetch/repo.go | 2 +- src/cmd/go/internal/modload/init.go | 18 +++---- 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/cmd/go/internal/modfetch/fetch.go b/src/cmd/go/internal/modfetch/fetch.go index 8a17a374d0f..767bec4cd13 100644 --- a/src/cmd/go/internal/modfetch/fetch.go +++ b/src/cmd/go/internal/modfetch/fetch.go @@ -49,7 +49,7 @@ func Download(ctx context.Context, mod module.Version) (dir string, err error) { } // The par.Cache here avoids duplicate work. - return ModuleFetchState.downloadCache.Do(mod, func() (string, error) { + return Fetcher_.downloadCache.Do(mod, func() (string, error) { dir, err := download(ctx, mod) if err != nil { return "", err @@ -78,7 +78,7 @@ func Unzip(ctx context.Context, mod module.Version, zipfile string) (dir string, base.Fatal(err) } - return ModuleFetchState.downloadCache.Do(mod, func() (string, error) { + return Fetcher_.downloadCache.Do(mod, func() (string, error) { ctx, span := trace.StartSpan(ctx, "modfetch.Unzip "+mod.String()) defer span.Done() @@ -459,8 +459,8 @@ type modSumStatus struct { used, dirty bool } -// State holds a snapshot of the global state of the modfetch package. -type State struct { +// Fetcher holds a snapshot of the global state of the modfetch package. +type Fetcher struct { // path to go.sum; set by package modload goSumFile string // path to module go.sums in workspace; set by package modload @@ -479,38 +479,38 @@ type State struct { sumState sumState } -var ModuleFetchState *State = NewState() +var Fetcher_ *Fetcher = NewFetcher() -func NewState() *State { - s := new(State) - s.lookupCache = new(par.Cache[lookupCacheKey, Repo]) - s.downloadCache = new(par.ErrCache[module.Version, string]) - return s +func NewFetcher() *Fetcher { + f := new(Fetcher) + f.lookupCache = new(par.Cache[lookupCacheKey, Repo]) + f.downloadCache = new(par.ErrCache[module.Version, string]) + return f } -func (s *State) GoSumFile() string { - return s.goSumFile +func (f *Fetcher) GoSumFile() string { + return f.goSumFile } -func (s *State) SetGoSumFile(str string) { - s.goSumFile = str +func (f *Fetcher) SetGoSumFile(str string) { + f.goSumFile = str } -func (s *State) AddWorkspaceGoSumFile(file string) { - s.workspaceGoSumFiles = append(s.workspaceGoSumFiles, file) +func (f *Fetcher) AddWorkspaceGoSumFile(file string) { + f.workspaceGoSumFiles = append(f.workspaceGoSumFiles, file) } // Reset resets globals in the modfetch package, so previous loads don't affect // contents of go.sum files. func Reset() { - SetState(NewState()) + SetState(NewFetcher()) } // SetState sets the global state of the modfetch package to the newState, and returns the previous // global state. newState should have been returned by SetState, or be an empty State. // There should be no concurrent calls to any of the exported functions of this package with // a call to SetState because it will modify the global state in a non-thread-safe way. -func SetState(newState *State) (oldState *State) { +func SetState(newState *Fetcher) (oldState *Fetcher) { if newState.lookupCache == nil { newState.lookupCache = new(par.Cache[lookupCacheKey, Repo]) } @@ -521,21 +521,21 @@ func SetState(newState *State) (oldState *State) { goSum.mu.Lock() defer goSum.mu.Unlock() - oldState = &State{ - goSumFile: ModuleFetchState.goSumFile, - workspaceGoSumFiles: ModuleFetchState.workspaceGoSumFiles, - lookupCache: ModuleFetchState.lookupCache, - downloadCache: ModuleFetchState.downloadCache, + oldState = &Fetcher{ + goSumFile: Fetcher_.goSumFile, + workspaceGoSumFiles: Fetcher_.workspaceGoSumFiles, + lookupCache: Fetcher_.lookupCache, + downloadCache: Fetcher_.downloadCache, sumState: goSum.sumState, } - ModuleFetchState.SetGoSumFile(newState.goSumFile) - ModuleFetchState.workspaceGoSumFiles = newState.workspaceGoSumFiles + Fetcher_.SetGoSumFile(newState.goSumFile) + Fetcher_.workspaceGoSumFiles = newState.workspaceGoSumFiles // Uses of lookupCache and downloadCache both can call checkModSum, // which in turn sets the used bit on goSum.status for modules. // Set (or reset) them so used can be computed properly. - ModuleFetchState.lookupCache = newState.lookupCache - ModuleFetchState.downloadCache = newState.downloadCache + Fetcher_.lookupCache = newState.lookupCache + Fetcher_.downloadCache = newState.downloadCache // Set, or reset all fields on goSum. If being reset to empty, it will be initialized later. goSum.sumState = newState.sumState @@ -547,7 +547,7 @@ func SetState(newState *State) (oldState *State) { // use of go.sum is now enabled. // The goSum lock must be held. func initGoSum() (bool, error) { - if ModuleFetchState.goSumFile == "" { + if Fetcher_.goSumFile == "" { return false, nil } if goSum.m != nil { @@ -558,7 +558,7 @@ func initGoSum() (bool, error) { goSum.status = make(map[modSum]modSumStatus) goSum.w = make(map[string]map[module.Version][]string) - for _, f := range ModuleFetchState.workspaceGoSumFiles { + for _, f := range Fetcher_.workspaceGoSumFiles { goSum.w[f] = make(map[module.Version][]string) _, err := readGoSumFile(goSum.w[f], f) if err != nil { @@ -566,7 +566,7 @@ func initGoSum() (bool, error) { } } - enabled, err := readGoSumFile(goSum.m, ModuleFetchState.goSumFile) + enabled, err := readGoSumFile(goSum.m, Fetcher_.goSumFile) goSum.enabled = enabled return enabled, err } @@ -812,7 +812,7 @@ func checkModSum(mod module.Version, h string) error { // goSum.mu must be locked. func haveModSumLocked(mod module.Version, h string) bool { sumFileName := "go.sum" - if strings.HasSuffix(ModuleFetchState.goSumFile, "go.work.sum") { + if strings.HasSuffix(Fetcher_.goSumFile, "go.work.sum") { sumFileName = "go.work.sum" } for _, vh := range goSum.m[mod] { @@ -956,7 +956,7 @@ Outer: if readonly { return ErrGoSumDirty } - if fsys.Replaced(ModuleFetchState.goSumFile) { + if fsys.Replaced(Fetcher_.goSumFile) { base.Fatalf("go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay") } @@ -966,7 +966,7 @@ Outer: defer unlock() } - err := lockedfile.Transform(ModuleFetchState.goSumFile, func(data []byte) ([]byte, error) { + err := lockedfile.Transform(Fetcher_.goSumFile, func(data []byte) ([]byte, error) { tidyGoSum := tidyGoSum(data, keep) return tidyGoSum, nil }) @@ -985,7 +985,7 @@ Outer: func TidyGoSum(keep map[module.Version]bool) (before, after []byte) { goSum.mu.Lock() defer goSum.mu.Unlock() - before, err := lockedfile.Read(ModuleFetchState.goSumFile) + before, err := lockedfile.Read(Fetcher_.goSumFile) if err != nil && !errors.Is(err, fs.ErrNotExist) { base.Fatalf("reading go.sum: %v", err) } @@ -1002,7 +1002,7 @@ func tidyGoSum(data []byte, keep map[module.Version]bool) []byte { // truncated the file to remove erroneous hashes, and we shouldn't restore // them without good reason. goSum.m = make(map[module.Version][]string, len(goSum.m)) - readGoSum(goSum.m, ModuleFetchState.goSumFile, data) + readGoSum(goSum.m, Fetcher_.goSumFile, data) for ms, st := range goSum.status { if st.used && !sumInWorkspaceModulesLocked(ms.mod) { addModSumLocked(ms.mod, ms.sum) diff --git a/src/cmd/go/internal/modfetch/repo.go b/src/cmd/go/internal/modfetch/repo.go index bb5dfc4655d..23ddfdc8842 100644 --- a/src/cmd/go/internal/modfetch/repo.go +++ b/src/cmd/go/internal/modfetch/repo.go @@ -205,7 +205,7 @@ func Lookup(ctx context.Context, proxy, path string) Repo { defer logCall("Lookup(%q, %q)", proxy, path)() } - return ModuleFetchState.lookupCache.Do(lookupCacheKey{proxy, path}, func() Repo { + return Fetcher_.lookupCache.Do(lookupCacheKey{proxy, path}, func() Repo { return newCachingRepo(ctx, path, func(ctx context.Context) (Repo, error) { r, err := lookup(ctx, proxy, path) if err == nil && traceRepo { diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go index 4680ea427e9..54d8009d326 100644 --- a/src/cmd/go/internal/modload/init.go +++ b/src/cmd/go/internal/modload/init.go @@ -398,7 +398,7 @@ func (s *State) setState(new *State) (old *State) { MainModules: s.MainModules, requirements: s.requirements, workFilePath: s.workFilePath, - modfetchState: s.modfetchState, + fetcher: s.fetcher, } s.initialized = new.initialized s.ForceUseModules = new.ForceUseModules @@ -411,8 +411,8 @@ func (s *State) setState(new *State) (old *State) { // The modfetch package's global state is used to compute // the go.sum file, so save and restore it along with the // modload state. - s.modfetchState = new.modfetchState - old.modfetchState = modfetch.SetState(s.modfetchState) // TODO(jitsu): remove after completing global state elimination + s.fetcher = new.fetcher + old.fetcher = modfetch.SetState(s.fetcher) // TODO(jitsu): remove after completing global state elimination return old } @@ -451,13 +451,13 @@ type State struct { // Set to the path to the go.work file, or "" if workspace mode is // disabled - workFilePath string - modfetchState *modfetch.State + workFilePath string + fetcher *modfetch.Fetcher } func NewState() *State { s := new(State) - s.modfetchState = modfetch.NewState() + s.fetcher = modfetch.NewFetcher() return s } @@ -937,9 +937,9 @@ func loadModFile(loaderstate *State, ctx context.Context, opts *PackageOpts) (*R } for _, modRoot := range loaderstate.modRoots { sumFile := strings.TrimSuffix(modFilePath(modRoot), ".mod") + ".sum" - modfetch.ModuleFetchState.AddWorkspaceGoSumFile(sumFile) + modfetch.Fetcher_.AddWorkspaceGoSumFile(sumFile) } - modfetch.ModuleFetchState.SetGoSumFile(loaderstate.workFilePath + ".sum") + modfetch.Fetcher_.SetGoSumFile(loaderstate.workFilePath + ".sum") } else if len(loaderstate.modRoots) == 0 { // We're in module mode, but not inside a module. // @@ -959,7 +959,7 @@ func loadModFile(loaderstate *State, ctx context.Context, opts *PackageOpts) (*R // // See golang.org/issue/32027. } else { - modfetch.ModuleFetchState.SetGoSumFile(strings.TrimSuffix(modFilePath(loaderstate.modRoots[0]), ".mod") + ".sum") + modfetch.Fetcher_.SetGoSumFile(strings.TrimSuffix(modFilePath(loaderstate.modRoots[0]), ".mod") + ".sum") } if len(loaderstate.modRoots) == 0 { // TODO(#49228): Instead of creating a fake module with an empty modroot, From aa093eed830796b3ba498b04077d8ee2d6d428bf Mon Sep 17 00:00:00 2001 From: Sean Liao Date: Fri, 21 Nov 2025 22:27:36 +0000 Subject: [PATCH 014/140] crypto/fips140: add Version Fixes #75301 Change-Id: If953b4382499570d5437491036f91cbe4fec7c01 Reviewed-on: https://go-review.googlesource.com/c/go/+/723101 Reviewed-by: Roland Shoemaker Reviewed-by: Filippo Valsorda LUCI-TryBot-Result: Go LUCI Reviewed-by: Dmitri Shuralyov --- api/next/75301.txt | 1 + .../6-stdlib/99-minor/crypto/fips140/75301.md | 1 + src/crypto/fips140/fips140.go | 15 +++++++++++++++ 3 files changed, 17 insertions(+) create mode 100644 api/next/75301.txt create mode 100644 doc/next/6-stdlib/99-minor/crypto/fips140/75301.md diff --git a/api/next/75301.txt b/api/next/75301.txt new file mode 100644 index 00000000000..8d16837091f --- /dev/null +++ b/api/next/75301.txt @@ -0,0 +1 @@ +pkg crypto/fips140, func Version() string #75301 diff --git a/doc/next/6-stdlib/99-minor/crypto/fips140/75301.md b/doc/next/6-stdlib/99-minor/crypto/fips140/75301.md new file mode 100644 index 00000000000..2dd77f61ef4 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/fips140/75301.md @@ -0,0 +1 @@ +[Version] returns the resolved FIPS 140-3 Go Cryptographic Module version when building against a frozen module with GOFIPS140. diff --git a/src/crypto/fips140/fips140.go b/src/crypto/fips140/fips140.go index 830b6f80af5..f44f3b399b2 100644 --- a/src/crypto/fips140/fips140.go +++ b/src/crypto/fips140/fips140.go @@ -23,3 +23,18 @@ func Enabled() bool { } return fips140.Enabled } + +// Version returns the FIPS 140-3 Go Cryptographic Module version (such as +// "v1.0.0"), as referenced in the Security Policy for the module, if building +// against a frozen module with GOFIPS140. Otherwise, it returns "latest". If an +// alias is in use (such as "inprogress") the actual resolved version is +// returned. +// +// The returned version may not uniquely identify the frozen module which was +// used to build the program, if there are multiple copies of the frozen module +// at the same version. The uniquely identifying version suffix can be found by +// checking the value of the GOFIPS140 setting in +// runtime/debug.BuildInfo.Settings. +func Version() string { + return fips140.Version() +} From 31d373534e6b2582817585851f45b8af6386d023 Mon Sep 17 00:00:00 2001 From: Sean Liao Date: Fri, 21 Nov 2025 22:11:41 +0000 Subject: [PATCH 015/140] doc: pre-announce removal of 1.23 and earlier crypto GODEBUGs For #75316 Change-Id: Ife391b8c3e7fd2fec0e53b296d47b4756a787001 Reviewed-on: https://go-review.googlesource.com/c/go/+/723100 Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: Go LUCI Reviewed-by: Filippo Valsorda Reviewed-by: Cherry Mui --- doc/godebug.md | 5 +++++ doc/next/6-stdlib/99-minor/crypto/tls/75836.md | 9 +++++++++ 2 files changed, 14 insertions(+) create mode 100644 doc/next/6-stdlib/99-minor/crypto/tls/75836.md diff --git a/doc/godebug.md b/doc/godebug.md index 0d1cd6b6627..6163d134ce0 100644 --- a/doc/godebug.md +++ b/doc/godebug.md @@ -334,6 +334,7 @@ any effect. Go 1.23 changed the default TLS cipher suites used by clients and servers when not explicitly configured, removing 3DES cipher suites. The default can be reverted using the [`tls3des` setting](/pkg/crypto/tls/#Config.CipherSuites). +This setting will be removed in Go 1.27. Go 1.23 changed the behavior of [`tls.X509KeyPair`](/pkg/crypto/tls#X509KeyPair) and [`tls.LoadX509KeyPair`](/pkg/crypto/tls#LoadX509KeyPair) to populate the @@ -341,6 +342,7 @@ Leaf field of the returned [`tls.Certificate`](/pkg/crypto/tls#Certificate). This behavior is controlled by the `x509keypairleaf` setting. For Go 1.23, it defaults to `x509keypairleaf=1`. Previous versions default to `x509keypairleaf=0`. +This setting will be removed in Go 1.27. Go 1.23 changed [`net/http.ServeContent`](/pkg/net/http#ServeContent), @@ -379,16 +381,19 @@ This setting will be removed in a future release, Go 1.27 at the earliest. Go 1.22 changed the default minimum TLS version supported by both servers and clients to TLS 1.2. The default can be reverted to TLS 1.0 using the [`tls10server` setting](/pkg/crypto/tls/#Config). +This setting will be removed in Go 1.27. Go 1.22 changed the default TLS cipher suites used by clients and servers when not explicitly configured, removing the cipher suites which used RSA based key exchange. The default can be reverted using the [`tlsrsakex` setting](/pkg/crypto/tls/#Config). +This setting will be removed in Go 1.27. Go 1.22 disabled [`ConnectionState.ExportKeyingMaterial`](/pkg/crypto/tls/#ConnectionState.ExportKeyingMaterial) when the connection supports neither TLS 1.3 nor Extended Master Secret (implemented in Go 1.21). It can be reenabled with the [`tlsunsafeekm` setting](/pkg/crypto/tls/#ConnectionState.ExportKeyingMaterial). +This setting will be removed in Go 1.27. Go 1.22 changed how the runtime interacts with transparent huge pages on Linux. In particular, a common default Linux kernel configuration can result in diff --git a/doc/next/6-stdlib/99-minor/crypto/tls/75836.md b/doc/next/6-stdlib/99-minor/crypto/tls/75836.md new file mode 100644 index 00000000000..33732800eff --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/tls/75836.md @@ -0,0 +1,9 @@ +The following GODEBUG settings introduced in [Go 1.22](/doc/godebug#go-122) +and [Go 1.23](/doc/godebug#go-123) will be removed in the next major Go release. +Starting in Go 1.27, the new behavior will apply regardless of GODEBUG setting or go.mod language version. + +- `tlsunsafeekm`: [ConnectionState.ExportKeyingMaterial] will require TLS 1.3 or Extended Master Secret. +- `tlsrsakex`: legacy RSA-only key exchanges without ECDH won't be enabled by default. +- `tls10server`: the default minimum TLS version for both clients and servers will be TLS 1.2. +- `tls3des`: the default cipher suites will not include 3DES. +- `x509keypairleaf`: [X509KeyPair] and [LoadX509KeyPair] will always populate the [Certificate.Leaf] field. From 4fb7e083a868946db08db9ef3bc807e21c8fc961 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Mon, 3 Nov 2025 14:47:42 -0500 Subject: [PATCH 016/140] crypto/tls: expose HelloRetryRequest state This commit adds fields to the ClientHelloInfo and ConnectionState structures to represent hello retry request state information. ClientHelloInfo gains a new HelloRetryRequest bool field that indicates if the client hello was sent in response to a TLS 1.3 hello retry request message previously emitted by the server. ConnectionState gains a new HelloRetryRequest bool field that indicates (depending on the connection role) whether the client received a TLS 1.3 hello retry request message from the server, or whether the server sent such a message to a client. Fixes #74425 Change-Id: Ic1a5290b8a4ba1568da1d2c2cf9f148150955fa5 Reviewed-on: https://go-review.googlesource.com/c/go/+/717440 Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI Reviewed-by: Filippo Valsorda Reviewed-by: Cherry Mui Auto-Submit: Daniel McCarney --- api/next/74425.txt | 2 + .../6-stdlib/99-minor/crypto/tls/74425.md | 5 +++ src/crypto/tls/bogo_shim_test.go | 4 +- src/crypto/tls/common.go | 11 +++-- src/crypto/tls/conn.go | 2 +- src/crypto/tls/handshake_client_test.go | 2 +- src/crypto/tls/handshake_server.go | 1 + src/crypto/tls/handshake_server_test.go | 41 +++++++++++++++++-- src/crypto/tls/tls_test.go | 8 ++-- 9 files changed, 62 insertions(+), 14 deletions(-) create mode 100644 api/next/74425.txt create mode 100644 doc/next/6-stdlib/99-minor/crypto/tls/74425.md diff --git a/api/next/74425.txt b/api/next/74425.txt new file mode 100644 index 00000000000..f204476d650 --- /dev/null +++ b/api/next/74425.txt @@ -0,0 +1,2 @@ +pkg crypto/tls, type ConnectionState struct, HelloRetryRequest bool #74425 +pkg crypto/tls, type ClientHelloInfo struct, HelloRetryRequest bool #74425 diff --git a/doc/next/6-stdlib/99-minor/crypto/tls/74425.md b/doc/next/6-stdlib/99-minor/crypto/tls/74425.md new file mode 100644 index 00000000000..8280f24c2c9 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/tls/74425.md @@ -0,0 +1,5 @@ +The new [ClientHelloInfo.HelloRetryRequest] field indicates if the ClientHello +was sent in response to a HelloRetryRequest message. The new +[ConnectionState.HelloRetryRequest] field indicates if the server +sent a HelloRetryRequest, or if the client received a HelloRetryRequest, +depending on connection role. diff --git a/src/crypto/tls/bogo_shim_test.go b/src/crypto/tls/bogo_shim_test.go index 02a943c13cf..a14386a61c2 100644 --- a/src/crypto/tls/bogo_shim_test.go +++ b/src/crypto/tls/bogo_shim_test.go @@ -476,11 +476,11 @@ func bogoShim() { log.Fatal("did not expect ECH, but it was accepted") } - if *expectHRR && !cs.testingOnlyDidHRR { + if *expectHRR && !cs.HelloRetryRequest { log.Fatal("expected HRR but did not do it") } - if *expectNoHRR && cs.testingOnlyDidHRR { + if *expectNoHRR && cs.HelloRetryRequest { log.Fatal("expected no HRR but did do it") } diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index c8e65e4d3c0..d809624b880 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -304,12 +304,13 @@ type ConnectionState struct { // client side. ECHAccepted bool + // HelloRetryRequest indicates whether we sent a HelloRetryRequest if we + // are a server, or if we received a HelloRetryRequest if we are a client. + HelloRetryRequest bool + // ekm is a closure exposed via ExportKeyingMaterial. ekm func(label string, context []byte, length int) ([]byte, error) - // testingOnlyDidHRR is true if a HelloRetryRequest was sent/received. - testingOnlyDidHRR bool - // testingOnlyPeerSignatureAlgorithm is the signature algorithm used by the // peer to sign the handshake. It is not set for resumed connections. testingOnlyPeerSignatureAlgorithm SignatureScheme @@ -469,6 +470,10 @@ type ClientHelloInfo struct { // connection to fail. Conn net.Conn + // HelloRetryRequest indicates whether the ClientHello was sent in response + // to a HelloRetryRequest message. + HelloRetryRequest bool + // config is embedded by the GetCertificate or GetConfigForClient caller, // for use with SupportsCertificate. config *Config diff --git a/src/crypto/tls/conn.go b/src/crypto/tls/conn.go index 2de120a1329..c04c7a506e0 100644 --- a/src/crypto/tls/conn.go +++ b/src/crypto/tls/conn.go @@ -1612,7 +1612,7 @@ func (c *Conn) connectionStateLocked() ConnectionState { state.Version = c.vers state.NegotiatedProtocol = c.clientProtocol state.DidResume = c.didResume - state.testingOnlyDidHRR = c.didHRR + state.HelloRetryRequest = c.didHRR state.testingOnlyPeerSignatureAlgorithm = c.peerSigAlg state.CurveID = c.curveID state.NegotiatedProtocolIsMutual = true diff --git a/src/crypto/tls/handshake_client_test.go b/src/crypto/tls/handshake_client_test.go index 6020c0f055c..e7de0b59119 100644 --- a/src/crypto/tls/handshake_client_test.go +++ b/src/crypto/tls/handshake_client_test.go @@ -626,7 +626,7 @@ func TestHandshakeClientHelloRetryRequest(t *testing.T) { args: []string{"-cipher", "ECDHE-RSA-AES128-GCM-SHA256", "-curves", "P-256"}, config: config, validate: func(cs ConnectionState) error { - if !cs.testingOnlyDidHRR { + if !cs.HelloRetryRequest { return errors.New("expected HelloRetryRequest") } return nil diff --git a/src/crypto/tls/handshake_server.go b/src/crypto/tls/handshake_server.go index a2cf176a86c..2d3efffcf05 100644 --- a/src/crypto/tls/handshake_server.go +++ b/src/crypto/tls/handshake_server.go @@ -1021,6 +1021,7 @@ func clientHelloInfo(ctx context.Context, c *Conn, clientHello *clientHelloMsg) SupportedVersions: supportedVersions, Extensions: clientHello.extensions, Conn: c.conn, + HelloRetryRequest: c.didHRR, config: c.config, ctx: ctx, } diff --git a/src/crypto/tls/handshake_server_test.go b/src/crypto/tls/handshake_server_test.go index 43183db2a19..7e35c252593 100644 --- a/src/crypto/tls/handshake_server_test.go +++ b/src/crypto/tls/handshake_server_test.go @@ -905,14 +905,30 @@ func TestHandshakeServerHelloRetryRequest(t *testing.T) { config := testConfig.Clone() config.CurvePreferences = []CurveID{CurveP256} + var clientHelloInfoHRR bool + var getCertificateCalled bool + eeCert := config.Certificates[0] + config.Certificates = nil + config.GetCertificate = func(clientHello *ClientHelloInfo) (*Certificate, error) { + getCertificateCalled = true + clientHelloInfoHRR = clientHello.HelloRetryRequest + return &eeCert, nil + } + test := &serverTest{ name: "HelloRetryRequest", command: []string{"openssl", "s_client", "-no_ticket", "-ciphersuites", "TLS_CHACHA20_POLY1305_SHA256", "-curves", "X25519:P-256"}, config: config, validate: func(cs ConnectionState) error { - if !cs.testingOnlyDidHRR { + if !cs.HelloRetryRequest { return errors.New("expected HelloRetryRequest") } + if !getCertificateCalled { + return errors.New("expected GetCertificate to be called") + } + if !clientHelloInfoHRR { + return errors.New("expected ClientHelloInfo.HelloRetryRequest to be true") + } return nil }, } @@ -920,19 +936,38 @@ func TestHandshakeServerHelloRetryRequest(t *testing.T) { } // TestHandshakeServerKeySharePreference checks that we prefer a key share even -// if it's later in the CurvePreferences order. +// if it's later in the CurvePreferences order, and that the client hello HRR +// field is correctly represented. func TestHandshakeServerKeySharePreference(t *testing.T) { config := testConfig.Clone() config.CurvePreferences = []CurveID{X25519, CurveP256} + // We also use this test as a convenient place to assert the ClientHelloInfo + // HelloRetryRequest field is _not_ set for a non-HRR hello. + var clientHelloInfoHRR bool + var getCertificateCalled bool + eeCert := config.Certificates[0] + config.Certificates = nil + config.GetCertificate = func(clientHello *ClientHelloInfo) (*Certificate, error) { + getCertificateCalled = true + clientHelloInfoHRR = clientHello.HelloRetryRequest + return &eeCert, nil + } + test := &serverTest{ name: "KeySharePreference", command: []string{"openssl", "s_client", "-no_ticket", "-ciphersuites", "TLS_CHACHA20_POLY1305_SHA256", "-curves", "P-256:X25519"}, config: config, validate: func(cs ConnectionState) error { - if cs.testingOnlyDidHRR { + if cs.HelloRetryRequest { return errors.New("unexpected HelloRetryRequest") } + if !getCertificateCalled { + return errors.New("expected GetCertificate to be called") + } + if clientHelloInfoHRR { + return errors.New("expected ClientHelloInfo.HelloRetryRequest to be false") + } return nil }, } diff --git a/src/crypto/tls/tls_test.go b/src/crypto/tls/tls_test.go index 93ec8643f67..6905f539499 100644 --- a/src/crypto/tls/tls_test.go +++ b/src/crypto/tls/tls_test.go @@ -2090,17 +2090,17 @@ func TestHandshakeMLKEM(t *testing.T) { } } if test.expectHRR { - if !ss.testingOnlyDidHRR { + if !ss.HelloRetryRequest { t.Error("server did not use HRR") } - if !cs.testingOnlyDidHRR { + if !cs.HelloRetryRequest { t.Error("client did not use HRR") } } else { - if ss.testingOnlyDidHRR { + if ss.HelloRetryRequest { t.Error("server used HRR") } - if cs.testingOnlyDidHRR { + if cs.HelloRetryRequest { t.Error("client used HRR") } } From 09e377b599cd723286af65ee2f741fd99b5d7676 Mon Sep 17 00:00:00 2001 From: guoguangwu Date: Mon, 18 Mar 2024 07:34:04 +0000 Subject: [PATCH 017/140] internal/poll: replace t.Sub(time.Now()) with time.Until in test Change-Id: Ia383d4d322008901cd1e57b05fb522db44076fa2 GitHub-Last-Rev: 5c7cbeb7371c6afa47c280e164087e45ca89f3d2 GitHub-Pull-Request: golang/go#66375 Reviewed-on: https://go-review.googlesource.com/c/go/+/572178 LUCI-TryBot-Result: Go LUCI Reviewed-by: Ian Lance Taylor Reviewed-by: Cherry Mui Auto-Submit: Sean Liao Reviewed-by: Michael Pratt Reviewed-by: Emmanuel Odeke Reviewed-by: Sean Liao --- src/internal/poll/splice_linux_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internal/poll/splice_linux_test.go b/src/internal/poll/splice_linux_test.go index e4a7eb2b435..9f2b8f94612 100644 --- a/src/internal/poll/splice_linux_test.go +++ b/src/internal/poll/splice_linux_test.go @@ -59,7 +59,7 @@ func TestSplicePipePool(t *testing.T) { // Exploit the timeout of "go test" as a timer for the subsequent verification. timeout := 5 * time.Minute if deadline, ok := t.Deadline(); ok { - timeout = deadline.Sub(time.Now()) + timeout = time.Until(deadline) timeout -= timeout / 10 // Leave 10% headroom for cleanup. } expiredTime := time.NewTimer(timeout) From 0d2baa808c837e2c5db1c0e51e53ae8fa6ce9b86 Mon Sep 17 00:00:00 2001 From: Andrey Pshenkin Date: Fri, 12 Sep 2025 18:43:13 +0100 Subject: [PATCH 018/140] crypto/rsa: add EncryptOAEPWithOptions Co-authored-by: Filippo Valsorda Change-Id: I78968794d609a7b343e5affc141d8ba96a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/722260 Reviewed-by: Roland Shoemaker TryBot-Bypass: Filippo Valsorda Reviewed-by: Cherry Mui Auto-Submit: Filippo Valsorda Reviewed-by: Daniel McCarney --- api/next/65716.txt | 1 + .../6-stdlib/99-minor/crypto/rsa/65716.md | 2 ++ src/crypto/rsa/fips.go | 24 +++++++++++++-- src/crypto/rsa/rsa.go | 4 +-- src/crypto/rsa/rsa_test.go | 30 +++++++++++++++++++ 5 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 api/next/65716.txt create mode 100644 doc/next/6-stdlib/99-minor/crypto/rsa/65716.md diff --git a/api/next/65716.txt b/api/next/65716.txt new file mode 100644 index 00000000000..aad479698b8 --- /dev/null +++ b/api/next/65716.txt @@ -0,0 +1 @@ +pkg crypto/rsa, func EncryptOAEPWithOptions(io.Reader, *PublicKey, []uint8, *OAEPOptions) ([]uint8, error) #65716 diff --git a/doc/next/6-stdlib/99-minor/crypto/rsa/65716.md b/doc/next/6-stdlib/99-minor/crypto/rsa/65716.md new file mode 100644 index 00000000000..e45376caa71 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/rsa/65716.md @@ -0,0 +1,2 @@ +The new [EncryptOAEPWithOptions] function allows specifying different hash +functions for OAEP padding and MGF1 mask generation. diff --git a/src/crypto/rsa/fips.go b/src/crypto/rsa/fips.go index 8373c125ae3..ba92659193f 100644 --- a/src/crypto/rsa/fips.go +++ b/src/crypto/rsa/fips.go @@ -191,14 +191,32 @@ func VerifyPSS(pub *PublicKey, hash crypto.Hash, digest []byte, sig []byte, opts // The message must be no longer than the length of the public modulus minus // twice the hash length, minus a further 2. func EncryptOAEP(hash hash.Hash, random io.Reader, pub *PublicKey, msg []byte, label []byte) ([]byte, error) { + return encryptOAEP(hash, hash, random, pub, msg, label) +} + +// EncryptOAEPWithOptions encrypts the given message with RSA-OAEP using the +// provided options. +// +// This function should only be used over [EncryptOAEP] when there is a need to +// specify the OAEP and MGF1 hashes separately. +// +// See [EncryptOAEP] for additional details. +func EncryptOAEPWithOptions(random io.Reader, pub *PublicKey, msg []byte, opts *OAEPOptions) ([]byte, error) { + if opts.MGFHash == 0 { + return encryptOAEP(opts.Hash.New(), opts.Hash.New(), random, pub, msg, opts.Label) + } + return encryptOAEP(opts.Hash.New(), opts.MGFHash.New(), random, pub, msg, opts.Label) +} + +func encryptOAEP(hash hash.Hash, mgfHash hash.Hash, random io.Reader, pub *PublicKey, msg []byte, label []byte) ([]byte, error) { if err := checkPublicKeySize(pub); err != nil { return nil, err } defer hash.Reset() + defer mgfHash.Reset() if boring.Enabled && random == boring.RandReader { - hash.Reset() k := pub.Size() if len(msg) > k-2*hash.Size()-2 { return nil, ErrMessageTooLong @@ -207,7 +225,7 @@ func EncryptOAEP(hash hash.Hash, random io.Reader, pub *PublicKey, msg []byte, l if err != nil { return nil, err } - return boring.EncryptRSAOAEP(hash, hash, bkey, msg, label) + return boring.EncryptRSAOAEP(hash, mgfHash, bkey, msg, label) } boring.UnreachableExceptTests() @@ -227,7 +245,7 @@ func EncryptOAEP(hash hash.Hash, random io.Reader, pub *PublicKey, msg []byte, l if err != nil { return nil, err } - return fipsError2(rsa.EncryptOAEP(hash, hash, random, k, msg, label)) + return fipsError2(rsa.EncryptOAEP(hash, mgfHash, random, k, msg, label)) } // DecryptOAEP decrypts ciphertext using RSA-OAEP. diff --git a/src/crypto/rsa/rsa.go b/src/crypto/rsa/rsa.go index b6b94a79bbc..7e1bf3d7a56 100644 --- a/src/crypto/rsa/rsa.go +++ b/src/crypto/rsa/rsa.go @@ -88,8 +88,8 @@ func (pub *PublicKey) Equal(x crypto.PublicKey) bool { return bigIntEqual(pub.N, xx.N) && pub.E == xx.E } -// OAEPOptions is an interface for passing options to OAEP decryption using the -// crypto.Decrypter interface. +// OAEPOptions allows passing options to OAEP encryption and decryption +// through the [PrivateKey.Decrypt] and [EncryptOAEPWithOptions] functions. type OAEPOptions struct { // Hash is the hash function that will be used when generating the mask. Hash crypto.Hash diff --git a/src/crypto/rsa/rsa_test.go b/src/crypto/rsa/rsa_test.go index b9e85bd8ff8..5ae4c1dd203 100644 --- a/src/crypto/rsa/rsa_test.go +++ b/src/crypto/rsa/rsa_test.go @@ -989,6 +989,36 @@ func TestEncryptDecryptOAEP(t *testing.T) { if !bytes.Equal(dec, message.in) { t.Errorf("#%d,%d: round trip %q -> %q", i, j, message.in, dec) } + + // Using different hash for MGF. + enc, err = EncryptOAEPWithOptions(rand.Reader, &priv.PublicKey, message.in, &OAEPOptions{Hash: crypto.SHA256, MGFHash: crypto.SHA1, Label: label}) + if err != nil { + t.Errorf("#%d,%d: EncryptOAEP with different MGFHash: %v", i, j, err) + continue + } + dec, err = priv.Decrypt(rand.Reader, enc, &OAEPOptions{Hash: crypto.SHA256, MGFHash: crypto.SHA1, Label: label}) + if err != nil { + t.Errorf("#%d,%d: DecryptOAEP with different MGFHash: %v", i, j, err) + continue + } + if !bytes.Equal(dec, message.in) { + t.Errorf("#%d,%d: round trip with different MGFHash %q -> %q", i, j, message.in, dec) + } + + // Using a zero MGFHash. + enc, err = EncryptOAEPWithOptions(rand.Reader, &priv.PublicKey, message.in, &OAEPOptions{Hash: crypto.SHA256, Label: label}) + if err != nil { + t.Errorf("#%d,%d: EncryptOAEP with zero MGFHash: %v", i, j, err) + continue + } + dec, err = DecryptOAEP(sha256, rand.Reader, priv, enc, label) + if err != nil { + t.Errorf("#%d,%d: DecryptOAEP with zero MGFHash: %v", i, j, err) + continue + } + if !bytes.Equal(dec, message.in) { + t.Errorf("#%d,%d: round trip with zero MGFHash %q -> %q", i, j, message.in, dec) + } } } } From ed4deb157eb044bab58e23928830a26faf438958 Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Thu, 20 Nov 2025 13:32:48 -0800 Subject: [PATCH 019/140] crypto/x509: cleanup name constraint tests Make TestConstraintCases a bit clearer by adding actual subtest names, mostly taken from the old comments. Also add a handful of extra test cases. Change-Id: Ie759d1ea85a353aeacab267bb6e175a90f20702c Reviewed-on: https://go-review.googlesource.com/c/go/+/722481 Reviewed-by: Daniel McCarney Auto-Submit: Roland Shoemaker Reviewed-by: Nicholas Husin LUCI-TryBot-Result: Go LUCI Reviewed-by: Nicholas Husin --- src/crypto/x509/name_constraints_test.go | 417 +++++++++++------------ 1 file changed, 202 insertions(+), 215 deletions(-) diff --git a/src/crypto/x509/name_constraints_test.go b/src/crypto/x509/name_constraints_test.go index a5851845164..32b6823c4ce 100644 --- a/src/crypto/x509/name_constraints_test.go +++ b/src/crypto/x509/name_constraints_test.go @@ -39,6 +39,7 @@ const ( ) type nameConstraintsTest struct { + name string roots []constraintsSpec intermediates [][]constraintsSpec leaf leafSpec @@ -61,17 +62,15 @@ type leafSpec struct { } var nameConstraintsTests = []nameConstraintsTest{ - // #0: dummy test for the certificate generation process itself. { + name: "certificate generation process", roots: make([]constraintsSpec, 1), leaf: leafSpec{ sans: []string{"dns:example.com"}, }, }, - - // #1: dummy test for the certificate generation process itself: single - // level of intermediate. { + name: "single level of intermediate", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -82,10 +81,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:example.com"}, }, }, - - // #2: dummy test for the certificate generation process itself: two - // levels of intermediates. { + name: "two levels of intermediates", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -99,9 +96,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:example.com"}, }, }, - - // #3: matching DNS constraint in root { + name: "matching DNS constraint in root", roots: []constraintsSpec{ { ok: []string{"dns:example.com"}, @@ -116,9 +112,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:example.com"}, }, }, - - // #4: matching DNS constraint in intermediate. { + name: "matching DNS constraint in intermediate", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -131,9 +126,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:example.com"}, }, }, - - // #5: .example.com only matches subdomains. { + name: "leading period only matches subdomains", roots: []constraintsSpec{ { ok: []string{"dns:.example.com"}, @@ -149,9 +143,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"example.com\" is not permitted", }, - - // #6: .example.com matches subdomains. { + name: "leading period matches subdomains", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -164,9 +157,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:foo.example.com"}, }, }, - - // #7: .example.com matches multiple levels of subdomains { + name: "leading period matches multiple levels of subdomains", roots: []constraintsSpec{ { ok: []string{"dns:.example.com"}, @@ -181,10 +173,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:foo.bar.example.com"}, }, }, - - // #8: specifying a permitted list of names does not exclude other name - // types { + name: "specifying a permitted list of names does not exclude other name types", roots: []constraintsSpec{ { ok: []string{"dns:.example.com"}, @@ -199,10 +189,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"ip:10.1.1.1"}, }, }, - - // #9: specifying a permitted list of names does not exclude other name - // types { + name: "specifying a permitted list of names does not exclude other name types", roots: []constraintsSpec{ { ok: []string{"ip:10.0.0.0/8"}, @@ -217,11 +205,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:example.com"}, }, }, - - // #10: intermediates can try to permit other names, which isn't - // forbidden if the leaf doesn't mention them. I.e. name constraints - // apply to names, not constraints themselves. { + name: "intermediates can try to permit other names, which isn't forbidden if the leaf doesn't mention them", roots: []constraintsSpec{ { ok: []string{"dns:example.com"}, @@ -238,10 +223,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:example.com"}, }, }, - - // #11: intermediates cannot add permitted names that the root doesn't - // grant them. { + name: "intermediates cannot add permitted names that the root doesn't grant them", roots: []constraintsSpec{ { ok: []string{"dns:example.com"}, @@ -250,7 +233,7 @@ var nameConstraintsTests = []nameConstraintsTest{ intermediates: [][]constraintsSpec{ { { - ok: []string{"dns:example.com", "dns:foo.com"}, + ok: []string{"dns:foo.example.com", "dns:foo.com"}, }, }, }, @@ -259,9 +242,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"foo.com\" is not permitted", }, - - // #12: intermediates can further limit their scope if they wish. { + name: "intermediates can further limit their scope if they wish", roots: []constraintsSpec{ { ok: []string{"dns:.example.com"}, @@ -278,10 +260,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:foo.bar.example.com"}, }, }, - - // #13: intermediates can further limit their scope and that limitation - // is effective { + name: "intermediates can further limit their scope and that limitation is effective", roots: []constraintsSpec{ { ok: []string{"dns:.example.com"}, @@ -299,9 +279,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"foo.notbar.example.com\" is not permitted", }, - - // #14: roots can exclude subtrees and that doesn't affect other names. { + name: "roots can exclude subtrees and that doesn't affect other names", roots: []constraintsSpec{ { bad: []string{"dns:.example.com"}, @@ -316,9 +295,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:foo.com"}, }, }, - - // #15: roots exclusions are effective. { + name: "roots exclusions are effective", roots: []constraintsSpec{ { bad: []string{"dns:.example.com"}, @@ -334,10 +312,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"foo.example.com\" is excluded", }, - - // #16: intermediates can also exclude names and that doesn't affect - // other names. { + name: "intermediates can also exclude names and that doesn't affect other names", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -350,9 +326,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:foo.com"}, }, }, - - // #17: intermediate exclusions are effective. { + name: "intermediate exclusions are effective", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -366,9 +341,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"foo.example.com\" is excluded", }, - - // #18: having an exclusion doesn't prohibit other types of names. { + name: "having an exclusion doesn't prohibit other types of names", roots: []constraintsSpec{ { bad: []string{"dns:.example.com"}, @@ -383,10 +357,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:foo.com", "ip:10.1.1.1"}, }, }, - - // #19: IP-based exclusions are permitted and don't affect unrelated IP - // addresses. { + name: "IP-based exclusions are permitted and don't affect unrelated IP addresses", roots: []constraintsSpec{ { bad: []string{"ip:10.0.0.0/8"}, @@ -401,9 +373,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"ip:192.168.1.1"}, }, }, - - // #20: IP-based exclusions are effective { + name: "IP-based exclusions are effective", roots: []constraintsSpec{ { bad: []string{"ip:10.0.0.0/8"}, @@ -419,9 +390,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"10.0.0.1\" is excluded", }, - - // #21: intermediates can further constrain IP ranges. { + name: "intermediates can further constrain IP ranges", roots: []constraintsSpec{ { bad: []string{"ip:0.0.0.0/1"}, @@ -439,10 +409,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"11.0.0.1\" is excluded", }, - - // #22: when multiple intermediates are present, chain building can - // avoid intermediates with incompatible constraints. { + name: "multiple intermediates with incompatible constraints", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -459,10 +427,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, noOpenSSL: true, // OpenSSL's chain building is not informed by constraints. }, - - // #23: (same as the previous test, but in the other order in ensure - // that we don't pass it by luck.) { + name: "multiple intermediates with incompatible constraints swapped", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -479,10 +445,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, noOpenSSL: true, // OpenSSL's chain building is not informed by constraints. }, - - // #24: when multiple roots are valid, chain building can avoid roots - // with incompatible constraints. { + name: "multiple roots with incompatible constraints", roots: []constraintsSpec{ {}, { @@ -499,10 +463,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, noOpenSSL: true, // OpenSSL's chain building is not informed by constraints. }, - - // #25: (same as the previous test, but in the other order in ensure - // that we don't pass it by luck.) { + name: "multiple roots with incompatible constraints swapped", roots: []constraintsSpec{ { ok: []string{"dns:foo.com"}, @@ -519,10 +481,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, noOpenSSL: true, // OpenSSL's chain building is not informed by constraints. }, - - // #26: chain building can find a valid path even with multiple levels - // of alternative intermediates and alternative roots. { + name: "chain building with multiple intermediates and roots", roots: []constraintsSpec{ { ok: []string{"dns:foo.com"}, @@ -551,9 +511,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, noOpenSSL: true, // OpenSSL's chain building is not informed by constraints. }, - - // #27: chain building doesn't get stuck when there is no valid path. { + name: "chain building fails with no valid path", roots: []constraintsSpec{ { ok: []string{"dns:foo.com"}, @@ -583,9 +542,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"bar.com\" is not permitted", }, - - // #28: unknown name types don't cause a problem without constraints. { + name: "unknown name types are unconstrained", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -596,9 +554,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"unknown:"}, }, }, - - // #29: unknown name types are allowed even in constrained chains. { + name: "unknown name types allowed in constrained chain", roots: []constraintsSpec{ { ok: []string{"dns:foo.com", "dns:.foo.com"}, @@ -613,10 +570,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"unknown:"}, }, }, - - // #30: without SANs, a certificate with a CN is still accepted in a - // constrained chain, since we ignore the CN in VerifyHostname. { + name: "CN is ignored in constrained chain", roots: []constraintsSpec{ { ok: []string{"dns:foo.com", "dns:.foo.com"}, @@ -632,10 +587,8 @@ var nameConstraintsTests = []nameConstraintsTest{ cn: "foo.com", }, }, - - // #31: IPv6 addresses work in constraints: roots can permit them as - // expected. { + name: "IPv6 permitted constraint", roots: []constraintsSpec{ { ok: []string{"ip:2000:abcd::/32"}, @@ -650,10 +603,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"ip:2000:abcd:1234::"}, }, }, - - // #32: IPv6 addresses work in constraints: root restrictions are - // effective. { + name: "IPv6 permitted constraint is effective", roots: []constraintsSpec{ { ok: []string{"ip:2000:abcd::/32"}, @@ -669,9 +620,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"2000:1234:abcd::\" is not permitted", }, - - // #33: An IPv6 permitted subtree doesn't affect DNS names. { + name: "IPv6 permitted constraint does not affect DNS", roots: []constraintsSpec{ { ok: []string{"ip:2000:abcd::/32"}, @@ -686,9 +636,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"ip:2000:abcd::", "dns:foo.com"}, }, }, - - // #34: IPv6 exclusions don't affect unrelated addresses. { + name: "IPv6 excluded constraint", roots: []constraintsSpec{ { bad: []string{"ip:2000:abcd::/32"}, @@ -703,9 +652,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"ip:2000:1234::"}, }, }, - - // #35: IPv6 exclusions are effective. { + name: "IPv6 excluded constraint is effective", roots: []constraintsSpec{ { bad: []string{"ip:2000:abcd::/32"}, @@ -721,9 +669,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"2000:abcd::\" is excluded", }, - - // #36: IPv6 constraints do not permit IPv4 addresses. { + name: "IPv6 constraint does not permit IPv4", roots: []constraintsSpec{ { ok: []string{"ip:2000:abcd::/32"}, @@ -739,9 +686,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"10.0.0.1\" is not permitted", }, - - // #37: IPv4 constraints do not permit IPv6 addresses. { + name: "IPv4 constraint does not permit IPv6", roots: []constraintsSpec{ { ok: []string{"ip:10.0.0.0/8"}, @@ -757,9 +703,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"2000:abcd::\" is not permitted", }, - - // #38: an exclusion of an unknown type doesn't affect other names. { + name: "unknown excluded constraint does not affect other names", roots: []constraintsSpec{ { bad: []string{"unknown:"}, @@ -774,10 +719,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:example.com"}, }, }, - - // #39: a permitted subtree of an unknown type doesn't affect other - // name types. { + name: "unknown permitted constraint does not affect other names", roots: []constraintsSpec{ { ok: []string{"unknown:"}, @@ -792,9 +735,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:example.com"}, }, }, - - // #40: exact email constraints work { + name: "exact email constraint", roots: []constraintsSpec{ { ok: []string{"email:foo@example.com"}, @@ -809,9 +751,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"email:foo@example.com"}, }, }, - - // #41: exact email constraints are effective { + name: "exact email constraint is effective", roots: []constraintsSpec{ { ok: []string{"email:foo@example.com"}, @@ -827,9 +768,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"bar@example.com\" is not permitted", }, - - // #42: email canonicalisation works. { + name: "email canonicalization", roots: []constraintsSpec{ { ok: []string{"email:foo@example.com"}, @@ -845,9 +785,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, noOpenSSL: true, // OpenSSL doesn't canonicalise email addresses before matching }, - - // #43: limiting email addresses to a host works. { + name: "email host constraint", roots: []constraintsSpec{ { ok: []string{"email:example.com"}, @@ -862,9 +801,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"email:foo@example.com"}, }, }, - - // #44: a leading dot matches hosts one level deep { + name: "email subdomain constraint", roots: []constraintsSpec{ { ok: []string{"email:.example.com"}, @@ -879,9 +817,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"email:foo@sub.example.com"}, }, }, - - // #45: a leading dot does not match the host itself { + name: "email subdomain constraint does not match parent", roots: []constraintsSpec{ { ok: []string{"email:.example.com"}, @@ -897,9 +834,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"foo@example.com\" is not permitted", }, - - // #46: a leading dot also matches two (or more) levels deep. { + name: "email subdomain constraint matches deeper subdomains", roots: []constraintsSpec{ { ok: []string{"email:.example.com"}, @@ -914,9 +850,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"email:foo@sub.sub.example.com"}, }, }, - - // #47: the local part of an email is case-sensitive { + name: "email local part is case-sensitive", roots: []constraintsSpec{ { ok: []string{"email:foo@example.com"}, @@ -932,9 +867,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"Foo@example.com\" is not permitted", }, - - // #48: the domain part of an email is not case-sensitive { + name: "email domain part is case-insensitive", roots: []constraintsSpec{ { ok: []string{"email:foo@EXAMPLE.com"}, @@ -949,9 +883,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"email:foo@example.com"}, }, }, - - // #49: the domain part of a DNS constraint is also not case-sensitive. { + name: "DNS domain is case-insensitive", roots: []constraintsSpec{ { ok: []string{"dns:EXAMPLE.com"}, @@ -966,9 +899,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:example.com"}, }, }, - - // #50: URI constraints only cover the host part of the URI { + name: "URI constraint covers host", roots: []constraintsSpec{ { ok: []string{"uri:example.com"}, @@ -987,9 +919,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, }, }, - - // #51: URIs with IPs are rejected { + name: "URI with IP is rejected", roots: []constraintsSpec{ { ok: []string{"uri:example.com"}, @@ -1005,9 +936,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "URI with IP", }, - - // #52: URIs with IPs and ports are rejected { + name: "URI with IP and port is rejected", roots: []constraintsSpec{ { ok: []string{"uri:example.com"}, @@ -1023,9 +953,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "URI with IP", }, - - // #53: URIs with IPv6 addresses are also rejected { + name: "URI with IPv6 is rejected", roots: []constraintsSpec{ { ok: []string{"uri:example.com"}, @@ -1041,9 +970,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "URI with IP", }, - - // #54: URIs with IPv6 addresses with ports are also rejected { + name: "URI with IPv6 and port is rejected", roots: []constraintsSpec{ { ok: []string{"uri:example.com"}, @@ -1059,9 +987,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "URI with IP", }, - - // #55: URI constraints are effective { + name: "URI permitted constraint is effective", roots: []constraintsSpec{ { ok: []string{"uri:example.com"}, @@ -1077,9 +1004,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"http://bar.com/\" is not permitted", }, - - // #56: URI constraints are effective { + name: "URI excluded constraint is effective", roots: []constraintsSpec{ { bad: []string{"uri:foo.com"}, @@ -1095,9 +1021,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"http://foo.com/\" is excluded", }, - - // #57: URI constraints can allow subdomains { + name: "URI subdomain constraint", roots: []constraintsSpec{ { ok: []string{"uri:.foo.com"}, @@ -1112,10 +1037,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"uri:http://www.foo.com/"}, }, }, - - // #58: excluding an IPv4-mapped-IPv6 address doesn't affect the IPv4 - // version of that address. { + name: "IPv4-mapped-IPv6 exclusion does not affect IPv4", roots: []constraintsSpec{ { bad: []string{"ip:::ffff:1.2.3.4/128"}, @@ -1130,9 +1053,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"ip:1.2.3.4"}, }, }, - - // #59: a URI constraint isn't matched by a URN. { + name: "URI constraint not matched by URN", roots: []constraintsSpec{ { ok: []string{"uri:example.com"}, @@ -1148,10 +1070,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "URI with empty host", }, - - // #60: excluding all IPv6 addresses doesn't exclude all IPv4 addresses - // too, even though IPv4 is mapped into the IPv6 range. { + name: "IPv6 exclusion does not exclude all IPv4", roots: []constraintsSpec{ { ok: []string{"ip:1.2.3.0/24"}, @@ -1167,10 +1087,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"ip:1.2.3.4"}, }, }, - - // #61: omitting extended key usage in a CA certificate implies that - // any usage is ok. { + name: "empty EKU in CA means any is ok", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -1182,9 +1100,8 @@ var nameConstraintsTests = []nameConstraintsTest{ ekus: []string{"serverAuth", "other"}, }, }, - - // #62: The “any” EKU also means that any usage is ok. { + name: "any EKU means any is ok", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -1198,11 +1115,9 @@ var nameConstraintsTests = []nameConstraintsTest{ ekus: []string{"serverAuth", "other"}, }, }, - - // #63: An intermediate with enumerated EKUs causes a failure if we - // test for an EKU not in that set. (ServerAuth is required by // default.) { + name: "intermediate with enumerated EKUs", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -1217,10 +1132,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "incompatible key usage", }, - - // #64: an unknown EKU in the leaf doesn't break anything, even if it's not - // correctly nested. { + name: "unknown EKU in leaf", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -1235,11 +1148,9 @@ var nameConstraintsTests = []nameConstraintsTest{ }, requestedEKUs: []ExtKeyUsage{ExtKeyUsageAny}, }, - - // #65: trying to add extra permitted key usages in an intermediate - // (after a limitation in the root) is acceptable so long as the leaf // certificate doesn't use them. { + name: "intermediate cannot add EKUs not in root if leaf uses them", roots: []constraintsSpec{ { ekus: []string{"serverAuth"}, @@ -1257,9 +1168,8 @@ var nameConstraintsTests = []nameConstraintsTest{ ekus: []string{"serverAuth"}, }, }, - - // #66: EKUs in roots are not ignored. { + name: "EKUs in root are effective", roots: []constraintsSpec{ { ekus: []string{"email"}, @@ -1278,10 +1188,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "incompatible key usage", }, - - // #67: SGC key usages used to permit serverAuth and clientAuth, - // but don't anymore. { + name: "netscapeSGC EKU does not permit server/client auth", roots: []constraintsSpec{ {}, }, @@ -1298,10 +1206,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "incompatible key usage", }, - - // #68: SGC key usages used to permit serverAuth and clientAuth, - // but don't anymore. { + name: "msSGC EKU does not permit server/client auth", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -1316,9 +1222,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "incompatible key usage", }, - - // #69: an empty DNS constraint should allow anything. { + name: "empty DNS permitted constraint allows anything", roots: []constraintsSpec{ { ok: []string{"dns:"}, @@ -1333,9 +1238,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:example.com"}, }, }, - - // #70: an empty DNS constraint should also reject everything. { + name: "empty DNS excluded constraint rejects everything", roots: []constraintsSpec{ { bad: []string{"dns:"}, @@ -1351,9 +1255,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"example.com\" is excluded", }, - - // #71: an empty email constraint should allow anything { + name: "empty email permitted constraint allows anything", roots: []constraintsSpec{ { ok: []string{"email:"}, @@ -1368,9 +1271,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"email:foo@example.com"}, }, }, - - // #72: an empty email constraint should also reject everything. { + name: "empty email excluded constraint rejects everything", roots: []constraintsSpec{ { bad: []string{"email:"}, @@ -1386,9 +1288,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"foo@example.com\" is excluded", }, - - // #73: an empty URI constraint should allow anything { + name: "empty URI permitted constraint allows anything", roots: []constraintsSpec{ { ok: []string{"uri:"}, @@ -1403,9 +1304,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"uri:https://example.com/test"}, }, }, - - // #74: an empty URI constraint should also reject everything. { + name: "empty URI excluded constraint rejects everything", roots: []constraintsSpec{ { bad: []string{"uri:"}, @@ -1421,10 +1321,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "\"https://example.com/test\" is excluded", }, - - // #75: serverAuth in a leaf shouldn't permit clientAuth when requested in - // VerifyOptions. { + name: "serverAuth EKU does not permit clientAuth", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -1438,10 +1336,8 @@ var nameConstraintsTests = []nameConstraintsTest{ requestedEKUs: []ExtKeyUsage{ExtKeyUsageClientAuth}, expectedError: "incompatible key usage", }, - - // #76: MSSGC in a leaf used to match a request for serverAuth, but doesn't - // anymore. { + name: "msSGC EKU does not permit serverAuth", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -1455,14 +1351,11 @@ var nameConstraintsTests = []nameConstraintsTest{ requestedEKUs: []ExtKeyUsage{ExtKeyUsageServerAuth}, expectedError: "incompatible key usage", }, - - // An invalid DNS SAN should be detected only at validation time so - // that we can process CA certificates in the wild that have invalid SANs. - // See https://github.com/golang/go/issues/23995 - - // #77: an invalid DNS or mail SAN will not be detected if name constraint - // checking is not triggered. { + // An invalid DNS SAN should be detected only at validation time so + // that we can process CA certificates in the wild that have invalid SANs. + // See https://github.com/golang/go/issues/23995 + name: "invalid SANs are ignored with no constraints", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -1473,10 +1366,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:this is invalid", "email:this @ is invalid"}, }, }, - - // #78: an invalid DNS SAN will be detected if any name constraint checking - // is triggered. { + name: "invalid DNS SAN detected with constraints", roots: []constraintsSpec{ { bad: []string{"uri:"}, @@ -1492,10 +1383,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "cannot parse dnsName", }, - - // #79: an invalid email SAN will be detected if any name constraint - // checking is triggered. { + name: "invalid email SAN detected with constraints", roots: []constraintsSpec{ { bad: []string{"uri:"}, @@ -1511,9 +1400,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "cannot parse rfc822Name", }, - - // #80: if several EKUs are requested, satisfying any of them is sufficient. { + name: "any requested EKU is sufficient", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -1526,10 +1414,8 @@ var nameConstraintsTests = []nameConstraintsTest{ }, requestedEKUs: []ExtKeyUsage{ExtKeyUsageClientAuth, ExtKeyUsageEmailProtection}, }, - - // #81: EKUs that are not asserted in VerifyOpts are not required to be - // nested. { + name: "unrequested EKUs not required to be nested", roots: make([]constraintsSpec, 1), intermediates: [][]constraintsSpec{ { @@ -1545,9 +1431,8 @@ var nameConstraintsTests = []nameConstraintsTest{ ekus: []string{"email", "serverAuth"}, }, }, - - // #82: a certificate without SANs and CN is accepted in a constrained chain. { + name: "empty leaf is accepted in constrained chain", roots: []constraintsSpec{ { ok: []string{"dns:foo.com", "dns:.foo.com"}, @@ -1562,10 +1447,8 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{}, }, }, - - // #83: a certificate without SANs and with a CN that does not parse as a - // hostname is accepted in a constrained chain. { + name: "no SANs and non-hostname CN is accepted in constrained chain", roots: []constraintsSpec{ { ok: []string{"dns:foo.com", "dns:.foo.com"}, @@ -1578,12 +1461,11 @@ var nameConstraintsTests = []nameConstraintsTest{ }, leaf: leafSpec{ sans: []string{}, - cn: "foo,bar", + cn: "foo.bar", }, }, - - // #84: a certificate with SANs and CN is accepted in a constrained chain. { + name: "constraints don't apply to CN", roots: []constraintsSpec{ { ok: []string{"dns:foo.com", "dns:.foo.com"}, @@ -1599,16 +1481,14 @@ var nameConstraintsTests = []nameConstraintsTest{ cn: "foo.bar", }, }, - - // #85: .example.com is an invalid DNS name, it should not match the - // constraint example.com. { + name: "DNS SAN cannot use leading period form", roots: []constraintsSpec{{ok: []string{"dns:example.com"}}}, leaf: leafSpec{sans: []string{"dns:.example.com"}}, expectedError: "cannot parse dnsName \".example.com\"", }, - // #86: URIs with IPv6 addresses with zones and ports are rejected { + name: "URI with IPv6 and zone is rejected", roots: []constraintsSpec{ { ok: []string{"uri:example.com"}, @@ -1624,6 +1504,113 @@ var nameConstraintsTests = []nameConstraintsTest{ }, expectedError: "URI with IP", }, + { + name: "intermediate can narrow permitted dns scope", + roots: []constraintsSpec{ + { + ok: []string{"dns:"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + { + ok: []string{"dns:example.com"}, + }, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:test.com"}, + }, + expectedError: "\"test.com\" is not permitted", + }, + { + name: "intermediate cannot narrow excluded dns scope", + roots: []constraintsSpec{ + { + bad: []string{"dns:"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + { + bad: []string{"dns:example.com"}, + }, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:test.com"}, + }, + expectedError: "\"test.com\" is excluded by constraint \"\"", + }, + { + name: "intermediate can narrow excluded dns scope", + roots: []constraintsSpec{ + { + bad: []string{"dns:example.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + { + bad: []string{"dns:"}, + }, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:test.com"}, + }, + expectedError: "\"test.com\" is excluded by constraint \"\"", + }, + { + name: "permitted dns constraint is not a prefix match", + roots: []constraintsSpec{ + { + ok: []string{"dns:example.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:testexample.com"}, + }, + expectedError: "\"testexample.com\" is not permitted", + }, + { + name: "subdomain constraint does not allow wildcard", + roots: []constraintsSpec{ + { + ok: []string{"dns:a.com", "dns:foo.example.com", "dns:z.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:*.example.com"}, + }, + expectedError: "\"*.example.com\" is not permitted", + }, + { + name: "excluded dns constraint is not a prefix match", + roots: []constraintsSpec{ + { + bad: []string{"dns:example.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:testexample.com"}, + }, + }, } func makeConstraintsCACert(constraints constraintsSpec, name string, key *ecdsa.PrivateKey, parent *Certificate, parentKey *ecdsa.PrivateKey) (*Certificate, error) { @@ -1885,7 +1872,7 @@ func TestConstraintCases(t *testing.T) { } for i, test := range nameConstraintsTests { - t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + t.Run(test.name, func(t *testing.T) { rootPool := NewCertPool() rootKey := privateKeys.Get().(*ecdsa.PrivateKey) rootName := "Root " + strconv.Itoa(i) @@ -1974,7 +1961,7 @@ func TestConstraintCases(t *testing.T) { } _, err = leafCert.Verify(verifyOpts) - logInfo := true + logInfo := false if len(test.expectedError) == 0 { if err != nil { t.Errorf("unexpected failure: %s", err) From 12d437c09a2ea871333547c8ac3ea536f433891b Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Thu, 25 Sep 2025 19:13:23 -0700 Subject: [PATCH 020/140] crypto/x509: sub-quadratic name constraint checking Previously, we implemented ~quadratic name constraint checking, wherein we would check every SAN against every respective constraint in the chain. This is the technique _basically everyone_ implements, because it's easy, but it requires also capping the total number of constraint checking operations to prevent denial of service. Instead, this change implements a log-linear checking technique, as originally described by davidben@google.com with some minor modifications. The comment at the top of crypto/x509/constraints.go describes this technique in detail. This technique is faster than the existing quadratic approach in all but one specific case, where there are a large number of constraints but only a single name, since our previous algorithm resolves to linear in that case. Change-Id: Icb761f5f9898c04e266c0d0c2b07ab2637f03418 Reviewed-on: https://go-review.googlesource.com/c/go/+/711421 Reviewed-by: Nicholas Husin Reviewed-by: Daniel McCarney LUCI-TryBot-Result: Go LUCI Auto-Submit: Roland Shoemaker Reviewed-by: Nicholas Husin --- src/crypto/x509/constraints.go | 624 +++++++++++++++++++++++++++++++++ src/crypto/x509/verify.go | 287 +-------------- src/crypto/x509/verify_test.go | 39 --- 3 files changed, 630 insertions(+), 320 deletions(-) create mode 100644 src/crypto/x509/constraints.go diff --git a/src/crypto/x509/constraints.go b/src/crypto/x509/constraints.go new file mode 100644 index 00000000000..5addedcf771 --- /dev/null +++ b/src/crypto/x509/constraints.go @@ -0,0 +1,624 @@ +// Copyright 2025 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 x509 + +import ( + "bytes" + "fmt" + "net" + "net/netip" + "net/url" + "slices" + "strings" +) + +// This file contains the data structures and functions necessary for +// efficiently checking X.509 name constraints. The method for constraint +// checking implemented in this file is based on a technique originally +// described by davidben@google.com. +// +// The basic concept is based on the fact that constraints describe possibly +// overlapping subtrees that we need to match against. If sorted in lexicographic +// order, and then pruned, removing any subtrees that overlap with preceding +// subtrees, a simple binary search can be used to find the nearest matching +// prefix. This reduces the complexity of name constraint checking from +// quadratic to log linear complexity. +// +// A close reading of RFC 5280 may suggest that constraints could also be +// implemented as a trie (or radix tree), which would present the possibility of +// doing construction and matching in linear time, but the memory cost of +// implementing them is actually quite high, and in the worst case (where each +// node has a high number of children) can be abused to require a program to use +// significant amounts of memory. The log linear approach taken here is +// extremely cheap in terms of memory because we directly alias the already +// parsed constraints, thus avoiding the need to do significant additional +// allocations. +// +// The basic data structure is nameConstraintsSet, which implements the sorting, +// pruning, and querying of the prefix sets. +// +// In order to check IP, DNS, URI, and email constraints, we need to use two +// different techniques, one for IP addresses, which is quite simple, and one +// for DNS names, which additionally compose the portions of URIs and emails we +// care about (technically we also need some special logic for email addresses +// as well for when constraints comprise of full email addresses) which is +// slightly more complex. +// +// IP addresses use two nameConstraintsSets, one for IPv4 addresses and one for +// IPv6 addresses, with no additional logic. +// +// DNS names require some extra logic in order to handle the distinctions +// between permitted and excluded subtrees, as well as for wildcards, and the +// semantics of leading period constraints (i.e. '.example.com'). This logic is +// implemented in the dnsConstraints type. +// +// Email addresses also require some additional logic, which does not make use +// of nameConstraintsSet, to handle constraints which define full email +// addresses (i.e. 'test@example.com'). For bare domain constraints, we use the +// dnsConstraints type described above, querying the domain portion of the email +// address. For full email addresses, we also hold a map of email addresses that +// map the local portion of the email to the domain. When querying full email +// addresses we then check if the local portion of the email is present in the +// map, and if so case insensitively compare the domain portion of the +// email. + +type nameConstraintsSet[T *net.IPNet | string, V net.IP | string] struct { + set []T +} + +// sortAndPrune sorts the constraints using the provided comparison function, and then +// prunes any constraints that are subsets of preceding constraints using the +// provided subset function. +func (nc *nameConstraintsSet[T, V]) sortAndPrune(cmp func(T, T) int, subset func(T, T) bool) { + if len(nc.set) < 2 { + return + } + + slices.SortFunc(nc.set, cmp) + + if len(nc.set) < 2 { + return + } + writeIndex := 1 + for readIndex := 1; readIndex < len(nc.set); readIndex++ { + if !subset(nc.set[writeIndex-1], nc.set[readIndex]) { + nc.set[writeIndex] = nc.set[readIndex] + writeIndex++ + } + } + nc.set = nc.set[:writeIndex] +} + +// search does a binary search over the constraints set for the provided value +// s, using the provided comparison function cmp to find the lower bound, and +// the match function to determine if the found constraint is a prefix of s. If +// a matching constraint is found, it is returned along with true. If no +// matching constraint is found, the zero value of T and false are returned. +func (nc *nameConstraintsSet[T, V]) search(s V, cmp func(T, V) int, match func(T, V) bool) (lowerBound T, exactMatch bool) { + if len(nc.set) == 0 { + return lowerBound, false + } + // Look for the lower bound of s in the set. + i, found := slices.BinarySearchFunc(nc.set, s, cmp) + // If we found an exact match, return it + if found { + return nc.set[i], true + } + + if i < 0 { + return lowerBound, false + } + + var constraint T + if i == 0 { + constraint = nc.set[0] + } else { + constraint = nc.set[i-1] + } + if match(constraint, s) { + return constraint, true + } + return lowerBound, false +} + +func ipNetworkSubset(a, b *net.IPNet) bool { + if !a.Contains(b.IP) { + return false + } + broadcast := make(net.IP, len(b.IP)) + for i := range b.IP { + broadcast[i] = b.IP[i] | (^b.Mask[i]) + } + return a.Contains(broadcast) +} + +func ipNetworkCompare(a, b *net.IPNet) int { + i := bytes.Compare(a.IP, b.IP) + if i != 0 { + return i + } + return bytes.Compare(a.Mask, b.Mask) +} + +func ipBinarySearch(constraint *net.IPNet, target net.IP) int { + return bytes.Compare(constraint.IP, target) +} + +func ipMatch(constraint *net.IPNet, target net.IP) bool { + return constraint.Contains(target) +} + +type ipConstraints struct { + // NOTE: we could store IP network prefixes as a pre-processed byte slice + // (i.e. by masking the IP) and doing the byte prefix checking using faster + // techniques, but this would require allocating new byte slices, which is + // likely significantly more expensive than just operating on the + // pre-allocated *net.IPNet and net.IP objects directly. + + ipv4 *nameConstraintsSet[*net.IPNet, net.IP] + ipv6 *nameConstraintsSet[*net.IPNet, net.IP] +} + +func newIPNetConstraints(l []*net.IPNet) interface { + query(net.IP) (*net.IPNet, bool) +} { + if len(l) == 0 { + return nil + } + var ipv4, ipv6 []*net.IPNet + for _, n := range l { + if len(n.IP) == net.IPv4len { + ipv4 = append(ipv4, n) + } else { + ipv6 = append(ipv6, n) + } + } + var v4c, v6c *nameConstraintsSet[*net.IPNet, net.IP] + if len(ipv4) > 0 { + v4c = &nameConstraintsSet[*net.IPNet, net.IP]{ + set: ipv4, + } + v4c.sortAndPrune(ipNetworkCompare, ipNetworkSubset) + } + if len(ipv6) > 0 { + v6c = &nameConstraintsSet[*net.IPNet, net.IP]{ + set: ipv6, + } + v6c.sortAndPrune(ipNetworkCompare, ipNetworkSubset) + } + return &ipConstraints{ipv4: v4c, ipv6: v6c} +} + +func (ipc *ipConstraints) query(ip net.IP) (*net.IPNet, bool) { + var c *nameConstraintsSet[*net.IPNet, net.IP] + if len(ip) == net.IPv4len { + c = ipc.ipv4 + } else { + c = ipc.ipv6 + } + if c == nil { + return nil, false + } + return c.search(ip, ipBinarySearch, ipMatch) +} + +// dnsHasSuffix case-insensitively checks if DNS name b is a label suffix of DNS +// name a, meaning that example.com is not considered a suffix of +// testexample.com, but is a suffix of test.example.com. +// +// dnsHasSuffix supports the URI "leading period" constraint semantics, which +// while not explicitly defined for dNSNames in RFC 5280, are widely supported +// (see errata 5997). In particular, a constraint of ".example.com" is +// considered to only match subdomains of example.com, but not example.com +// itself. +// +// a and b must both be non-empty strings representing (mostly) valid DNS names. +func dnsHasSuffix(a, b string) bool { + lenA := len(a) + lenB := len(b) + if lenA > lenB { + return false + } + i := lenA - 1 + offset := lenA - lenB + for ; i >= 0; i-- { + ar, br := a[i], b[i-(offset)] + if ar == br { + continue + } + if br < ar { + ar, br = br, ar + } + if 'A' <= ar && ar <= 'Z' && br == ar+'a'-'A' { + continue + } + return false + } + + if a[0] != '.' && lenB > lenA && b[lenB-lenA-1] != '.' { + return false + } + + return true +} + +// dnsCompareTable contains the ASCII alphabet mapped from a characters index in +// the table to its lowercased form. +var dnsCompareTable [256]byte + +func init() { + // NOTE: we don't actually need the + // full alphabet, but calculating offsets would be more expensive than just + // having redundant characters. + for i := 0; i < 256; i++ { + c := byte(i) + if 'A' <= c && c <= 'Z' { + // Lowercase uppercase characters A-Z. + c += 'a' - 'A' + } + dnsCompareTable[i] = c + } + // Set the period character to 0 so that we get the right sorting behavior. + // + // In particular, we need the period character to sort before the only + // other valid DNS name character which isn't a-z or 0-9, the hyphen, + // otherwise a name with a dash would be incorrectly sorted into the middle + // of another tree. + // + // For example, imagine a certificate with the constraints "a.com", "a.a.com", and + // "a-a.com". These would sort as "a.com", "a-a.com", "a.a.com", which would break + // the pruning step since we wouldn't see that "a.a.com" is a subset of "a.com". + // Sorting the period before the hyphen ensures that "a.a.com" sorts before "a-a.com". + dnsCompareTable['.'] = 0 +} + +// dnsCompare is a case-insensitive reversed implementation of strings.Compare +// that operates from the end to the start of the strings. This is more +// efficient that allocating reversed version of a and b and using +// strings.Compare directly (even though it is highly optimized). +// +// NOTE: this function treats the period character ('.') as sorting above every +// other character, which is necessary for us to properly sort names into their +// correct order. This is further discussed in the init function above. +func dnsCompare(a, b string) int { + idxA := len(a) - 1 + idxB := len(b) - 1 + + for idxA >= 0 && idxB >= 0 { + byteA := dnsCompareTable[a[idxA]] + byteB := dnsCompareTable[b[idxB]] + if byteA == byteB { + idxA-- + idxB-- + continue + } + ret := 1 + if byteA < byteB { + ret = -1 + } + return ret + } + + ret := 0 + if idxA < idxB { + ret = -1 + } else if idxB < idxA { + ret = 1 + } + return ret +} + +type dnsConstraints struct { + // all lets us short circuit the query logic if we see a zero length + // constraint which permits or excludes everything. + all bool + + // permitted indicates if these constraints are for permitted or excluded + // names. + permitted bool + + constraints *nameConstraintsSet[string, string] + + // parentConstraints contains a subset of constraints which are used for + // wildcard SAN queries, which are constructed by removing the first label + // from the constraints in constraints. parentConstraints is only populated + // if permitted is false. + parentConstraints map[string]string +} + +func newDNSConstraints(l []string, permitted bool) interface{ query(string) (string, bool) } { + if len(l) == 0 { + return nil + } + for _, n := range l { + if len(n) == 0 { + return &dnsConstraints{all: true} + } + } + constraints := slices.Clone(l) + + nc := &dnsConstraints{ + constraints: &nameConstraintsSet[string, string]{ + set: constraints, + }, + permitted: permitted, + } + + nc.constraints.sortAndPrune(dnsCompare, dnsHasSuffix) + + if !permitted { + parentConstraints := map[string]string{} + for _, name := range nc.constraints.set { + trimmedName := trimFirstLabel(name) + if trimmedName == "" { + continue + } + parentConstraints[trimmedName] = name + } + if len(parentConstraints) > 0 { + nc.parentConstraints = parentConstraints + } + } + + return nc +} + +func (dnc *dnsConstraints) query(s string) (string, bool) { + if dnc.all { + return "", true + } + + constraint, match := dnc.constraints.search(s, dnsCompare, dnsHasSuffix) + if match { + return constraint, true + } + + if !dnc.permitted && s[0] == '*' { + trimmed := trimFirstLabel(s) + if constraint, found := dnc.parentConstraints[trimmed]; found { + return constraint, true + } + } + return "", false +} + +type emailConstraints struct { + dnsConstraints interface{ query(string) (string, bool) } + + fullEmails map[string]string +} + +func newEmailConstraints(l []string, permitted bool) interface { + query(parsedEmail) (string, bool) +} { + if len(l) == 0 { + return nil + } + exactMap := map[string]string{} + var domains []string + for _, c := range l { + if !strings.ContainsRune(c, '@') { + domains = append(domains, c) + continue + } + parsed, ok := parseRFC2821Mailbox(c) + if !ok { + // We've already parsed these addresses in parseCertificate, and + // treat failures as a hard failure for parsing. The only way we can + // get a parse failure here is if the caller has mutated the + // certificate since parsing. + continue + } + exactMap[parsed.local] = parsed.domain + } + ec := &emailConstraints{ + fullEmails: exactMap, + } + if len(domains) > 0 { + ec.dnsConstraints = newDNSConstraints(domains, permitted) + } + return ec +} + +func (ec *emailConstraints) query(s parsedEmail) (string, bool) { + if len(ec.fullEmails) > 0 && strings.ContainsRune(s.email, '@') { + if domain, ok := ec.fullEmails[s.mailbox.local]; ok && strings.EqualFold(domain, s.mailbox.domain) { + return ec.fullEmails[s.email] + "@" + s.mailbox.domain, true + } + } + if ec.dnsConstraints == nil { + return "", false + } + constraint, found := ec.dnsConstraints.query(s.mailbox.domain) + return constraint, found +} + +type constraints[T any, V any] struct { + constraintType string + permitted interface{ query(V) (T, bool) } + excluded interface{ query(V) (T, bool) } +} + +func checkConstraints[T string | *net.IPNet, V any, P string | net.IP | parsedURI | parsedEmail](c constraints[T, V], s V, p P) error { + if c.permitted != nil { + if _, found := c.permitted.query(s); !found { + return fmt.Errorf("%s %q is not permitted by any constraint", c.constraintType, p) + } + } + if c.excluded != nil { + if constraint, found := c.excluded.query(s); found { + return fmt.Errorf("%s %q is excluded by constraint %q", c.constraintType, p, constraint) + } + } + return nil +} + +type chainConstraints struct { + ip constraints[*net.IPNet, net.IP] + dns constraints[string, string] + uri constraints[string, string] + email constraints[string, parsedEmail] + + index int + next *chainConstraints +} + +func (cc *chainConstraints) check(dns []string, uris []parsedURI, emails []parsedEmail, ips []net.IP) error { + for _, ip := range ips { + if err := checkConstraints(cc.ip, ip, ip); err != nil { + return err + } + } + for _, d := range dns { + if !domainNameValid(d, false) { + return fmt.Errorf("x509: cannot parse dnsName %q", d) + } + if err := checkConstraints(cc.dns, d, d); err != nil { + return err + } + } + for _, u := range uris { + if !domainNameValid(u.domain, false) { + return fmt.Errorf("x509: internal error: URI SAN %q failed to parse", u) + } + if err := checkConstraints(cc.uri, u.domain, u); err != nil { + return err + } + } + for _, e := range emails { + if !domainNameValid(e.mailbox.domain, false) { + return fmt.Errorf("x509: cannot parse rfc822Name %q", e.mailbox) + } + if err := checkConstraints(cc.email, e, e); err != nil { + return err + } + } + return nil +} + +func checkChainConstraints(chain []*Certificate) error { + var currentConstraints *chainConstraints + var last *chainConstraints + for i, c := range chain { + if !c.hasNameConstraints() { + continue + } + cc := &chainConstraints{ + ip: constraints[*net.IPNet, net.IP]{"IP address", newIPNetConstraints(c.PermittedIPRanges), newIPNetConstraints(c.ExcludedIPRanges)}, + dns: constraints[string, string]{"DNS name", newDNSConstraints(c.PermittedDNSDomains, true), newDNSConstraints(c.ExcludedDNSDomains, false)}, + uri: constraints[string, string]{"URI", newDNSConstraints(c.PermittedURIDomains, true), newDNSConstraints(c.ExcludedURIDomains, false)}, + email: constraints[string, parsedEmail]{"email address", newEmailConstraints(c.PermittedEmailAddresses, true), newEmailConstraints(c.ExcludedEmailAddresses, false)}, + index: i, + } + if currentConstraints == nil { + currentConstraints = cc + last = cc + } else if last != nil { + last.next = cc + last = cc + } + } + if currentConstraints == nil { + return nil + } + + for i, c := range chain { + if !c.hasSANExtension() { + continue + } + if i >= currentConstraints.index { + for currentConstraints.index <= i { + if currentConstraints.next == nil { + return nil + } + currentConstraints = currentConstraints.next + } + } + + uris, err := parseURIs(c.URIs) + if err != nil { + return err + } + emails, err := parseMailboxes(c.EmailAddresses) + if err != nil { + return err + } + + for n := currentConstraints; n != nil; n = n.next { + if err := n.check(c.DNSNames, uris, emails, c.IPAddresses); err != nil { + return err + } + } + } + + return nil +} + +type parsedURI struct { + uri *url.URL + domain string +} + +func (u parsedURI) String() string { + return u.uri.String() +} + +func parseURIs(uris []*url.URL) ([]parsedURI, error) { + parsed := make([]parsedURI, 0, len(uris)) + for _, uri := range uris { + host := strings.ToLower(uri.Host) + if len(host) == 0 { + return nil, fmt.Errorf("URI with empty host (%q) cannot be matched against constraints", uri.String()) + } + if strings.Contains(host, ":") && !strings.HasSuffix(host, "]") { + var err error + host, _, err = net.SplitHostPort(uri.Host) + if err != nil { + return nil, fmt.Errorf("cannot parse URI host %q: %v", uri.Host, err) + } + } + + // netip.ParseAddr will reject the URI IPv6 literal form "[...]", so we + // check if _either_ the string parses as an IP, or if it is enclosed in + // square brackets. + if _, err := netip.ParseAddr(host); err == nil || (strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]")) { + return nil, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", uri.String()) + } + + parsed = append(parsed, parsedURI{uri, host}) + } + return parsed, nil +} + +type parsedEmail struct { + email string + mailbox *rfc2821Mailbox +} + +func (e parsedEmail) String() string { + return e.mailbox.local + "@" + e.mailbox.domain +} + +func parseMailboxes(emails []string) ([]parsedEmail, error) { + parsed := make([]parsedEmail, 0, len(emails)) + for _, email := range emails { + mailbox, ok := parseRFC2821Mailbox(email) + if !ok { + return nil, fmt.Errorf("cannot parse rfc822Name %q", email) + } + mailbox.domain = strings.ToLower(mailbox.domain) + parsed = append(parsed, parsedEmail{strings.ToLower(email), &mailbox}) + } + return parsed, nil +} + +func trimFirstLabel(dnsName string) string { + firstDotInd := strings.IndexByte(dnsName, '.') + if firstDotInd < 0 { + // Constraint is a single label, we cannot trim it. + return "" + } + return dnsName[firstDotInd:] +} diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go index b13e0933456..fa46e226e3c 100644 --- a/src/crypto/x509/verify.go +++ b/src/crypto/x509/verify.go @@ -13,9 +13,6 @@ import ( "iter" "maps" "net" - "net/netip" - "net/url" - "reflect" "runtime" "slices" "strings" @@ -430,180 +427,6 @@ func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) { return reverseLabels, true } -func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { - // If the constraint contains an @, then it specifies an exact mailbox - // name. - if strings.Contains(constraint, "@") { - constraintMailbox, ok := parseRFC2821Mailbox(constraint) - if !ok { - return false, fmt.Errorf("x509: internal error: cannot parse constraint %q", constraint) - } - return mailbox.local == constraintMailbox.local && strings.EqualFold(mailbox.domain, constraintMailbox.domain), nil - } - - // Otherwise the constraint is like a DNS constraint of the domain part - // of the mailbox. - return matchDomainConstraint(mailbox.domain, constraint, reversedDomainsCache, reversedConstraintsCache) -} - -func matchURIConstraint(uri *url.URL, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { - // From RFC 5280, Section 4.2.1.10: - // “a uniformResourceIdentifier that does not include an authority - // component with a host name specified as a fully qualified domain - // name (e.g., if the URI either does not include an authority - // component or includes an authority component in which the host name - // is specified as an IP address), then the application MUST reject the - // certificate.” - - host := uri.Host - if len(host) == 0 { - return false, fmt.Errorf("URI with empty host (%q) cannot be matched against constraints", uri.String()) - } - - if strings.Contains(host, ":") && !strings.HasSuffix(host, "]") { - var err error - host, _, err = net.SplitHostPort(uri.Host) - if err != nil { - return false, err - } - } - - // netip.ParseAddr will reject the URI IPv6 literal form "[...]", so we - // check if _either_ the string parses as an IP, or if it is enclosed in - // square brackets. - if _, err := netip.ParseAddr(host); err == nil || (strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]")) { - return false, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", uri.String()) - } - - return matchDomainConstraint(host, constraint, reversedDomainsCache, reversedConstraintsCache) -} - -func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) { - if len(ip) != len(constraint.IP) { - return false, nil - } - - for i := range ip { - if mask := constraint.Mask[i]; ip[i]&mask != constraint.IP[i]&mask { - return false, nil - } - } - - return true, nil -} - -func matchDomainConstraint(domain, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { - // The meaning of zero length constraints is not specified, but this - // code follows NSS and accepts them as matching everything. - if len(constraint) == 0 { - return true, nil - } - - domainLabels, found := reversedDomainsCache[domain] - if !found { - var ok bool - domainLabels, ok = domainToReverseLabels(domain) - if !ok { - return false, fmt.Errorf("x509: internal error: cannot parse domain %q", domain) - } - reversedDomainsCache[domain] = domainLabels - } - - // RFC 5280 says that a leading period in a domain name means that at - // least one label must be prepended, but only for URI and email - // constraints, not DNS constraints. The code also supports that - // behaviour for DNS constraints. - - mustHaveSubdomains := false - if constraint[0] == '.' { - mustHaveSubdomains = true - constraint = constraint[1:] - } - - constraintLabels, found := reversedConstraintsCache[constraint] - if !found { - var ok bool - constraintLabels, ok = domainToReverseLabels(constraint) - if !ok { - return false, fmt.Errorf("x509: internal error: cannot parse domain %q", constraint) - } - reversedConstraintsCache[constraint] = constraintLabels - } - - if len(domainLabels) < len(constraintLabels) || - (mustHaveSubdomains && len(domainLabels) == len(constraintLabels)) { - return false, nil - } - - for i, constraintLabel := range constraintLabels { - if !strings.EqualFold(constraintLabel, domainLabels[i]) { - return false, nil - } - } - - return true, nil -} - -// checkNameConstraints checks that c permits a child certificate to claim the -// given name, of type nameType. The argument parsedName contains the parsed -// form of name, suitable for passing to the match function. The total number -// of comparisons is tracked in the given count and should not exceed the given -// limit. -func (c *Certificate) checkNameConstraints(count *int, - maxConstraintComparisons int, - nameType string, - name string, - parsedName any, - match func(parsedName, constraint any) (match bool, err error), - permitted, excluded any) error { - - excludedValue := reflect.ValueOf(excluded) - - *count += excludedValue.Len() - if *count > maxConstraintComparisons { - return CertificateInvalidError{c, TooManyConstraints, ""} - } - - for i := 0; i < excludedValue.Len(); i++ { - constraint := excludedValue.Index(i).Interface() - match, err := match(parsedName, constraint) - if err != nil { - return CertificateInvalidError{c, CANotAuthorizedForThisName, err.Error()} - } - - if match { - return CertificateInvalidError{c, CANotAuthorizedForThisName, fmt.Sprintf("%s %q is excluded by constraint %q", nameType, name, constraint)} - } - } - - permittedValue := reflect.ValueOf(permitted) - - *count += permittedValue.Len() - if *count > maxConstraintComparisons { - return CertificateInvalidError{c, TooManyConstraints, ""} - } - - ok := true - for i := 0; i < permittedValue.Len(); i++ { - constraint := permittedValue.Index(i).Interface() - - var err error - if ok, err = match(parsedName, constraint); err != nil { - return CertificateInvalidError{c, CANotAuthorizedForThisName, err.Error()} - } - - if ok { - break - } - } - - if !ok { - return CertificateInvalidError{c, CANotAuthorizedForThisName, fmt.Sprintf("%s %q is not permitted by any constraint", nameType, name)} - } - - return nil -} - // isValid performs validity checks on c given that it is a candidate to append // to the chain in currentChain. func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *VerifyOptions) error { @@ -636,8 +459,10 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V } } - if (certType == intermediateCertificate || certType == rootCertificate) && len(currentChain) == 0 { - return errors.New("x509: internal error: empty chain when appending CA cert") + if certType == intermediateCertificate || certType == rootCertificate { + if len(currentChain) == 0 { + return errors.New("x509: internal error: empty chain when appending CA cert") + } } // KeyUsage status flags are ignored. From Engineering Security, Peter @@ -794,9 +619,9 @@ func (c *Certificate) Verify(opts VerifyOptions) ([][]*Certificate, error) { incompatibleKeyUsageChains++ return true } - if err := checkChainConstraints(chain, opts); err != nil { + if err := checkChainConstraints(chain); err != nil { if constraintsHintErr == nil { - constraintsHintErr = err + constraintsHintErr = CertificateInvalidError{c, CANotAuthorizedForThisName, err.Error()} } return true } @@ -1182,106 +1007,6 @@ NextCert: return true } -func checkChainConstraints(chain []*Certificate, opts VerifyOptions) error { - maxConstraintComparisons := opts.MaxConstraintComparisions - if maxConstraintComparisons == 0 { - maxConstraintComparisons = 250000 - } - comparisonCount := 0 - - // Each time we do constraint checking, we need to check the constraints in - // the current certificate against all of the names that preceded it. We - // reverse these names using domainToReverseLabels, which is a relatively - // expensive operation. Since we check each name against each constraint, - // this requires us to do N*C calls to domainToReverseLabels (where N is the - // total number of names that preceed the certificate, and C is the total - // number of constraints in the certificate). By caching the results of - // calling domainToReverseLabels, we can reduce that to N+C calls at the - // cost of keeping all of the parsed names and constraints in memory until - // we return from isValid. - reversedDomainsCache := map[string][]string{} - reversedConstraintsCache := map[string][]string{} - - for i, c := range chain { - if !c.hasNameConstraints() { - continue - } - for _, sanCert := range chain[:i] { - if !sanCert.hasSANExtension() { - continue - } - err := forEachSAN(sanCert.getSANExtension(), func(tag int, data []byte) error { - switch tag { - case nameTypeEmail: - name := string(data) - mailbox, ok := parseRFC2821Mailbox(name) - if !ok { - return fmt.Errorf("x509: cannot parse rfc822Name %q", mailbox) - } - - if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox, - func(parsedName, constraint any) (bool, error) { - return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string), reversedDomainsCache, reversedConstraintsCache) - }, c.PermittedEmailAddresses, c.ExcludedEmailAddresses); err != nil { - return err - } - - case nameTypeDNS: - name := string(data) - if !domainNameValid(name, false) { - return fmt.Errorf("x509: cannot parse dnsName %q", name) - } - - if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name, - func(parsedName, constraint any) (bool, error) { - return matchDomainConstraint(parsedName.(string), constraint.(string), reversedDomainsCache, reversedConstraintsCache) - }, c.PermittedDNSDomains, c.ExcludedDNSDomains); err != nil { - return err - } - - case nameTypeURI: - name := string(data) - uri, err := url.Parse(name) - if err != nil { - return fmt.Errorf("x509: internal error: URI SAN %q failed to parse", name) - } - - if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "URI", name, uri, - func(parsedName, constraint any) (bool, error) { - return matchURIConstraint(parsedName.(*url.URL), constraint.(string), reversedDomainsCache, reversedConstraintsCache) - }, c.PermittedURIDomains, c.ExcludedURIDomains); err != nil { - return err - } - - case nameTypeIP: - ip := net.IP(data) - if l := len(ip); l != net.IPv4len && l != net.IPv6len { - return fmt.Errorf("x509: internal error: IP SAN %x failed to parse", data) - } - - if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "IP address", ip.String(), ip, - func(parsedName, constraint any) (bool, error) { - return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet)) - }, c.PermittedIPRanges, c.ExcludedIPRanges); err != nil { - return err - } - - default: - // Unknown SAN types are ignored. - } - - return nil - }) - - if err != nil { - return err - } - } - } - - return nil -} - func mustNewOIDFromInts(ints []uint64) OID { oid, err := OIDFromInts(ints) if err != nil { diff --git a/src/crypto/x509/verify_test.go b/src/crypto/x509/verify_test.go index 17e7aaf897a..6dadba3a566 100644 --- a/src/crypto/x509/verify_test.go +++ b/src/crypto/x509/verify_test.go @@ -1331,45 +1331,6 @@ func TestUnknownAuthorityError(t *testing.T) { } } -var nameConstraintTests = []struct { - constraint, domain string - expectError bool - shouldMatch bool -}{ - {"", "anything.com", false, true}, - {"example.com", "example.com", false, true}, - {"example.com.", "example.com", true, false}, - {"example.com", "example.com.", true, false}, - {"example.com", "ExAmPle.coM", false, true}, - {"example.com", "exampl1.com", false, false}, - {"example.com", "www.ExAmPle.coM", false, true}, - {"example.com", "sub.www.ExAmPle.coM", false, true}, - {"example.com", "notexample.com", false, false}, - {".example.com", "example.com", false, false}, - {".example.com", "www.example.com", false, true}, - {".example.com", "www..example.com", true, false}, -} - -func TestNameConstraints(t *testing.T) { - for i, test := range nameConstraintTests { - result, err := matchDomainConstraint(test.domain, test.constraint, map[string][]string{}, map[string][]string{}) - - if err != nil && !test.expectError { - t.Errorf("unexpected error for test #%d: domain=%s, constraint=%s, err=%s", i, test.domain, test.constraint, err) - continue - } - - if err == nil && test.expectError { - t.Errorf("unexpected success for test #%d: domain=%s, constraint=%s", i, test.domain, test.constraint) - continue - } - - if result != test.shouldMatch { - t.Errorf("unexpected result for test #%d: domain=%s, constraint=%s, result=%t", i, test.domain, test.constraint, result) - } - } -} - const selfSignedWithCommonName = `-----BEGIN CERTIFICATE----- MIIDCjCCAfKgAwIBAgIBADANBgkqhkiG9w0BAQsFADAaMQswCQYDVQQKEwJjYTEL MAkGA1UEAxMCY2EwHhcNMTYwODI4MTcwOTE4WhcNMjEwODI3MTcwOTE4WjAcMQsw From e8fdfeb72b0468b645f256bcaf46570f866a54fd Mon Sep 17 00:00:00 2001 From: Quentin Quaadgras Date: Wed, 19 Nov 2025 21:18:39 +0000 Subject: [PATCH 021/140] reflect: add iterator equivalents for NumField, NumIn, NumOut and NumMethod The new methods are Type.Fields, Type.Methods, Type.Ins, Type.Outs, Value.Fields and Value.Methods. These methods have been introduced into the reflect package (as well as tests) replacing three-clause for loops where possible. Fixes #66631 Change-Id: Iab346e52c0eadd7817afae96d9ef73a35db65fd2 GitHub-Last-Rev: 8768ef71b9fd74536094cb1072c7075dc732cd5c GitHub-Pull-Request: golang/go#75646 Reviewed-on: https://go-review.googlesource.com/c/go/+/707356 LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui Reviewed-by: Alan Donovan Auto-Submit: Alan Donovan --- api/next/66631.txt | 6 ++ doc/next/6-stdlib/99-minor/reflect/66631.md | 4 ++ src/reflect/abi_test.go | 4 +- src/reflect/all_test.go | 3 +- src/reflect/benchmark_test.go | 11 ++-- src/reflect/example_test.go | 3 +- src/reflect/type.go | 69 +++++++++++++++++++++ src/reflect/value.go | 47 +++++++++++++- 8 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 api/next/66631.txt create mode 100644 doc/next/6-stdlib/99-minor/reflect/66631.md diff --git a/api/next/66631.txt b/api/next/66631.txt new file mode 100644 index 00000000000..ffc068bde63 --- /dev/null +++ b/api/next/66631.txt @@ -0,0 +1,6 @@ +pkg reflect, type Type interface, Fields() iter.Seq[StructField] #66631 +pkg reflect, type Type interface, Methods() iter.Seq[Method] #66631 +pkg reflect, type Type interface, Ins() iter.Seq[Type] #66631 +pkg reflect, type Type interface, Outs() iter.Seq[Type] #66631 +pkg reflect, method (Value) Fields() iter.Seq2[StructField, Value] #66631 +pkg reflect, method (Value) Methods() iter.Seq2[Method, Value] #66631 diff --git a/doc/next/6-stdlib/99-minor/reflect/66631.md b/doc/next/6-stdlib/99-minor/reflect/66631.md new file mode 100644 index 00000000000..ec5a04ca889 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/reflect/66631.md @@ -0,0 +1,4 @@ +[reflect.Type] includes new methods that return iterators for a type's fields, methods, inputs and outputs. +Similarly, [reflect.Value] includes two new methods that return iterators over a value's fields or methods, +each element being a pair of the value ([reflect.Value]) and its type information ([reflect.StructField] or +[reflect.Method]). diff --git a/src/reflect/abi_test.go b/src/reflect/abi_test.go index 9d934727790..576f288903d 100644 --- a/src/reflect/abi_test.go +++ b/src/reflect/abi_test.go @@ -175,8 +175,8 @@ func TestReflectCallABI(t *testing.T) { t.Fatalf("test case has different number of inputs and outputs: %d in, %d out", typ.NumIn(), typ.NumOut()) } var args []reflect.Value - for i := 0; i < typ.NumIn(); i++ { - args = append(args, genValue(t, typ.In(i), r)) + for arg := range typ.Ins() { + args = append(args, genValue(t, arg, r)) } results := fn.Call(args) for i := range results { diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 30ec3fad512..54a80e98c7e 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -8491,8 +8491,7 @@ func TestInitFuncTypes(t *testing.T) { go func() { defer wg.Done() ipT := TypeOf(net.IP{}) - for i := 0; i < ipT.NumMethod(); i++ { - _ = ipT.Method(i) + for range ipT.Methods() { } }() } diff --git a/src/reflect/benchmark_test.go b/src/reflect/benchmark_test.go index 6b2f9ce7a03..d5cea2becf5 100644 --- a/src/reflect/benchmark_test.go +++ b/src/reflect/benchmark_test.go @@ -146,10 +146,8 @@ func BenchmarkIsZero(b *testing.B) { s.ArrayInt_1024_NoZero[512] = 1 source := ValueOf(s) - for i := 0; i < source.NumField(); i++ { - name := source.Type().Field(i).Name - value := source.Field(i) - b.Run(name, func(b *testing.B) { + for field, value := range source.Fields() { + b.Run(field.Name, func(b *testing.B) { for i := 0; i < b.N; i++ { sink = value.IsZero() } @@ -175,9 +173,8 @@ func BenchmarkSetZero(b *testing.B) { Struct Value })).Elem() - for i := 0; i < source.NumField(); i++ { - name := source.Type().Field(i).Name - value := source.Field(i) + for field, value := range source.Fields() { + name := field.Name zero := Zero(value.Type()) b.Run(name+"/Direct", func(b *testing.B) { for i := 0; i < b.N; i++ { diff --git a/src/reflect/example_test.go b/src/reflect/example_test.go index b4f3b2932f7..bcc2303766e 100644 --- a/src/reflect/example_test.go +++ b/src/reflect/example_test.go @@ -96,8 +96,7 @@ func ExampleStructTag_Lookup() { s := S{} st := reflect.TypeOf(s) - for i := 0; i < st.NumField(); i++ { - field := st.Field(i) + for field := range st.Fields() { if alias, ok := field.Tag.Lookup("alias"); ok { if alias == "" { fmt.Println("(blank)") diff --git a/src/reflect/type.go b/src/reflect/type.go index 914b5443f35..a2cab6eb7a5 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -18,6 +18,7 @@ package reflect import ( "internal/abi" "internal/goarch" + "iter" "runtime" "strconv" "sync" @@ -64,6 +65,10 @@ type Type interface { // This may make the executable binary larger but will not affect execution time. Method(int) Method + // Methods returns an iterator over each method in the type's method set. The sequence is + // equivalent to calling Method successively for each index i in the range [0, NumMethod()). + Methods() iter.Seq[Method] + // MethodByName returns the method with that name in the type's // method set and a boolean indicating if the method was found. // @@ -172,6 +177,11 @@ type Type interface { // It panics if i is not in the range [0, NumField()). Field(i int) StructField + // Fields returns an iterator over each struct field for struct type t. The sequence is + // equivalent to calling Field successively for each index i in the range [0, NumField()). + // It panics if the type's Kind is not Struct. + Fields() iter.Seq[StructField] + // FieldByIndex returns the nested field corresponding // to the index sequence. It is equivalent to calling Field // successively for each index i. @@ -208,6 +218,11 @@ type Type interface { // It panics if i is not in the range [0, NumIn()). In(i int) Type + // Ins returns an iterator over each input parameter of function type t. The sequence + // is equivalent to calling In successively for each index i in the range [0, NumIn()). + // It panics if the type's Kind is not Func. + Ins() iter.Seq[Type] + // Key returns a map type's key type. // It panics if the type's Kind is not Map. Key() Type @@ -233,6 +248,11 @@ type Type interface { // It panics if i is not in the range [0, NumOut()). Out(i int) Type + // Outs returns an iterator over each output parameter of function type t. The sequence + // is equivalent to calling Out successively for each index i in the range [0, NumOut()). + // It panics if the type's Kind is not Func. + Outs() iter.Seq[Type] + // OverflowComplex reports whether the complex128 x cannot be represented by type t. // It panics if t's Kind is not Complex64 or Complex128. OverflowComplex(x complex128) bool @@ -937,6 +957,55 @@ func canRangeFunc2(t *abi.Type) bool { return yield.InCount == 2 && yield.OutCount == 1 && yield.Out(0).Kind() == abi.Bool } +func (t *rtype) Fields() iter.Seq[StructField] { + if t.Kind() != Struct { + panic("reflect: Fields of non-struct type " + t.String()) + } + return func(yield func(StructField) bool) { + for i := range t.NumField() { + if !yield(t.Field(i)) { + return + } + } + } +} + +func (t *rtype) Methods() iter.Seq[Method] { + return func(yield func(Method) bool) { + for i := range t.NumMethod() { + if !yield(t.Method(i)) { + return + } + } + } +} + +func (t *rtype) Ins() iter.Seq[Type] { + if t.Kind() != Func { + panic("reflect: Ins of non-func type " + t.String()) + } + return func(yield func(Type) bool) { + for i := range t.NumIn() { + if !yield(t.In(i)) { + return + } + } + } +} + +func (t *rtype) Outs() iter.Seq[Type] { + if t.Kind() != Func { + panic("reflect: Outs of non-func type " + t.String()) + } + return func(yield func(Type) bool) { + for i := range t.NumOut() { + if !yield(t.Out(i)) { + return + } + } + } +} + // add returns p+x. // // The whySafe string is ignored, so that the function still inlines diff --git a/src/reflect/value.go b/src/reflect/value.go index a82d976c478..7f0ec2a397a 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -10,6 +10,7 @@ import ( "internal/goarch" "internal/strconv" "internal/unsafeheader" + "iter" "math" "runtime" "unsafe" @@ -2631,6 +2632,48 @@ func (v Value) UnsafePointer() unsafe.Pointer { panic(&ValueError{"reflect.Value.UnsafePointer", v.kind()}) } +// Fields returns an iterator over each [StructField] of v along with its [Value]. +// +// The sequence is equivalent to calling [Value.Field] successively +// for each index i in the range [0, NumField()). +// +// It panics if v's Kind is not Struct. +func (v Value) Fields() iter.Seq2[StructField, Value] { + t := v.Type() + if t.Kind() != Struct { + panic("reflect: Fields of non-struct type " + t.String()) + } + return func(yield func(StructField, Value) bool) { + for i := range v.NumField() { + if !yield(t.Field(i), v.Field(i)) { + return + } + } + } +} + +// Methods returns an iterator over each [Method] of v along with the corresponding +// method [Value]; this is a function with v bound as the receiver. As such, the +// receiver shouldn't be included in the arguments to [Value.Call]. +// +// The sequence is equivalent to calling [Value.Method] successively +// for each index i in the range [0, NumMethod()). +// +// Methods panics if v is a nil interface value. +// +// Calling this method will force the linker to retain all exported methods in all packages. +// This may make the executable binary larger but will not affect execution time. +func (v Value) Methods() iter.Seq2[Method, Value] { + return func(yield func(Method, Value) bool) { + rtype := v.Type() + for i := range v.NumMethod() { + if !yield(rtype.Method(i), v.Method(i)) { + return + } + } + } +} + // StringHeader is the runtime representation of a string. // It cannot be used safely or portably and its representation may // change in a later release. @@ -3232,8 +3275,8 @@ func (v Value) Comparable() bool { return v.IsNil() || v.Elem().Comparable() case Struct: - for i := 0; i < v.NumField(); i++ { - if !v.Field(i).Comparable() { + for _, value := range v.Fields() { + if !value.Comparable() { return false } } From a5ebc6b67c1e397ab74abadf20a7f290cf28949e Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Mon, 24 Nov 2025 20:46:14 +0100 Subject: [PATCH 022/140] crypto/ecdsa: clean up ECDSA parsing and serialization paths Check for invalid encodings and keys more systematically in ParseRawPrivateKey/PrivateKey.Bytes, ParseUncompressedPublicKey/PublicKey.Bytes, and fips140/ecdsa.NewPrivateKey/NewPublicKey. Also, use these functions throughout the codebase. This should not change any observable behavior, because there were multiple layers of checks and every path would hit at least one. Change-Id: I6a6a46566c95de871a5a37996835a0e51495f1d8 Reviewed-on: https://go-review.googlesource.com/c/go/+/724000 LUCI-TryBot-Result: Go LUCI Auto-Submit: Filippo Valsorda Reviewed-by: Roland Shoemaker Reviewed-by: Cherry Mui --- src/crypto/ecdsa/ecdsa.go | 62 +++++++++++----------- src/crypto/ecdsa/ecdsa_test.go | 3 +- src/crypto/internal/fips140/ecdsa/ecdsa.go | 14 +++++ src/crypto/tls/tls.go | 7 ++- src/crypto/x509/parser.go | 22 +++----- src/crypto/x509/sec1.go | 30 +++++------ src/crypto/x509/x509.go | 6 +-- 7 files changed, 72 insertions(+), 72 deletions(-) diff --git a/src/crypto/ecdsa/ecdsa.go b/src/crypto/ecdsa/ecdsa.go index 54fbecb5764..38f0b564ac9 100644 --- a/src/crypto/ecdsa/ecdsa.go +++ b/src/crypto/ecdsa/ecdsa.go @@ -60,15 +60,16 @@ type PublicKey struct { // ECDH returns k as a [ecdh.PublicKey]. It returns an error if the key is // invalid according to the definition of [ecdh.Curve.NewPublicKey], or if the // Curve is not supported by crypto/ecdh. -func (k *PublicKey) ECDH() (*ecdh.PublicKey, error) { - c := curveToECDH(k.Curve) +func (pub *PublicKey) ECDH() (*ecdh.PublicKey, error) { + c := curveToECDH(pub.Curve) if c == nil { return nil, errors.New("ecdsa: unsupported curve by crypto/ecdh") } - if !k.Curve.IsOnCurve(k.X, k.Y) { - return nil, errors.New("ecdsa: invalid public key") + k, err := pub.Bytes() + if err != nil { + return nil, err } - return c.NewPublicKey(elliptic.Marshal(k.Curve, k.X, k.Y)) + return c.NewPublicKey(k) } // Equal reports whether pub and x have the same value. @@ -181,16 +182,16 @@ type PrivateKey struct { // ECDH returns k as a [ecdh.PrivateKey]. It returns an error if the key is // invalid according to the definition of [ecdh.Curve.NewPrivateKey], or if the // Curve is not supported by [crypto/ecdh]. -func (k *PrivateKey) ECDH() (*ecdh.PrivateKey, error) { - c := curveToECDH(k.Curve) +func (priv *PrivateKey) ECDH() (*ecdh.PrivateKey, error) { + c := curveToECDH(priv.Curve) if c == nil { return nil, errors.New("ecdsa: unsupported curve by crypto/ecdh") } - size := (k.Curve.Params().N.BitLen() + 7) / 8 - if k.D.BitLen() > size*8 { - return nil, errors.New("ecdsa: invalid private key") + k, err := priv.Bytes() + if err != nil { + return nil, err } - return c.NewPrivateKey(k.D.FillBytes(make([]byte, size))) + return c.NewPrivateKey(k) } func curveToECDH(c elliptic.Curve) ecdh.Curve { @@ -367,10 +368,6 @@ func generateFIPS[P ecdsa.Point[P]](curve elliptic.Curve, c *ecdsa.Curve[P], ran return privateKeyFromFIPS(curve, privateKey) } -// errNoAsm is returned by signAsm and verifyAsm when the assembly -// implementation is not available. -var errNoAsm = errors.New("no assembly implementation available") - // SignASN1 signs a hash (which should be the result of hashing a larger message) // 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. It @@ -576,27 +573,30 @@ func privateKeyToFIPS[P ecdsa.Point[P]](c *ecdsa.Curve[P], priv *PrivateKey) (*e if err != nil { return nil, err } + + // Reject values that would not get correctly encoded. + if priv.D.BitLen() > priv.Curve.Params().N.BitLen() { + return nil, errors.New("ecdsa: private key scalar too large") + } + if priv.D.Sign() <= 0 { + return nil, errors.New("ecdsa: private key scalar is zero or negative") + } + + size := (priv.Curve.Params().N.BitLen() + 7) / 8 + const maxScalarSize = 66 // enough for a P-521 private key + if size > maxScalarSize { + return nil, errors.New("ecdsa: internal error: curve size too large") + } + D := priv.D.FillBytes(make([]byte, size, maxScalarSize)) + return privateKeyCache.Get(priv, func() (*ecdsa.PrivateKey, error) { - return ecdsa.NewPrivateKey(c, priv.D.Bytes(), Q) + return ecdsa.NewPrivateKey(c, D, Q) }, func(k *ecdsa.PrivateKey) bool { return subtle.ConstantTimeCompare(k.PublicKey().Bytes(), Q) == 1 && - leftPadBytesEqual(k.Bytes(), priv.D.Bytes()) + subtle.ConstantTimeCompare(k.Bytes(), D) == 1 }) } -func leftPadBytesEqual(a, b []byte) bool { - if len(a) < len(b) { - a, b = b, a - } - if len(a) > len(b) { - x := make([]byte, 0, 66 /* enough for a P-521 private key */) - x = append(x, make([]byte, len(a)-len(b))...) - x = append(x, b...) - b = x - } - return subtle.ConstantTimeCompare(a, b) == 1 -} - // pointFromAffine is used to convert the PublicKey to a nistec SetBytes input. func pointFromAffine(curve elliptic.Curve, x, y *big.Int) ([]byte, error) { bitSize := curve.Params().BitSize @@ -607,7 +607,7 @@ func pointFromAffine(curve elliptic.Curve, x, y *big.Int) ([]byte, error) { if x.BitLen() > bitSize || y.BitLen() > bitSize { return nil, errors.New("overflowing coordinate") } - // Encode the coordinates and let SetBytes reject invalid points. + // Encode the coordinates and let [ecdsa.NewPublicKey] reject invalid points. byteLen := (bitSize + 7) / 8 buf := make([]byte, 1+2*byteLen) buf[0] = 4 // uncompressed point diff --git a/src/crypto/ecdsa/ecdsa_test.go b/src/crypto/ecdsa/ecdsa_test.go index 87e74f2a0e9..9594dbd1cb6 100644 --- a/src/crypto/ecdsa/ecdsa_test.go +++ b/src/crypto/ecdsa/ecdsa_test.go @@ -694,7 +694,8 @@ func testInvalidPrivateKeys(t *testing.T, curve elliptic.Curve) { t.Errorf("ParseRawPrivateKey accepted short key") } - b = append(b, make([]byte, (curve.Params().BitSize+7)/8)...) + b = make([]byte, (curve.Params().BitSize+7)/8) + b = append(b, []byte{1, 2, 3}...) if _, err := ParseRawPrivateKey(curve, b); err == nil { t.Errorf("ParseRawPrivateKey accepted long key") } diff --git a/src/crypto/internal/fips140/ecdsa/ecdsa.go b/src/crypto/internal/fips140/ecdsa/ecdsa.go index 81179de4f4e..1c00f55810a 100644 --- a/src/crypto/internal/fips140/ecdsa/ecdsa.go +++ b/src/crypto/internal/fips140/ecdsa/ecdsa.go @@ -156,24 +156,38 @@ var p521Order = []byte{0x01, 0xff, 0x3b, 0xb5, 0xc9, 0xb8, 0x89, 0x9c, 0x47, 0xae, 0xbb, 0x6f, 0xb7, 0x1e, 0x91, 0x38, 0x64, 0x09} +// NewPrivateKey creates a new ECDSA private key from the given D and Q byte +// slices. D must be the fixed-length big-endian encoding of the private scalar, +// and Q must be the compressed or uncompressed encoding of the public point. func NewPrivateKey[P Point[P]](c *Curve[P], D, Q []byte) (*PrivateKey, error) { fips140.RecordApproved() pub, err := NewPublicKey(c, Q) if err != nil { return nil, err } + if len(D) != c.N.Size() { + return nil, errors.New("ecdsa: invalid private key length") + } d, err := bigmod.NewNat().SetBytes(D, c.N) if err != nil { return nil, err } + if d.IsZero() == 1 { + return nil, errors.New("ecdsa: private key is zero") + } priv := &PrivateKey{pub: *pub, d: d.Bytes(c.N)} return priv, nil } +// NewPublicKey creates a new ECDSA public key from the given Q byte slice. +// Q must be the compressed or uncompressed encoding of the public point. func NewPublicKey[P Point[P]](c *Curve[P], Q []byte) (*PublicKey, error) { // SetBytes checks that Q is a valid point on the curve, and that its // coordinates are reduced modulo p, fulfilling the requirements of SP // 800-89, Section 5.3.2. + if len(Q) < 1 || Q[0] == 0 { + return nil, errors.New("ecdsa: invalid public key encoding") + } _, err := c.newPoint().SetBytes(Q) if err != nil { return nil, err diff --git a/src/crypto/tls/tls.go b/src/crypto/tls/tls.go index a0fd8e835da..69f096870c9 100644 --- a/src/crypto/tls/tls.go +++ b/src/crypto/tls/tls.go @@ -24,7 +24,6 @@ package tls // https://www.imperialviolet.org/2013/02/04/luckythirteen.html. import ( - "bytes" "context" "crypto" "crypto/ecdsa" @@ -335,7 +334,7 @@ func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error) { if !ok { return fail(errors.New("tls: private key type does not match public key type")) } - if pub.N.Cmp(priv.N) != 0 { + if !priv.PublicKey.Equal(pub) { return fail(errors.New("tls: private key does not match public key")) } case *ecdsa.PublicKey: @@ -343,7 +342,7 @@ func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error) { if !ok { return fail(errors.New("tls: private key type does not match public key type")) } - if pub.X.Cmp(priv.X) != 0 || pub.Y.Cmp(priv.Y) != 0 { + if !priv.PublicKey.Equal(pub) { return fail(errors.New("tls: private key does not match public key")) } case ed25519.PublicKey: @@ -351,7 +350,7 @@ func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error) { if !ok { return fail(errors.New("tls: private key type does not match public key type")) } - if !bytes.Equal(priv.Public().(ed25519.PublicKey), pub) { + if !priv.Public().(ed25519.PublicKey).Equal(pub) { return fail(errors.New("tls: private key does not match public key")) } default: diff --git a/src/crypto/x509/parser.go b/src/crypto/x509/parser.go index 680dcee203a..e255f7d604a 100644 --- a/src/crypto/x509/parser.go +++ b/src/crypto/x509/parser.go @@ -10,7 +10,6 @@ import ( "crypto/ecdh" "crypto/ecdsa" "crypto/ed25519" - "crypto/elliptic" "crypto/rsa" "crypto/x509/pkix" "encoding/asn1" @@ -250,7 +249,7 @@ func parseExtension(der cryptobyte.String) (pkix.Extension, error) { func parsePublicKey(keyData *publicKeyInfo) (any, error) { oid := keyData.Algorithm.Algorithm params := keyData.Algorithm.Parameters - der := cryptobyte.String(keyData.PublicKey.RightAlign()) + data := keyData.PublicKey.RightAlign() switch { case oid.Equal(oidPublicKeyRSA): // RSA public keys must have a NULL in the parameters. @@ -259,6 +258,7 @@ func parsePublicKey(keyData *publicKeyInfo) (any, error) { return nil, errors.New("x509: RSA key missing NULL parameters") } + der := cryptobyte.String(data) p := &pkcs1PublicKey{N: new(big.Int)} if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("x509: invalid RSA public key") @@ -292,34 +292,26 @@ func parsePublicKey(keyData *publicKeyInfo) (any, error) { if namedCurve == nil { return nil, errors.New("x509: unsupported elliptic curve") } - x, y := elliptic.Unmarshal(namedCurve, der) - if x == nil { - return nil, errors.New("x509: failed to unmarshal elliptic curve point") - } - pub := &ecdsa.PublicKey{ - Curve: namedCurve, - X: x, - Y: y, - } - return pub, nil + return ecdsa.ParseUncompressedPublicKey(namedCurve, data) case oid.Equal(oidPublicKeyEd25519): // RFC 8410, Section 3 // > For all of the OIDs, the parameters MUST be absent. if len(params.FullBytes) != 0 { return nil, errors.New("x509: Ed25519 key encoded with illegal parameters") } - if len(der) != ed25519.PublicKeySize { + if len(data) != ed25519.PublicKeySize { return nil, errors.New("x509: wrong Ed25519 public key size") } - return ed25519.PublicKey(der), nil + return ed25519.PublicKey(data), nil case oid.Equal(oidPublicKeyX25519): // RFC 8410, Section 3 // > For all of the OIDs, the parameters MUST be absent. if len(params.FullBytes) != 0 { return nil, errors.New("x509: X25519 key encoded with illegal parameters") } - return ecdh.X25519().NewPublicKey(der) + return ecdh.X25519().NewPublicKey(data) case oid.Equal(oidPublicKeyDSA): + der := cryptobyte.String(data) y := new(big.Int) if !der.ReadASN1Integer(y) { return nil, errors.New("x509: invalid DSA public key") diff --git a/src/crypto/x509/sec1.go b/src/crypto/x509/sec1.go index 8e81bd41298..6212c2d4ff5 100644 --- a/src/crypto/x509/sec1.go +++ b/src/crypto/x509/sec1.go @@ -11,7 +11,6 @@ import ( "encoding/asn1" "errors" "fmt" - "math/big" ) const ecPrivKeyVersion = 1 @@ -55,15 +54,19 @@ func MarshalECPrivateKey(key *ecdsa.PrivateKey) ([]byte, error) { // marshalECPrivateKeyWithOID marshals an EC private key into ASN.1, DER format and // sets the curve ID to the given OID, or omits it if OID is nil. func marshalECPrivateKeyWithOID(key *ecdsa.PrivateKey, oid asn1.ObjectIdentifier) ([]byte, error) { - if !key.Curve.IsOnCurve(key.X, key.Y) { - return nil, errors.New("invalid elliptic key public key") + privateKey, err := key.Bytes() + if err != nil { + return nil, err + } + publicKey, err := key.PublicKey.Bytes() + if err != nil { + return nil, err } - privateKey := make([]byte, (key.Curve.Params().N.BitLen()+7)/8) return asn1.Marshal(ecPrivateKey{ Version: 1, - PrivateKey: key.D.FillBytes(privateKey), + PrivateKey: privateKey, NamedCurveOID: oid, - PublicKey: asn1.BitString{Bytes: elliptic.Marshal(key.Curve, key.X, key.Y)}, + PublicKey: asn1.BitString{Bytes: publicKey}, }) } @@ -106,16 +109,8 @@ func parseECPrivateKey(namedCurveOID *asn1.ObjectIdentifier, der []byte) (key *e return nil, errors.New("x509: unknown elliptic curve") } - k := new(big.Int).SetBytes(privKey.PrivateKey) - curveOrder := curve.Params().N - if k.Cmp(curveOrder) >= 0 { - return nil, errors.New("x509: invalid elliptic curve private key value") - } - priv := new(ecdsa.PrivateKey) - priv.Curve = curve - priv.D = k - - privateKey := make([]byte, (curveOrder.BitLen()+7)/8) + size := (curve.Params().N.BitLen() + 7) / 8 + privateKey := make([]byte, size) // Some private keys have leading zero padding. This is invalid // according to [SEC1], but this code will ignore it. @@ -130,7 +125,6 @@ func parseECPrivateKey(namedCurveOID *asn1.ObjectIdentifier, der []byte) (key *e // according to [SEC1] but since OpenSSL used to do this, we ignore // this too. copy(privateKey[len(privateKey)-len(privKey.PrivateKey):], privKey.PrivateKey) - priv.X, priv.Y = curve.ScalarBaseMult(privateKey) - return priv, nil + return ecdsa.ParseRawPrivateKey(curve, privateKey) } diff --git a/src/crypto/x509/x509.go b/src/crypto/x509/x509.go index afd3d8673a7..85e8fceedcb 100644 --- a/src/crypto/x509/x509.go +++ b/src/crypto/x509/x509.go @@ -101,10 +101,10 @@ func marshalPublicKey(pub any) (publicKeyBytes []byte, publicKeyAlgorithm pkix.A if !ok { return nil, pkix.AlgorithmIdentifier{}, errors.New("x509: unsupported elliptic curve") } - if !pub.Curve.IsOnCurve(pub.X, pub.Y) { - return nil, pkix.AlgorithmIdentifier{}, errors.New("x509: invalid elliptic curve public key") + publicKeyBytes, err = pub.Bytes() + if err != nil { + return nil, pkix.AlgorithmIdentifier{}, err } - publicKeyBytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y) publicKeyAlgorithm.Algorithm = oidPublicKeyECDSA var paramBytes []byte paramBytes, err = asn1.Marshal(oid) From e3088d6eb8ff0d63edc3452cbed827cb67231182 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Sat, 22 Nov 2025 23:03:14 +0100 Subject: [PATCH 023/140] crypto/hpke: expose crypto/internal/hpke Fixes #75300 Change-Id: I6a83e0d040dba3366819d2afff704f886a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/723560 Reviewed-by: Cherry Mui Reviewed-by: Daniel McCarney Auto-Submit: Filippo Valsorda TryBot-Bypass: Filippo Valsorda Reviewed-by: Roland Shoemaker --- api/next/75300.txt | 51 +++++++++++++++++++ doc/next/6-stdlib/50-hpke.md | 7 +++ .../6-stdlib/99-minor/crypto/hpke/75300.md | 1 + src/crypto/{internal => }/hpke/aead.go | 0 src/crypto/{internal => }/hpke/hpke.go | 20 ++++---- src/crypto/{internal => }/hpke/hpke_test.go | 0 src/crypto/{internal => }/hpke/kdf.go | 4 +- src/crypto/{internal => }/hpke/kem.go | 6 +-- src/crypto/{internal => }/hpke/pq.go | 6 +-- .../{internal => }/hpke/testdata/hpke-pq.json | 0 .../{internal => }/hpke/testdata/rfc9180.json | 0 src/crypto/tls/ech.go | 2 +- src/crypto/tls/handshake_client.go | 2 +- src/crypto/tls/handshake_server_tls13.go | 2 +- src/go/build/deps_test.go | 6 ++- 15 files changed, 84 insertions(+), 23 deletions(-) create mode 100644 doc/next/6-stdlib/50-hpke.md create mode 100644 doc/next/6-stdlib/99-minor/crypto/hpke/75300.md rename src/crypto/{internal => }/hpke/aead.go (100%) rename src/crypto/{internal => }/hpke/hpke.go (92%) rename src/crypto/{internal => }/hpke/hpke_test.go (100%) rename src/crypto/{internal => }/hpke/kdf.go (98%) rename src/crypto/{internal => }/hpke/kem.go (98%) rename src/crypto/{internal => }/hpke/pq.go (98%) rename src/crypto/{internal => }/hpke/testdata/hpke-pq.json (100%) rename src/crypto/{internal => }/hpke/testdata/rfc9180.json (100%) diff --git a/api/next/75300.txt b/api/next/75300.txt index da24eb4aa34..5e418e525c1 100644 --- a/api/next/75300.txt +++ b/api/next/75300.txt @@ -10,3 +10,54 @@ pkg crypto/ecdh, type KeyExchanger interface, ECDH(*PublicKey) ([]uint8, error) pkg crypto/ecdh, type KeyExchanger interface, PublicKey() *PublicKey #75300 pkg crypto/mlkem, method (*DecapsulationKey1024) Encapsulator() crypto.Encapsulator #75300 pkg crypto/mlkem, method (*DecapsulationKey768) Encapsulator() crypto.Encapsulator #75300 +pkg crypto/hpke, func AES128GCM() AEAD #75300 +pkg crypto/hpke, func AES256GCM() AEAD #75300 +pkg crypto/hpke, func ChaCha20Poly1305() AEAD #75300 +pkg crypto/hpke, func DHKEM(ecdh.Curve) KEM #75300 +pkg crypto/hpke, func ExportOnly() AEAD #75300 +pkg crypto/hpke, func HKDFSHA256() KDF #75300 +pkg crypto/hpke, func HKDFSHA384() KDF #75300 +pkg crypto/hpke, func HKDFSHA512() KDF #75300 +pkg crypto/hpke, func MLKEM1024() KEM #75300 +pkg crypto/hpke, func MLKEM1024P384() KEM #75300 +pkg crypto/hpke, func MLKEM768() KEM #75300 +pkg crypto/hpke, func MLKEM768P256() KEM #75300 +pkg crypto/hpke, func MLKEM768X25519() KEM #75300 +pkg crypto/hpke, func NewAEAD(uint16) (AEAD, error) #75300 +pkg crypto/hpke, func NewDHKEMPrivateKey(ecdh.KeyExchanger) (PrivateKey, error) #75300 +pkg crypto/hpke, func NewDHKEMPublicKey(*ecdh.PublicKey) (PublicKey, error) #75300 +pkg crypto/hpke, func NewHybridPrivateKey(crypto.Decapsulator, ecdh.KeyExchanger) (PrivateKey, error) #75300 +pkg crypto/hpke, func NewHybridPublicKey(crypto.Encapsulator, *ecdh.PublicKey) (PublicKey, error) #75300 +pkg crypto/hpke, func NewKDF(uint16) (KDF, error) #75300 +pkg crypto/hpke, func NewKEM(uint16) (KEM, error) #75300 +pkg crypto/hpke, func NewMLKEMPrivateKey(crypto.Decapsulator) (PrivateKey, error) #75300 +pkg crypto/hpke, func NewMLKEMPublicKey(crypto.Encapsulator) (PublicKey, error) #75300 +pkg crypto/hpke, func NewRecipient([]uint8, PrivateKey, KDF, AEAD, []uint8) (*Recipient, error) #75300 +pkg crypto/hpke, func NewSender(PublicKey, KDF, AEAD, []uint8) ([]uint8, *Sender, error) #75300 +pkg crypto/hpke, func Open(PrivateKey, KDF, AEAD, []uint8, []uint8) ([]uint8, error) #75300 +pkg crypto/hpke, func SHAKE128() KDF #75300 +pkg crypto/hpke, func SHAKE256() KDF #75300 +pkg crypto/hpke, func Seal(PublicKey, KDF, AEAD, []uint8, []uint8) ([]uint8, error) #75300 +pkg crypto/hpke, method (*Recipient) Export(string, int) ([]uint8, error) #75300 +pkg crypto/hpke, method (*Recipient) Open([]uint8, []uint8) ([]uint8, error) #75300 +pkg crypto/hpke, method (*Sender) Export(string, int) ([]uint8, error) #75300 +pkg crypto/hpke, method (*Sender) Seal([]uint8, []uint8) ([]uint8, error) #75300 +pkg crypto/hpke, type AEAD interface, ID() uint16 #75300 +pkg crypto/hpke, type AEAD interface, unexported methods #75300 +pkg crypto/hpke, type KDF interface, ID() uint16 #75300 +pkg crypto/hpke, type KDF interface, unexported methods #75300 +pkg crypto/hpke, type KEM interface, DeriveKeyPair([]uint8) (PrivateKey, error) #75300 +pkg crypto/hpke, type KEM interface, GenerateKey() (PrivateKey, error) #75300 +pkg crypto/hpke, type KEM interface, ID() uint16 #75300 +pkg crypto/hpke, type KEM interface, NewPrivateKey([]uint8) (PrivateKey, error) #75300 +pkg crypto/hpke, type KEM interface, NewPublicKey([]uint8) (PublicKey, error) #75300 +pkg crypto/hpke, type KEM interface, unexported methods #75300 +pkg crypto/hpke, type PrivateKey interface, Bytes() ([]uint8, error) #75300 +pkg crypto/hpke, type PrivateKey interface, KEM() KEM #75300 +pkg crypto/hpke, type PrivateKey interface, PublicKey() PublicKey #75300 +pkg crypto/hpke, type PrivateKey interface, unexported methods #75300 +pkg crypto/hpke, type PublicKey interface, Bytes() []uint8 #75300 +pkg crypto/hpke, type PublicKey interface, KEM() KEM #75300 +pkg crypto/hpke, type PublicKey interface, unexported methods #75300 +pkg crypto/hpke, type Recipient struct #75300 +pkg crypto/hpke, type Sender struct #75300 diff --git a/doc/next/6-stdlib/50-hpke.md b/doc/next/6-stdlib/50-hpke.md new file mode 100644 index 00000000000..ee621eee359 --- /dev/null +++ b/doc/next/6-stdlib/50-hpke.md @@ -0,0 +1,7 @@ +### crypto/hpke + +The new [crypto/hpke] package implements Hybrid Public Key Encryption +(HPKE) as specified in [RFC 9180], including support for post-quantum +hybrid KEMs. + +[RFC 9180]: https://rfc-editor.org/rfc/rfc9180.html diff --git a/doc/next/6-stdlib/99-minor/crypto/hpke/75300.md b/doc/next/6-stdlib/99-minor/crypto/hpke/75300.md new file mode 100644 index 00000000000..e769ca78022 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/hpke/75300.md @@ -0,0 +1 @@ + diff --git a/src/crypto/internal/hpke/aead.go b/src/crypto/hpke/aead.go similarity index 100% rename from src/crypto/internal/hpke/aead.go rename to src/crypto/hpke/aead.go diff --git a/src/crypto/internal/hpke/hpke.go b/src/crypto/hpke/hpke.go similarity index 92% rename from src/crypto/internal/hpke/hpke.go rename to src/crypto/hpke/hpke.go index d2df2484003..5ad3b8a4ca9 100644 --- a/src/crypto/internal/hpke/hpke.go +++ b/src/crypto/hpke/hpke.go @@ -10,8 +10,8 @@ package hpke import ( "crypto/cipher" - "encoding/binary" "errors" + "internal/byteorder" ) type context struct { @@ -45,14 +45,14 @@ func newContext(sharedSecret []byte, kemID uint16, kdf KDF, aead AEAD, info []by if kdf.oneStage() { secrets := make([]byte, 0, 2+2+len(sharedSecret)) - secrets = binary.BigEndian.AppendUint16(secrets, 0) // empty psk - secrets = binary.BigEndian.AppendUint16(secrets, uint16(len(sharedSecret))) + secrets = byteorder.BEAppendUint16(secrets, 0) // empty psk + secrets = byteorder.BEAppendUint16(secrets, uint16(len(sharedSecret))) secrets = append(secrets, sharedSecret...) ksContext := make([]byte, 0, 1+2+2+len(info)) - ksContext = append(ksContext, 0) // mode 0 - ksContext = binary.BigEndian.AppendUint16(ksContext, 0) // empty psk_id - ksContext = binary.BigEndian.AppendUint16(ksContext, uint16(len(info))) + ksContext = append(ksContext, 0) // mode 0 + ksContext = byteorder.BEAppendUint16(ksContext, 0) // empty psk_id + ksContext = byteorder.BEAppendUint16(ksContext, uint16(len(info))) ksContext = append(ksContext, info...) secret, err := kdf.labeledDerive(sid, secrets, "secret", ksContext, @@ -245,7 +245,7 @@ func (r *Recipient) Export(exporterContext string, length int) ([]byte, error) { func (ctx *context) nextNonce() []byte { nonce := make([]byte, ctx.aead.NonceSize()) - binary.BigEndian.PutUint64(nonce[len(nonce)-8:], ctx.seqNum) + byteorder.BEPutUint64(nonce[len(nonce)-8:], ctx.seqNum) for i := range ctx.baseNonce { nonce[i] ^= ctx.baseNonce[i] } @@ -255,8 +255,8 @@ func (ctx *context) nextNonce() []byte { func suiteID(kemID, kdfID, aeadID uint16) []byte { suiteID := make([]byte, 0, 4+2+2+2) suiteID = append(suiteID, []byte("HPKE")...) - suiteID = binary.BigEndian.AppendUint16(suiteID, kemID) - suiteID = binary.BigEndian.AppendUint16(suiteID, kdfID) - suiteID = binary.BigEndian.AppendUint16(suiteID, aeadID) + suiteID = byteorder.BEAppendUint16(suiteID, kemID) + suiteID = byteorder.BEAppendUint16(suiteID, kdfID) + suiteID = byteorder.BEAppendUint16(suiteID, aeadID) return suiteID } diff --git a/src/crypto/internal/hpke/hpke_test.go b/src/crypto/hpke/hpke_test.go similarity index 100% rename from src/crypto/internal/hpke/hpke_test.go rename to src/crypto/hpke/hpke_test.go diff --git a/src/crypto/internal/hpke/kdf.go b/src/crypto/hpke/kdf.go similarity index 98% rename from src/crypto/internal/hpke/kdf.go rename to src/crypto/hpke/kdf.go index 7862dd22a2a..23217a718dc 100644 --- a/src/crypto/internal/hpke/kdf.go +++ b/src/crypto/hpke/kdf.go @@ -9,10 +9,10 @@ import ( "crypto/sha256" "crypto/sha3" "crypto/sha512" - "encoding/binary" "errors" "fmt" "hash" + "internal/byteorder" ) // The KDF is one of the three components of an HPKE ciphersuite, implementing @@ -93,7 +93,7 @@ func (kdf *hkdfKDF) labeledExtract(suiteID []byte, salt []byte, label string, in func (kdf *hkdfKDF) labeledExpand(suiteID []byte, randomKey []byte, label string, info []byte, length uint16) ([]byte, error) { labeledInfo := make([]byte, 0, 2+7+len(suiteID)+len(label)+len(info)) - labeledInfo = binary.BigEndian.AppendUint16(labeledInfo, length) + labeledInfo = byteorder.BEAppendUint16(labeledInfo, length) labeledInfo = append(labeledInfo, []byte("HPKE-v1")...) labeledInfo = append(labeledInfo, suiteID...) labeledInfo = append(labeledInfo, label...) diff --git a/src/crypto/internal/hpke/kem.go b/src/crypto/hpke/kem.go similarity index 98% rename from src/crypto/internal/hpke/kem.go rename to src/crypto/hpke/kem.go index 4aa87fc0b44..c30f79bad49 100644 --- a/src/crypto/internal/hpke/kem.go +++ b/src/crypto/hpke/kem.go @@ -7,8 +7,8 @@ package hpke import ( "crypto/ecdh" "crypto/rand" - "encoding/binary" "errors" + "internal/byteorder" ) // A KEM is a Key Encapsulation Mechanism, one of the three components of an @@ -114,7 +114,7 @@ type dhKEM struct { } func (kem *dhKEM) extractAndExpand(dhKey, kemContext []byte) ([]byte, error) { - suiteID := binary.BigEndian.AppendUint16([]byte("KEM"), kem.id) + suiteID := byteorder.BEAppendUint16([]byte("KEM"), kem.id) eaePRK, err := kem.kdf.labeledExtract(suiteID, nil, "eae_prk", dhKey) if err != nil { return nil, err @@ -302,7 +302,7 @@ func (kem *dhKEM) NewPrivateKey(ikm []byte) (PrivateKey, error) { func (kem *dhKEM) DeriveKeyPair(ikm []byte) (PrivateKey, error) { // DeriveKeyPair from RFC 9180 Section 7.1.3. - suiteID := binary.BigEndian.AppendUint16([]byte("KEM"), kem.id) + suiteID := byteorder.BEAppendUint16([]byte("KEM"), kem.id) prk, err := kem.kdf.labeledExtract(suiteID, nil, "dkp_prk", ikm) if err != nil { return nil, err diff --git a/src/crypto/internal/hpke/pq.go b/src/crypto/hpke/pq.go similarity index 98% rename from src/crypto/internal/hpke/pq.go rename to src/crypto/hpke/pq.go index dfcd084ab71..322f937ae86 100644 --- a/src/crypto/internal/hpke/pq.go +++ b/src/crypto/hpke/pq.go @@ -11,8 +11,8 @@ import ( "crypto/mlkem" "crypto/rand" "crypto/sha3" - "encoding/binary" "errors" + "internal/byteorder" ) var mlkem768X25519 = &hybridKEM{ @@ -299,7 +299,7 @@ func newHybridPrivateKey(pq crypto.Decapsulator, t ecdh.KeyExchanger, seed []byt } func (kem *hybridKEM) DeriveKeyPair(ikm []byte) (PrivateKey, error) { - suiteID := binary.BigEndian.AppendUint16([]byte("KEM"), kem.id) + suiteID := byteorder.BEAppendUint16([]byte("KEM"), kem.id) dk, err := SHAKE256().labeledDerive(suiteID, ikm, "DeriveKeyPair", nil, 32) if err != nil { return nil, err @@ -496,7 +496,7 @@ func (kem *mlkemKEM) NewPrivateKey(priv []byte) (PrivateKey, error) { } func (kem *mlkemKEM) DeriveKeyPair(ikm []byte) (PrivateKey, error) { - suiteID := binary.BigEndian.AppendUint16([]byte("KEM"), kem.id) + suiteID := byteorder.BEAppendUint16([]byte("KEM"), kem.id) dk, err := SHAKE256().labeledDerive(suiteID, ikm, "DeriveKeyPair", nil, 64) if err != nil { return nil, err diff --git a/src/crypto/internal/hpke/testdata/hpke-pq.json b/src/crypto/hpke/testdata/hpke-pq.json similarity index 100% rename from src/crypto/internal/hpke/testdata/hpke-pq.json rename to src/crypto/hpke/testdata/hpke-pq.json diff --git a/src/crypto/internal/hpke/testdata/rfc9180.json b/src/crypto/hpke/testdata/rfc9180.json similarity index 100% rename from src/crypto/internal/hpke/testdata/rfc9180.json rename to src/crypto/hpke/testdata/rfc9180.json diff --git a/src/crypto/tls/ech.go b/src/crypto/tls/ech.go index fb02197e2ba..adeff9e0508 100644 --- a/src/crypto/tls/ech.go +++ b/src/crypto/tls/ech.go @@ -6,7 +6,7 @@ package tls import ( "bytes" - "crypto/internal/hpke" + "crypto/hpke" "errors" "fmt" "strings" diff --git a/src/crypto/tls/handshake_client.go b/src/crypto/tls/handshake_client.go index 47cf88323d8..c739544db67 100644 --- a/src/crypto/tls/handshake_client.go +++ b/src/crypto/tls/handshake_client.go @@ -10,9 +10,9 @@ import ( "crypto" "crypto/ecdsa" "crypto/ed25519" + "crypto/hpke" "crypto/internal/fips140/mlkem" "crypto/internal/fips140/tls13" - "crypto/internal/hpke" "crypto/rsa" "crypto/subtle" "crypto/tls/internal/fips140tls" diff --git a/src/crypto/tls/handshake_server_tls13.go b/src/crypto/tls/handshake_server_tls13.go index c5b0552cae6..c227371aceb 100644 --- a/src/crypto/tls/handshake_server_tls13.go +++ b/src/crypto/tls/handshake_server_tls13.go @@ -10,9 +10,9 @@ import ( "crypto" "crypto/hkdf" "crypto/hmac" + "crypto/hpke" "crypto/internal/fips140/mlkem" "crypto/internal/fips140/tls13" - "crypto/internal/hpke" "crypto/rsa" "crypto/tls/internal/fips140tls" "errors" diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 9a6b86b65c8..b02db785c13 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -604,9 +604,11 @@ var depsRules = ` < golang.org/x/crypto/internal/poly1305 < golang.org/x/crypto/chacha20poly1305; - CRYPTO-MATH, NET, container/list, encoding/hex, encoding/pem, + CRYPTO-MATH, golang.org/x/crypto/chacha20poly1305 + < crypto/hpke; + + CRYPTO-MATH, NET, container/list, encoding/hex, encoding/pem, crypto/hpke, golang.org/x/crypto/chacha20poly1305, crypto/tls/internal/fips140tls - < crypto/internal/hpke < crypto/x509/internal/macos < crypto/x509/pkix < crypto/x509 From bd9222b525b44d91941fc4d179b467103817d463 Mon Sep 17 00:00:00 2001 From: Tom Thorogood Date: Sun, 15 Dec 2024 12:08:43 +1030 Subject: [PATCH 024/140] crypto/sha3: reduce cSHAKE allocations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consider a hypothetical SumCSHAKE256 function: func SumCSHAKE256(N, S, data []byte, length int) []byte { out := make([]byte, 64) return sumCSHAKE256(out, N, S, data, length) } func sumCSHAKE256(out, N, S, data []byte, length int) []byte { if len(out) < length { out = make([]byte, length) } else { out = out[:length] } h := sha3.NewCSHAKE256(N, S) h.Write(data) h.Read(out) return out } Currently this has 4 allocations: - one for out (unless stack allocated), - one for the SHAKE result of crypto/internal/fips140/sha3.newCShake, - one for the initBlock allocation in crypto/internal/fips140/sha3.newCShake, - one for the result of crypto/internal/fips140/sha3.bytepad. We eliminate the SHAKE allocation by outlining the SHAKE allocation in crypto/internal/fips140/sha3.NewCSHAKE128 and NewCSHAKE256. As crypto/sha3.NewCSHAKE128 and NewCSHAKE256 immediately de-reference this result, this allocation is eliminated. We eliminate the bytepad allocation by instead writing the various values directly with SHAKE.Write. Values passed to Write don't escape and, with the exception of data (which is initBlock), all our Writes are of fixed size allocations. We can't simply modify bytepad to return a fixed size byte-slice as the length of data is not constant nor does it have a reasonable upper bound. We're stuck with the initBlock allocation because of the API (Reset and the various marshallers), but we still net a substantial improvement. benchstat output using the following benchmark: func BenchmarkSumCSHAKE256(b *testing.B) { N := []byte("N") S := []byte("S") data := []byte("testdata") b.SetBytes(64) for b.Loop() { SumCSHAKE256(N, S, data, 64) } } name old time/op new time/op delta SumCSHAKE256-12 1.09µs ±20% 0.79µs ± 1% -27.41% (p=0.000 n=10+9) name old speed new speed delta SumCSHAKE256-12 59.8MB/s ±18% 81.0MB/s ± 1% +35.33% (p=0.000 n=10+9) name old alloc/op new alloc/op delta SumCSHAKE256-12 536B ± 0% 88B ± 0% -83.58% (p=0.000 n=10+10) name old allocs/op new allocs/op delta SumCSHAKE256-12 4.00 ± 0% 2.00 ± 0% -50.00% (p=0.000 n=10+10) Updates #69982 Change-Id: If426ea8127c58f5ef062cf74712ec70fd26a7372 Reviewed-on: https://go-review.googlesource.com/c/go/+/636255 LUCI-TryBot-Result: Go LUCI Reviewed-by: Filippo Valsorda Reviewed-by: Cherry Mui Auto-Submit: Filippo Valsorda Reviewed-by: Roland Shoemaker --- src/crypto/internal/fips140/sha3/shake.go | 48 +++++++++++++++-------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/crypto/internal/fips140/sha3/shake.go b/src/crypto/internal/fips140/sha3/shake.go index fc5a60a1303..bbd062131e9 100644 --- a/src/crypto/internal/fips140/sha3/shake.go +++ b/src/crypto/internal/fips140/sha3/shake.go @@ -23,14 +23,14 @@ type SHAKE struct { initBlock []byte } -func bytepad(data []byte, rate int) []byte { - out := make([]byte, 0, 9+len(data)+rate-1) - out = append(out, leftEncode(uint64(rate))...) - out = append(out, data...) - if padlen := rate - len(out)%rate; padlen < rate { - out = append(out, make([]byte, padlen)...) +func bytepadWrite(c *SHAKE, data []byte, rate int) { + rateEnc := leftEncode(uint64(rate)) + c.Write(rateEnc) + c.Write(data) + if padlen := rate - (len(rateEnc)+len(data))%rate; padlen < rate { + const maxRate = rateK256 + c.Write(make([]byte, padlen, maxRate)) // explicit cap to allow stack allocation } - return out } func leftEncode(x uint64) []byte { @@ -47,14 +47,14 @@ func leftEncode(x uint64) []byte { return b } -func newCShake(N, S []byte, rate, outputLen int, dsbyte byte) *SHAKE { - c := &SHAKE{d: Digest{rate: rate, outputLen: outputLen, dsbyte: dsbyte}} +func newCShake(c *SHAKE, N, S []byte, rate, outputLen int, dsbyte byte) *SHAKE { + c.d = Digest{rate: rate, outputLen: outputLen, dsbyte: dsbyte} c.initBlock = make([]byte, 0, 9+len(N)+9+len(S)) // leftEncode returns max 9 bytes c.initBlock = append(c.initBlock, leftEncode(uint64(len(N))*8)...) c.initBlock = append(c.initBlock, N...) c.initBlock = append(c.initBlock, leftEncode(uint64(len(S))*8)...) c.initBlock = append(c.initBlock, S...) - c.Write(bytepad(c.initBlock, c.d.rate)) + bytepadWrite(c, c.initBlock, c.d.rate) return c } @@ -82,7 +82,7 @@ func (s *SHAKE) Read(out []byte) (n int, err error) { func (s *SHAKE) Reset() { s.d.Reset() if len(s.initBlock) != 0 { - s.Write(bytepad(s.initBlock, s.d.rate)) + bytepadWrite(s, s.initBlock, s.d.rate) } } @@ -132,10 +132,17 @@ func NewShake256() *SHAKE { // cSHAKE is desired. S is a customization byte string used for domain // separation. When N and S are both empty, this is equivalent to NewShake128. func NewCShake128(N, S []byte) *SHAKE { + // The actual logic is in a separate function to outline this allocation. + c := &SHAKE{} + return newCShake128(c, N, S) +} + +func newCShake128(c *SHAKE, N, S []byte) *SHAKE { if len(N) == 0 && len(S) == 0 { - return NewShake128() + *c = *NewShake128() + return c } - return newCShake(N, S, rateK256, 32, dsbyteCShake) + return newCShake(c, N, S, rateK256, 32, dsbyteCShake) } // NewCShake256 creates a new cSHAKE256 XOF. @@ -144,8 +151,15 @@ func NewCShake128(N, S []byte) *SHAKE { // cSHAKE is desired. S is a customization byte string used for domain // separation. When N and S are both empty, this is equivalent to NewShake256. func NewCShake256(N, S []byte) *SHAKE { - if len(N) == 0 && len(S) == 0 { - return NewShake256() - } - return newCShake(N, S, rateK512, 64, dsbyteCShake) + // The actual logic is in a separate function to outline this allocation. + c := &SHAKE{} + return newCShake256(c, N, S) +} + +func newCShake256(c *SHAKE, N, S []byte) *SHAKE { + if len(N) == 0 && len(S) == 0 { + *c = *NewShake256() + return c + } + return newCShake(c, N, S, rateK512, 64, dsbyteCShake) } From 657b331ff5da9b02bd98b489ff144a03c6651bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Joaqu=C3=ADn=20Atria?= Date: Mon, 24 Nov 2025 19:52:22 +0000 Subject: [PATCH 025/140] net/url: fix example of Values.Encode Calling url.Values.Encode generates a query string with the values sorted by key. However, in the example in the documentation this behaviour is not reflected. This change corrects this. Change-Id: Id95a5d79b57dc20c3bff1f0c6975c76dcd8412b1 Reviewed-on: https://go-review.googlesource.com/c/go/+/723960 LUCI-TryBot-Result: Go LUCI Reviewed-by: Damien Neil Auto-Submit: Damien Neil Reviewed-by: Sean Liao Reviewed-by: Florian Lehner Reviewed-by: Cherry Mui Auto-Submit: Sean Liao --- src/net/url/example_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/net/url/example_test.go b/src/net/url/example_test.go index 311ba5c329b..bae83ab1d05 100644 --- a/src/net/url/example_test.go +++ b/src/net/url/example_test.go @@ -58,11 +58,12 @@ func ExampleValues() { v.Add("friend", "Jess") v.Add("friend", "Sarah") v.Add("friend", "Zoe") - // v.Encode() == "name=Ava&friend=Jess&friend=Sarah&friend=Zoe" + fmt.Println(v.Encode()) fmt.Println(v.Get("name")) fmt.Println(v.Get("friend")) fmt.Println(v["friend"]) // Output: + // friend=Jess&friend=Sarah&friend=Zoe&name=Ava // Ava // Jess // [Jess Sarah Zoe] From 6e4a0d8e44c845251c01fee3923113e6ba8d1e06 Mon Sep 17 00:00:00 2001 From: kmvijay Date: Thu, 30 Oct 2025 14:50:14 +0000 Subject: [PATCH 026/140] crypto/internal/fips140/bigmod: vector implementation of addMulVVWx on s390x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit addMulVVWx assembly routine is used to multiply bignum multiplicand with a 64-bit multiplier. The new implementation for s390x architecture uses an algorithm based on vector instructions, with a significant performance improvement. Note: z13 is the minimum architecture for Go, which already has VX support. The performance improvement is as below: goos: linux goarch: s390x pkg: crypto/internal/fips140/bigmod Orig.txt Vector_Patch.txt sec/op sec/op vs base ModAdd 164.1n ± 0% 159.7n ± 0% -2.7% (p=0.000 n=10) ModSub 152.3n ± 1% 147.3n ± 0% -3.25 (p=0.000 n=10) MontgomeryRepr 4.806µ ± 3% 1.829µ ± 0% -61.94% (p=0.000 n=10) MontgomeryMul 4.812µ ± 5% 1.834µ ± 0% -61.90% (p=0.000 n=10) ModMul 9.646µ ± 3% 3.698µ ± 0% -61.67% (p=0.000 n=10) ExpBig 11.28m ± 0% 11.28m ± 0% +0.04 (p=0.035 n=10) Exp 12.284m ± 5% 5.004m ± 1% -59.26 (p=0.000 n=10) geomean 18.61µ 10.74µ -42.2 Change-Id: I679944c9dac9f43f1626b018f72efa6da0d2442d Cq-Include-Trybots: luci.golang.try:gotip-linux-s390x Reviewed-on: https://go-review.googlesource.com/c/go/+/716480 Auto-Submit: Filippo Valsorda Reviewed-by: Vishwanatha HD Reviewed-by: Cherry Mui Reviewed-by: Roland Shoemaker Reviewed-by: Filippo Valsorda Reviewed-by: Srinivas Pokala LUCI-TryBot-Result: Go LUCI --- .../internal/fips140/bigmod/nat_s390x.s | 185 ++++++++++++------ 1 file changed, 130 insertions(+), 55 deletions(-) diff --git a/src/crypto/internal/fips140/bigmod/nat_s390x.s b/src/crypto/internal/fips140/bigmod/nat_s390x.s index 0c07a0c8a6d..9adeb9981dd 100644 --- a/src/crypto/internal/fips140/bigmod/nat_s390x.s +++ b/src/crypto/internal/fips140/bigmod/nat_s390x.s @@ -4,82 +4,157 @@ //go:build !purego +// Register usage (z13 convention): +// R2 = rp (result pointer) +// R3 = ap (source pointer) +// R4 = an / idx (loop counter) +// R5 = b0 (multiplier limb) +// R6 = cy (carry) + #include "textflag.h" // func addMulVVW1024(z, x *uint, y uint) (c uint) TEXT ·addMulVVW1024(SB), $0-32 - MOVD $16, R5 - JMP addMulVVWx(SB) + MOVD $16, R4 + JMP addMulVVWx(SB) // func addMulVVW1536(z, x *uint, y uint) (c uint) TEXT ·addMulVVW1536(SB), $0-32 - MOVD $24, R5 - JMP addMulVVWx(SB) + MOVD $24, R4 + JMP addMulVVWx(SB) // func addMulVVW2048(z, x *uint, y uint) (c uint) TEXT ·addMulVVW2048(SB), $0-32 - MOVD $32, R5 - JMP addMulVVWx(SB) + MOVD $32, R4 + JMP addMulVVWx(SB) TEXT addMulVVWx(SB), NOFRAME|NOSPLIT, $0 MOVD z+0(FP), R2 - MOVD x+8(FP), R8 - MOVD y+16(FP), R9 + MOVD x+8(FP), R3 + MOVD y+16(FP), R5 - MOVD $0, R1 // i*8 = 0 - MOVD $0, R7 // i = 0 - MOVD $0, R0 // make sure it's zero - MOVD $0, R4 // c = 0 + MOVD $0, R6 - MOVD R5, R12 - AND $-2, R12 - CMPBGE R5, $2, A6 - BR E6 +L_ent: + VZERO V0 + VZERO V2 + SRD $2, R4, R10 + TMLL R4, $1 + BRC $8, L_bx0 -A6: - MOVD (R8)(R1*1), R6 - MULHDU R9, R6 - MOVD (R2)(R1*1), R10 - ADDC R10, R11 // add to low order bits - ADDE R0, R6 - ADDC R4, R11 - ADDE R0, R6 - MOVD R6, R4 - MOVD R11, (R2)(R1*1) +L_bx1: + VLEG $1, 0(R2), V2 + VZERO V4 + TMLL R4, $2 + BRC $7, L_b11 - MOVD (8)(R8)(R1*1), R6 - MULHDU R9, R6 - MOVD (8)(R2)(R1*1), R10 - ADDC R10, R11 // add to low order bits - ADDE R0, R6 - ADDC R4, R11 - ADDE R0, R6 - MOVD R6, R4 - MOVD R11, (8)(R2)(R1*1) +L_b01: + MOVD $-24, R4 + MOVD R6, R0 + MOVD 0(R3), R7 + MLGR R5, R6 + ADDC R0, R7 + MOVD $0, R0 + ADDE R0, R6 + VLVGG $1, R7, V4 + VAQ V2, V4, V2 + VSTEG $1, V2, 0(R2) + VMRHG V2, V2, V2 + CMPBEQ R10, $0, L_1 + BR L_cj0 - ADD $16, R1 // i*8 + 8 - ADD $2, R7 // i++ +L_b11: + MOVD $-8, R4 + MOVD 0(R3), R9 + MLGR R5, R8 + ADDC R6, R9 + MOVD $0, R6 + ADDE R6, R8 + VLVGG $1, R9, V4 + VAQ V2, V4, V2 + VSTEG $1, V2, 0(R2) + VMRHG V2, V2, V2 + BR L_cj1 - CMPBLT R7, R12, A6 - BR E6 +L_bx0: + TMLL R4, $2 + BRC $7, L_b10 -L6: - // TODO: drop unused single-step loop. - MOVD (R8)(R1*1), R6 - MULHDU R9, R6 - MOVD (R2)(R1*1), R10 - ADDC R10, R11 // add to low order bits - ADDE R0, R6 - ADDC R4, R11 - ADDE R0, R6 - MOVD R6, R4 - MOVD R11, (R2)(R1*1) +L_b00: + MOVD $-32, R4 - ADD $8, R1 // i*8 + 8 - ADD $1, R7 // i++ +L_cj0: + MOVD 32(R3)(R4), R1 + MOVD 40(R3)(R4), R9 + MLGR R5, R0 + MLGR R5, R8 + VL 32(R4)(R2), V1 + VPDI $4, V1, V1, V1 + VLVGP R0, R1, V6 + VLVGP R9, R6, V7 + BR L_mid -E6: - CMPBLT R7, R5, L6 // i < n +L_b10: + MOVD $-16, R4 + MOVD R6, R8 - MOVD R4, c+24(FP) +L_cj1: + MOVD 16(R4)(R3), R1 + MOVD 24(R4)(R3), R7 + MLGR R5, R0 + MLGR R5, R6 + VL 16(R4)(R2), V1 + VPDI $4, V1, V1, V1 + VLVGP R0, R1, V6 + VLVGP R7, R8, V7 + CMPBEQ R10, $0, L_end + +L_top: + MOVD 32(R4)(R3), R1 + MOVD 40(R4)(R3), R9 + MLGR R5, R0 + MLGR R5, R8 + VACQ V6, V1, V0, V5 + VACCCQ V6, V1, V0, V0 + VACQ V5, V7, V2, V3 + VACCCQ V5, V7, V2, V2 + VPDI $4, V3, V3, V3 + VL 32(R4)(R2), V1 + VPDI $4, V1, V1, V1 + VST V3, 16(R4)(R2) + VLVGP R0, R1, V6 + VLVGP R9, R6, V7 + +L_mid: + MOVD 48(R4)(R3), R1 + MOVD 56(R4)(R3), R7 + MLGR R5, R0 + MLGR R5, R6 + VACQ V6, V1, V0, V5 + VACCCQ V6, V1, V0, V0 + VACQ V5, V7, V2, V3 + VACCCQ V5, V7, V2, V2 + VPDI $4, V3, V3, V3 + VL 48(R4)(R2), V1 + VPDI $4, V1, V1, V1 + VST V3, 32(R4)(R2) + VLVGP R0, R1, V6 + VLVGP R7, R8, V7 + MOVD $32(R4), R4 + BRCTG R10, L_top + +L_end: + VACQ V6, V1, V0, V5 + VACCCQ V6, V1, V0, V0 + VACQ V5, V7, V2, V3 + VACCCQ V5, V7, V2, V2 + VPDI $4, V3, V3, V3 + VST V3, 16(R2)(R4) + VAG V0, V2, V2 + +L_1: + VLGVG $1, V2, R2 + ADDC R6, R2 + MOVD R2, c+24(FP) RET + From 1cc1337f0a24e281bd278d100349d273ea0bcf0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E7=8E=AE=E6=96=87?= Date: Fri, 21 Nov 2025 23:26:50 +0800 Subject: [PATCH 027/140] internal/runtime/cgroup: allow more tests to run on all OSes Move non-Linux specific part out of _linux.go. The parsing code itself doesn't depend anything Linux specific, even if it the format is Linux specific. This should benefit developers working on non-Linux OSes. Change-Id: I1692978d583c3dd9a57ff269c97e8fca53a7a057 Reviewed-on: https://go-review.googlesource.com/c/go/+/723240 Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Pratt Reviewed-by: Carrillo Rodriguez --- src/internal/runtime/cgroup/cgroup.go | 418 ++++++++++++++++++ src/internal/runtime/cgroup/cgroup_linux.go | 410 ----------------- .../{cgroup_linux_test.go => cgroup_test.go} | 0 .../runtime/cgroup/export_linux_test.go | 15 - src/internal/runtime/cgroup/export_test.go | 10 + 5 files changed, 428 insertions(+), 425 deletions(-) create mode 100644 src/internal/runtime/cgroup/cgroup.go rename src/internal/runtime/cgroup/{cgroup_linux_test.go => cgroup_test.go} (100%) delete mode 100644 src/internal/runtime/cgroup/export_linux_test.go diff --git a/src/internal/runtime/cgroup/cgroup.go b/src/internal/runtime/cgroup/cgroup.go new file mode 100644 index 00000000000..68c31fcbc3b --- /dev/null +++ b/src/internal/runtime/cgroup/cgroup.go @@ -0,0 +1,418 @@ +// Copyright 2025 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 cgroup + +import ( + "internal/bytealg" + "internal/strconv" +) + +var ( + ErrNoCgroup error = stringError("not in a cgroup") + + errMalformedFile error = stringError("malformed file") +) + +const _PATH_MAX = 4096 + +const ( + // Required amount of scratch space for CPULimit. + // + // TODO(prattmic): This is shockingly large (~70KiB) due to the (very + // unlikely) combination of extremely long paths consisting mostly + // escaped characters. The scratch buffer ends up in .bss in package + // runtime, so it doesn't contribute to binary size and generally won't + // be faulted in, but it would still be nice to shrink this. A more + // complex parser that did not need to keep entire lines in memory + // could get away with much less. Alternatively, we could do a one-off + // mmap allocation for this buffer, which is only mapped larger if we + // actually need the extra space. + ScratchSize = PathSize + ParseSize + + // Required space to store a path of the cgroup in the filesystem. + PathSize = _PATH_MAX + + // /proc/self/mountinfo path escape sequences are 4 characters long, so + // a path consisting entirely of escaped characters could be 4 times + // larger. + escapedPathMax = 4 * _PATH_MAX + + // Required space to parse /proc/self/mountinfo and /proc/self/cgroup. + // See findCPUMount and findCPURelativePath. + ParseSize = 4 * escapedPathMax +) + +// Version indicates the cgroup version. +type Version int + +const ( + VersionUnknown Version = iota + V1 + V2 +) + +func parseV1Number(buf []byte) (int64, error) { + // Ignore trailing newline. + i := bytealg.IndexByte(buf, '\n') + if i < 0 { + return 0, errMalformedFile + } + buf = buf[:i] + + val, err := strconv.ParseInt(string(buf), 10, 64) + if err != nil { + return 0, errMalformedFile + } + + return val, nil +} + +func parseV2Limit(buf []byte) (float64, bool, error) { + i := bytealg.IndexByte(buf, ' ') + if i < 0 { + return 0, false, errMalformedFile + } + + quotaStr := buf[:i] + if bytealg.Compare(quotaStr, []byte("max")) == 0 { + // No limit. + return 0, false, nil + } + + periodStr := buf[i+1:] + // Ignore trailing newline, if any. + i = bytealg.IndexByte(periodStr, '\n') + if i < 0 { + return 0, false, errMalformedFile + } + periodStr = periodStr[:i] + + quota, err := strconv.ParseInt(string(quotaStr), 10, 64) + if err != nil { + return 0, false, errMalformedFile + } + + period, err := strconv.ParseInt(string(periodStr), 10, 64) + if err != nil { + return 0, false, errMalformedFile + } + + return float64(quota) / float64(period), true, nil +} + +// Finds the path of the current process's CPU cgroup relative to the cgroup +// mount and writes it to out. +// +// Returns the number of bytes written and the cgroup version (1 or 2). +func parseCPURelativePath(fd int, read func(fd int, b []byte) (int, uintptr), out []byte, scratch []byte) (int, Version, error) { + // The format of each line is + // + // hierarchy-ID:controller-list:cgroup-path + // + // controller-list is comma-separated. + // See man 5 cgroup for more details. + // + // cgroup v2 has hierarchy-ID 0. If a v1 hierarchy contains "cpu", that + // is the CPU controller. Otherwise the v2 hierarchy (if any) is the + // CPU controller. + // + // hierarchy-ID and controller-list have relatively small maximum + // sizes, and the path can be up to _PATH_MAX, so we need a bit more + // than 1 _PATH_MAX of scratch space. + + l := newLineReader(fd, scratch, read) + + // Bytes written to out. + n := 0 + + for { + err := l.next() + if err == errIncompleteLine { + // Don't allow incomplete lines. While in theory the + // incomplete line may be for a controller we don't + // care about, in practice all lines should be of + // similar length, so we should just have a buffer big + // enough for any. + return 0, 0, err + } else if err == errEOF { + break + } else if err != nil { + return 0, 0, err + } + + line := l.line() + + // The format of each line is + // + // hierarchy-ID:controller-list:cgroup-path + // + // controller-list is comma-separated. + // See man 5 cgroup for more details. + i := bytealg.IndexByte(line, ':') + if i < 0 { + return 0, 0, errMalformedFile + } + + hierarchy := line[:i] + line = line[i+1:] + + i = bytealg.IndexByte(line, ':') + if i < 0 { + return 0, 0, errMalformedFile + } + + controllers := line[:i] + line = line[i+1:] + + path := line + + if string(hierarchy) == "0" { + // v2 hierarchy. + n = copy(out, path) + // Keep searching, we might find a v1 hierarchy with a + // CPU controller, which takes precedence. + } else { + // v1 hierarchy + if containsCPU(controllers) { + // Found a v1 CPU controller. This must be the + // only one, so we're done. + return copy(out, path), V1, nil + } + } + } + + if n == 0 { + // Found nothing. + return 0, 0, ErrNoCgroup + } + + // Must be v2, v1 returns above. + return n, V2, nil +} + +// Returns true if comma-separated list b contains "cpu". +func containsCPU(b []byte) bool { + for len(b) > 0 { + i := bytealg.IndexByte(b, ',') + if i < 0 { + // Neither cmd/compile nor gccgo allocates for these string conversions. + return string(b) == "cpu" + } + + curr := b[:i] + rest := b[i+1:] + + if string(curr) == "cpu" { + return true + } + + b = rest + } + + return false +} + +// Returns the mount point for the cpu cgroup controller (v1 or v2) from +// /proc/self/mountinfo. +func parseCPUMount(fd int, read func(fd int, b []byte) (int, uintptr), out []byte, scratch []byte) (int, error) { + // The format of each line is: + // + // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue + // (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) + // + // (1) mount ID: unique identifier of the mount (may be reused after umount) + // (2) parent ID: ID of parent (or of self for the top of the mount tree) + // (3) major:minor: value of st_dev for files on filesystem + // (4) root: root of the mount within the filesystem + // (5) mount point: mount point relative to the process's root + // (6) mount options: per mount options + // (7) optional fields: zero or more fields of the form "tag[:value]" + // (8) separator: marks the end of the optional fields + // (9) filesystem type: name of filesystem of the form "type[.subtype]" + // (10) mount source: filesystem specific information or "none" + // (11) super options: per super block options + // + // See man 5 proc_pid_mountinfo for more details. + // + // Note that emitted paths will not contain space, tab, newline, or + // carriage return. Those are escaped. See Linux show_mountinfo -> + // show_path. We must unescape before returning. + // + // We return the mount point (5) if the filesystem type (9) is cgroup2, + // or cgroup with "cpu" in the super options (11). + // + // (4), (5), and (10) are up to _PATH_MAX. The remaining fields have a + // small fixed maximum size, so 4*_PATH_MAX is plenty of scratch space. + // Note that non-cgroup mounts may have arbitrarily long (11), but we + // can skip those when parsing. + + l := newLineReader(fd, scratch, read) + + // Bytes written to out. + n := 0 + + for { + //incomplete := false + err := l.next() + if err == errIncompleteLine { + // An incomplete line is fine as long as it doesn't + // impede parsing the fields we need. It shouldn't be + // possible for any mount to use more than 3*PATH_MAX + // before (9) because there are two paths and all other + // earlier fields have bounded options. Only (11) has + // unbounded options. + } else if err == errEOF { + break + } else if err != nil { + return 0, err + } + + line := l.line() + + // Skip first four fields. + for range 4 { + i := bytealg.IndexByte(line, ' ') + if i < 0 { + return 0, errMalformedFile + } + line = line[i+1:] + } + + // (5) mount point: mount point relative to the process's root + i := bytealg.IndexByte(line, ' ') + if i < 0 { + return 0, errMalformedFile + } + mnt := line[:i] + line = line[i+1:] + + // Skip ahead past optional fields, delimited by " - ". + for { + i = bytealg.IndexByte(line, ' ') + if i < 0 { + return 0, errMalformedFile + } + if i+3 >= len(line) { + return 0, errMalformedFile + } + delim := line[i : i+3] + if string(delim) == " - " { + line = line[i+3:] + break + } + line = line[i+1:] + } + + // (9) filesystem type: name of filesystem of the form "type[.subtype]" + i = bytealg.IndexByte(line, ' ') + if i < 0 { + return 0, errMalformedFile + } + ftype := line[:i] + line = line[i+1:] + + if string(ftype) != "cgroup" && string(ftype) != "cgroup2" { + continue + } + + // As in findCPUPath, cgroup v1 with a CPU controller takes + // precendence over cgroup v2. + if string(ftype) == "cgroup2" { + // v2 hierarchy. + n, err = unescapePath(out, mnt) + if err != nil { + // Don't keep searching on error. The kernel + // should never produce broken escaping. + return n, err + } + // Keep searching, we might find a v1 hierarchy with a + // CPU controller, which takes precedence. + continue + } + + // (10) mount source: filesystem specific information or "none" + i = bytealg.IndexByte(line, ' ') + if i < 0 { + return 0, errMalformedFile + } + // Don't care about mount source. + line = line[i+1:] + + // (11) super options: per super block options + superOpt := line + + // v1 hierarchy + if containsCPU(superOpt) { + // Found a v1 CPU controller. This must be the + // only one, so we're done. + return unescapePath(out, mnt) + } + } + + if n == 0 { + // Found nothing. + return 0, ErrNoCgroup + } + + return n, nil +} + +var errInvalidEscape error = stringError("invalid path escape sequence") + +// unescapePath copies in to out, unescaping escape sequences generated by +// Linux's show_path. +// +// That is, '\', ' ', '\t', and '\n' are converted to octal escape sequences, +// like '\040' for space. +// +// out must be at least as large as in. +// +// Returns the number of bytes written to out. +// +// Also see escapePath in cgroup_linux_test.go. +func unescapePath(out []byte, in []byte) (int, error) { + // Not strictly necessary, but simplifies the implementation and will + // always hold in users. + if len(out) < len(in) { + throw("output too small") + } + + var outi, ini int + for ini < len(in) { + c := in[ini] + if c != '\\' { + out[outi] = c + outi++ + ini++ + continue + } + + // Start of escape sequence. + + // Escape sequence is always 4 characters: one slash and three + // digits. + if ini+3 >= len(in) { + return outi, errInvalidEscape + } + + var outc byte + for i := range 3 { + c := in[ini+1+i] + if c < '0' || c > '9' { + return outi, errInvalidEscape + } + + outc *= 8 + outc += c - '0' + } + + out[outi] = outc + outi++ + + ini += 4 + } + + return outi, nil +} diff --git a/src/internal/runtime/cgroup/cgroup_linux.go b/src/internal/runtime/cgroup/cgroup_linux.go index 7b35a9bc187..5e3ee0d2c2c 100644 --- a/src/internal/runtime/cgroup/cgroup_linux.go +++ b/src/internal/runtime/cgroup/cgroup_linux.go @@ -5,44 +5,7 @@ package cgroup import ( - "internal/bytealg" "internal/runtime/syscall/linux" - "internal/strconv" -) - -var ( - ErrNoCgroup error = stringError("not in a cgroup") - - errMalformedFile error = stringError("malformed file") -) - -const _PATH_MAX = 4096 - -const ( - // Required amount of scratch space for CPULimit. - // - // TODO(prattmic): This is shockingly large (~70KiB) due to the (very - // unlikely) combination of extremely long paths consisting mostly - // escaped characters. The scratch buffer ends up in .bss in package - // runtime, so it doesn't contribute to binary size and generally won't - // be faulted in, but it would still be nice to shrink this. A more - // complex parser that did not need to keep entire lines in memory - // could get away with much less. Alternatively, we could do a one-off - // mmap allocation for this buffer, which is only mapped larger if we - // actually need the extra space. - ScratchSize = PathSize + ParseSize - - // Required space to store a path of the cgroup in the filesystem. - PathSize = _PATH_MAX - - // /proc/self/mountinfo path escape sequences are 4 characters long, so - // a path consisting entirely of escaped characters could be 4 times - // larger. - escapedPathMax = 4 * _PATH_MAX - - // Required space to parse /proc/self/mountinfo and /proc/self/cgroup. - // See findCPUMount and findCPURelativePath. - ParseSize = 4 * escapedPathMax ) // Include explicit NUL to be sure we include it in the slice. @@ -52,15 +15,6 @@ const ( v1PeriodFile = "/cpu.cfs_period_us\x00" ) -// Version indicates the cgroup version. -type Version int - -const ( - VersionUnknown Version = iota - V1 - V2 -) - // CPU owns the FDs required to read the CPU limit from a cgroup. type CPU struct { version Version @@ -212,22 +166,6 @@ func readV1Number(fd int) (int64, error) { return parseV1Number(buf) } -func parseV1Number(buf []byte) (int64, error) { - // Ignore trailing newline. - i := bytealg.IndexByte(buf, '\n') - if i < 0 { - return 0, errMalformedFile - } - buf = buf[:i] - - val, err := strconv.ParseInt(string(buf), 10, 64) - if err != nil { - return 0, errMalformedFile - } - - return val, nil -} - // Returns CPU throughput limit, or ok false if there is no limit. func readV2Limit(fd int) (float64, bool, error) { // The format of the file is " \n" where quota and @@ -260,39 +198,6 @@ func readV2Limit(fd int) (float64, bool, error) { return parseV2Limit(buf) } -func parseV2Limit(buf []byte) (float64, bool, error) { - i := bytealg.IndexByte(buf, ' ') - if i < 0 { - return 0, false, errMalformedFile - } - - quotaStr := buf[:i] - if bytealg.Compare(quotaStr, []byte("max")) == 0 { - // No limit. - return 0, false, nil - } - - periodStr := buf[i+1:] - // Ignore trailing newline, if any. - i = bytealg.IndexByte(periodStr, '\n') - if i < 0 { - return 0, false, errMalformedFile - } - periodStr = periodStr[:i] - - quota, err := strconv.ParseInt(string(quotaStr), 10, 64) - if err != nil { - return 0, false, errMalformedFile - } - - period, err := strconv.ParseInt(string(periodStr), 10, 64) - if err != nil { - return 0, false, errMalformedFile - } - - return float64(quota) / float64(period), true, nil -} - // FindCPU finds the path to the CPU cgroup that this process is a member of // and places it in out. scratch is a scratch buffer for internal use. // @@ -364,118 +269,6 @@ func FindCPURelativePath(out []byte, scratch []byte) (int, Version, error) { return n, version, nil } -// Finds the path of the current process's CPU cgroup relative to the cgroup -// mount and writes it to out. -// -// Returns the number of bytes written and the cgroup version (1 or 2). -func parseCPURelativePath(fd int, read func(fd int, b []byte) (int, uintptr), out []byte, scratch []byte) (int, Version, error) { - // The format of each line is - // - // hierarchy-ID:controller-list:cgroup-path - // - // controller-list is comma-separated. - // See man 5 cgroup for more details. - // - // cgroup v2 has hierarchy-ID 0. If a v1 hierarchy contains "cpu", that - // is the CPU controller. Otherwise the v2 hierarchy (if any) is the - // CPU controller. - // - // hierarchy-ID and controller-list have relatively small maximum - // sizes, and the path can be up to _PATH_MAX, so we need a bit more - // than 1 _PATH_MAX of scratch space. - - l := newLineReader(fd, scratch, read) - - // Bytes written to out. - n := 0 - - for { - err := l.next() - if err == errIncompleteLine { - // Don't allow incomplete lines. While in theory the - // incomplete line may be for a controller we don't - // care about, in practice all lines should be of - // similar length, so we should just have a buffer big - // enough for any. - return 0, 0, err - } else if err == errEOF { - break - } else if err != nil { - return 0, 0, err - } - - line := l.line() - - // The format of each line is - // - // hierarchy-ID:controller-list:cgroup-path - // - // controller-list is comma-separated. - // See man 5 cgroup for more details. - i := bytealg.IndexByte(line, ':') - if i < 0 { - return 0, 0, errMalformedFile - } - - hierarchy := line[:i] - line = line[i+1:] - - i = bytealg.IndexByte(line, ':') - if i < 0 { - return 0, 0, errMalformedFile - } - - controllers := line[:i] - line = line[i+1:] - - path := line - - if string(hierarchy) == "0" { - // v2 hierarchy. - n = copy(out, path) - // Keep searching, we might find a v1 hierarchy with a - // CPU controller, which takes precedence. - } else { - // v1 hierarchy - if containsCPU(controllers) { - // Found a v1 CPU controller. This must be the - // only one, so we're done. - return copy(out, path), V1, nil - } - } - } - - if n == 0 { - // Found nothing. - return 0, 0, ErrNoCgroup - } - - // Must be v2, v1 returns above. - return n, V2, nil -} - -// Returns true if comma-separated list b contains "cpu". -func containsCPU(b []byte) bool { - for len(b) > 0 { - i := bytealg.IndexByte(b, ',') - if i < 0 { - // Neither cmd/compile nor gccgo allocates for these string conversions. - return string(b) == "cpu" - } - - curr := b[:i] - rest := b[i+1:] - - if string(curr) == "cpu" { - return true - } - - b = rest - } - - return false -} - // FindCPUMountPoint finds the root of the CPU cgroup mount places it in out. // scratch is a scratch buffer for internal use. // @@ -505,206 +298,3 @@ func FindCPUMountPoint(out []byte, scratch []byte) (int, error) { return n, nil } - -// Returns the mount point for the cpu cgroup controller (v1 or v2) from -// /proc/self/mountinfo. -func parseCPUMount(fd int, read func(fd int, b []byte) (int, uintptr), out []byte, scratch []byte) (int, error) { - // The format of each line is: - // - // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue - // (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) - // - // (1) mount ID: unique identifier of the mount (may be reused after umount) - // (2) parent ID: ID of parent (or of self for the top of the mount tree) - // (3) major:minor: value of st_dev for files on filesystem - // (4) root: root of the mount within the filesystem - // (5) mount point: mount point relative to the process's root - // (6) mount options: per mount options - // (7) optional fields: zero or more fields of the form "tag[:value]" - // (8) separator: marks the end of the optional fields - // (9) filesystem type: name of filesystem of the form "type[.subtype]" - // (10) mount source: filesystem specific information or "none" - // (11) super options: per super block options - // - // See man 5 proc_pid_mountinfo for more details. - // - // Note that emitted paths will not contain space, tab, newline, or - // carriage return. Those are escaped. See Linux show_mountinfo -> - // show_path. We must unescape before returning. - // - // We return the mount point (5) if the filesystem type (9) is cgroup2, - // or cgroup with "cpu" in the super options (11). - // - // (4), (5), and (10) are up to _PATH_MAX. The remaining fields have a - // small fixed maximum size, so 4*_PATH_MAX is plenty of scratch space. - // Note that non-cgroup mounts may have arbitrarily long (11), but we - // can skip those when parsing. - - l := newLineReader(fd, scratch, read) - - // Bytes written to out. - n := 0 - - for { - //incomplete := false - err := l.next() - if err == errIncompleteLine { - // An incomplete line is fine as long as it doesn't - // impede parsing the fields we need. It shouldn't be - // possible for any mount to use more than 3*PATH_MAX - // before (9) because there are two paths and all other - // earlier fields have bounded options. Only (11) has - // unbounded options. - } else if err == errEOF { - break - } else if err != nil { - return 0, err - } - - line := l.line() - - // Skip first four fields. - for range 4 { - i := bytealg.IndexByte(line, ' ') - if i < 0 { - return 0, errMalformedFile - } - line = line[i+1:] - } - - // (5) mount point: mount point relative to the process's root - i := bytealg.IndexByte(line, ' ') - if i < 0 { - return 0, errMalformedFile - } - mnt := line[:i] - line = line[i+1:] - - // Skip ahead past optional fields, delimited by " - ". - for { - i = bytealg.IndexByte(line, ' ') - if i < 0 { - return 0, errMalformedFile - } - if i+3 >= len(line) { - return 0, errMalformedFile - } - delim := line[i : i+3] - if string(delim) == " - " { - line = line[i+3:] - break - } - line = line[i+1:] - } - - // (9) filesystem type: name of filesystem of the form "type[.subtype]" - i = bytealg.IndexByte(line, ' ') - if i < 0 { - return 0, errMalformedFile - } - ftype := line[:i] - line = line[i+1:] - - if string(ftype) != "cgroup" && string(ftype) != "cgroup2" { - continue - } - - // As in findCPUPath, cgroup v1 with a CPU controller takes - // precendence over cgroup v2. - if string(ftype) == "cgroup2" { - // v2 hierarchy. - n, err = unescapePath(out, mnt) - if err != nil { - // Don't keep searching on error. The kernel - // should never produce broken escaping. - return n, err - } - // Keep searching, we might find a v1 hierarchy with a - // CPU controller, which takes precedence. - continue - } - - // (10) mount source: filesystem specific information or "none" - i = bytealg.IndexByte(line, ' ') - if i < 0 { - return 0, errMalformedFile - } - // Don't care about mount source. - line = line[i+1:] - - // (11) super options: per super block options - superOpt := line - - // v1 hierarchy - if containsCPU(superOpt) { - // Found a v1 CPU controller. This must be the - // only one, so we're done. - return unescapePath(out, mnt) - } - } - - if n == 0 { - // Found nothing. - return 0, ErrNoCgroup - } - - return n, nil -} - -var errInvalidEscape error = stringError("invalid path escape sequence") - -// unescapePath copies in to out, unescaping escape sequences generated by -// Linux's show_path. -// -// That is, '\', ' ', '\t', and '\n' are converted to octal escape sequences, -// like '\040' for space. -// -// out must be at least as large as in. -// -// Returns the number of bytes written to out. -// -// Also see escapePath in cgroup_linux_test.go. -func unescapePath(out []byte, in []byte) (int, error) { - // Not strictly necessary, but simplifies the implementation and will - // always hold in users. - if len(out) < len(in) { - throw("output too small") - } - - var outi, ini int - for ini < len(in) { - c := in[ini] - if c != '\\' { - out[outi] = c - outi++ - ini++ - continue - } - - // Start of escape sequence. - - // Escape sequence is always 4 characters: one slash and three - // digits. - if ini+3 >= len(in) { - return outi, errInvalidEscape - } - - var outc byte - for i := range 3 { - c := in[ini+1+i] - if c < '0' || c > '9' { - return outi, errInvalidEscape - } - - outc *= 8 - outc += c - '0' - } - - out[outi] = outc - outi++ - - ini += 4 - } - - return outi, nil -} diff --git a/src/internal/runtime/cgroup/cgroup_linux_test.go b/src/internal/runtime/cgroup/cgroup_test.go similarity index 100% rename from src/internal/runtime/cgroup/cgroup_linux_test.go rename to src/internal/runtime/cgroup/cgroup_test.go diff --git a/src/internal/runtime/cgroup/export_linux_test.go b/src/internal/runtime/cgroup/export_linux_test.go deleted file mode 100644 index 653fcd1b2fd..00000000000 --- a/src/internal/runtime/cgroup/export_linux_test.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2025 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 cgroup - -var ContainsCPU = containsCPU - -var ParseV1Number = parseV1Number -var ParseV2Limit = parseV2Limit - -var ParseCPURelativePath = parseCPURelativePath -var ParseCPUMount = parseCPUMount - -var UnescapePath = unescapePath diff --git a/src/internal/runtime/cgroup/export_test.go b/src/internal/runtime/cgroup/export_test.go index 200e5aee121..55acdc0877e 100644 --- a/src/internal/runtime/cgroup/export_test.go +++ b/src/internal/runtime/cgroup/export_test.go @@ -22,3 +22,13 @@ var ( ErrEOF = errEOF ErrIncompleteLine = errIncompleteLine ) + +var ContainsCPU = containsCPU + +var ParseV1Number = parseV1Number +var ParseV2Limit = parseV2Limit + +var ParseCPURelativePath = parseCPURelativePath +var ParseCPUMount = parseCPUMount + +var UnescapePath = unescapePath From 5b34354bd3961460163f111b27e5c220e53a1cff Mon Sep 17 00:00:00 2001 From: Neal Patel Date: Tue, 14 Oct 2025 15:31:44 -0400 Subject: [PATCH 028/140] crypto/internal/fips140/sha256: interleave scheduling and rounds for 11.2% speed-up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit goos: linux goarch: amd64 pkg: crypto/sha256 cpu: AMD EPYC 7B13 │ before │ after │ │ sec/op │ sec/op vs base │ Hash8Bytes/New 384.4n ± 2% 347.2n ± 0% -9.68% (p=0.001 n=7) Hash8Bytes/New-2 386.3n ± 2% 348.3n ± 1% -9.84% (p=0.001 n=7) Hash8Bytes/New-4 386.7n ± 1% 347.9n ± 1% -10.03% (p=0.001 n=7) Hash8Bytes/Sum224 406.9n ± 2% 358.8n ± 3% -11.82% (p=0.001 n=7) Hash8Bytes/Sum224-2 404.1n ± 1% 359.8n ± 1% -10.96% (p=0.001 n=7) Hash8Bytes/Sum224-4 409.5n ± 1% 360.9n ± 1% -11.87% (p=0.001 n=7) Hash8Bytes/Sum256 401.3n ± 1% 352.4n ± 0% -12.19% (p=0.001 n=7) Hash8Bytes/Sum256-2 402.0n ± 1% 354.2n ± 1% -11.89% (p=0.001 n=7) Hash8Bytes/Sum256-4 403.7n ± 1% 353.5n ± 1% -12.43% (p=0.001 n=7) Hash1K/New 5.836µ ± 1% 5.180µ ± 2% -11.24% (p=0.001 n=7) Hash1K/New-2 5.855µ ± 1% 5.177µ ± 5% -11.58% (p=0.001 n=7) Hash1K/New-4 5.878µ ± 0% 5.215µ ± 3% -11.28% (p=0.001 n=7) Hash1K/Sum224 5.860µ ± 1% 5.225µ ± 1% -10.84% (p=0.001 n=7) Hash1K/Sum224-2 5.852µ ± 1% 5.198µ ± 1% -11.18% (p=0.001 n=7) Hash1K/Sum224-4 5.867µ ± 1% 5.226µ ± 4% -10.93% (p=0.001 n=7) Hash1K/Sum256 5.851µ ± 0% 5.246µ ± 1% -10.34% (p=0.001 n=7) Hash1K/Sum256-2 5.863µ ± 1% 5.237µ ± 2% -10.68% (p=0.001 n=7) Hash1K/Sum256-4 5.873µ ± 1% 5.191µ ± 1% -11.61% (p=0.001 n=7) Hash8K/New 44.06µ ± 0% 38.93µ ± 1% -11.63% (p=0.001 n=7) Hash8K/New-2 44.23µ ± 0% 39.14µ ± 1% -11.50% (p=0.001 n=7) Hash8K/New-4 44.25µ ± 1% 39.04µ ± 1% -11.77% (p=0.001 n=7) Hash8K/Sum224 43.98µ ± 1% 40.47µ ± 2% -7.98% (p=0.001 n=7) Hash8K/Sum224-2 44.31µ ± 1% 39.54µ ± 3% -10.76% (p=0.001 n=7) Hash8K/Sum224-4 44.45µ ± 1% 39.04µ ± 2% -12.16% (p=0.001 n=7) Hash8K/Sum256 43.95µ ± 0% 39.23µ ± 0% -10.75% (p=0.001 n=7) Hash8K/Sum256-2 44.19µ ± 1% 39.39µ ± 2% -10.87% (p=0.001 n=7) Hash8K/Sum256-4 44.19µ ± 1% 39.27µ ± 1% -11.13% (p=0.001 n=7) Hash256K/New 1.397m ± 1% 1.238m ± 1% -11.39% (p=0.001 n=7) Hash256K/New-2 1.404m ± 1% 1.242m ± 1% -11.53% (p=0.001 n=7) Hash256K/New-4 1.402m ± 1% 1.243m ± 1% -11.31% (p=0.001 n=7) Hash256K/Sum224 1.398m ± 0% 1.237m ± 1% -11.48% (p=0.001 n=7) Hash256K/Sum224-2 1.402m ± 1% 1.239m ± 1% -11.59% (p=0.001 n=7) Hash256K/Sum224-4 1.409m ± 1% 1.245m ± 1% -11.61% (p=0.001 n=7) Hash256K/Sum256 1.402m ± 1% 1.242m ± 1% -11.38% (p=0.001 n=7) Hash256K/Sum256-2 1.397m ± 1% 1.240m ± 1% -11.22% (p=0.001 n=7) Hash256K/Sum256-4 1.404m ± 1% 1.250m ± 1% -10.97% (p=0.001 n=7) Hash1M/New 5.584m ± 2% 4.944m ± 1% -11.46% (p=0.001 n=7) Hash1M/New-2 5.609m ± 1% 4.974m ± 1% -11.33% (p=0.001 n=7) Hash1M/New-4 5.625m ± 2% 4.984m ± 2% -11.40% (p=0.001 n=7) Hash1M/Sum224 5.578m ± 0% 4.949m ± 0% -11.28% (p=0.001 n=7) Hash1M/Sum224-2 5.603m ± 1% 4.985m ± 1% -11.02% (p=0.001 n=7) Hash1M/Sum224-4 5.619m ± 1% 4.976m ± 1% -11.44% (p=0.001 n=7) Hash1M/Sum256 5.589m ± 1% 4.940m ± 0% -11.61% (p=0.001 n=7) Hash1M/Sum256-2 5.581m ± 1% 4.981m ± 1% -10.75% (p=0.001 n=7) Hash1M/Sum256-4 5.618m ± 3% 4.966m ± 1% -11.59% (p=0.001 n=7) geomean 60.48µ 53.71µ -11.19% │ before │ after │ │ B/s │ B/s vs base │ Hash8Bytes/New 19.85Mi ± 2% 21.97Mi ± 0% +10.72% (p=0.001 n=7) Hash8Bytes/New-2 19.75Mi ± 2% 21.91Mi ± 1% +10.91% (p=0.001 n=7) Hash8Bytes/New-4 19.73Mi ± 1% 21.93Mi ± 1% +11.16% (p=0.001 n=7) Hash8Bytes/Sum224 18.75Mi ± 2% 21.27Mi ± 3% +13.43% (p=0.001 n=7) Hash8Bytes/Sum224-2 18.88Mi ± 1% 21.20Mi ± 1% +12.27% (p=0.001 n=7) Hash8Bytes/Sum224-4 18.63Mi ± 1% 21.14Mi ± 1% +13.46% (p=0.001 n=7) Hash8Bytes/Sum256 19.01Mi ± 1% 21.65Mi ± 0% +13.90% (p=0.001 n=7) Hash8Bytes/Sum256-2 18.98Mi ± 1% 21.54Mi ± 1% +13.52% (p=0.001 n=7) Hash8Bytes/Sum256-4 18.90Mi ± 1% 21.58Mi ± 1% +14.18% (p=0.001 n=7) Hash1K/New 167.4Mi ± 1% 188.5Mi ± 2% +12.65% (p=0.001 n=7) Hash1K/New-2 166.8Mi ± 1% 188.6Mi ± 5% +13.11% (p=0.001 n=7) Hash1K/New-4 166.1Mi ± 0% 187.3Mi ± 3% +12.71% (p=0.001 n=7) Hash1K/Sum224 166.7Mi ± 1% 186.9Mi ± 1% +12.14% (p=0.001 n=7) Hash1K/Sum224-2 166.9Mi ± 1% 187.9Mi ± 1% +12.59% (p=0.001 n=7) Hash1K/Sum224-4 166.5Mi ± 1% 186.9Mi ± 4% +12.27% (p=0.001 n=7) Hash1K/Sum256 166.9Mi ± 0% 186.1Mi ± 1% +11.51% (p=0.001 n=7) Hash1K/Sum256-2 166.6Mi ± 1% 186.5Mi ± 2% +11.94% (p=0.001 n=7) Hash1K/Sum256-4 166.3Mi ± 1% 188.1Mi ± 1% +13.15% (p=0.001 n=7) Hash8K/New 177.3Mi ± 0% 200.7Mi ± 1% +13.17% (p=0.001 n=7) Hash8K/New-2 176.6Mi ± 0% 199.6Mi ± 1% +13.00% (p=0.001 n=7) Hash8K/New-4 176.6Mi ± 1% 200.1Mi ± 1% +13.34% (p=0.001 n=7) Hash8K/Sum224 177.6Mi ± 1% 193.0Mi ± 2% +8.67% (p=0.001 n=7) Hash8K/Sum224-2 176.3Mi ± 1% 197.6Mi ± 3% +12.06% (p=0.001 n=7) Hash8K/Sum224-4 175.8Mi ± 1% 200.1Mi ± 2% +13.84% (p=0.001 n=7) Hash8K/Sum256 177.8Mi ± 0% 199.2Mi ± 0% +12.04% (p=0.001 n=7) Hash8K/Sum256-2 176.8Mi ± 1% 198.4Mi ± 2% +12.20% (p=0.001 n=7) Hash8K/Sum256-4 176.8Mi ± 1% 198.9Mi ± 1% +12.52% (p=0.001 n=7) Hash256K/New 179.0Mi ± 1% 202.0Mi ± 1% +12.86% (p=0.001 n=7) Hash256K/New-2 178.1Mi ± 1% 201.3Mi ± 1% +13.03% (p=0.001 n=7) Hash256K/New-4 178.4Mi ± 1% 201.1Mi ± 1% +12.76% (p=0.001 n=7) Hash256K/Sum224 178.8Mi ± 0% 202.0Mi ± 1% +12.97% (p=0.001 n=7) Hash256K/Sum224-2 178.3Mi ± 1% 201.7Mi ± 1% +13.11% (p=0.001 n=7) Hash256K/Sum224-4 177.5Mi ± 1% 200.8Mi ± 1% +13.13% (p=0.001 n=7) Hash256K/Sum256 178.3Mi ± 1% 201.2Mi ± 1% +12.83% (p=0.001 n=7) Hash256K/Sum256-2 179.0Mi ± 1% 201.6Mi ± 1% +12.64% (p=0.001 n=7) Hash256K/Sum256-4 178.0Mi ± 1% 200.0Mi ± 1% +12.33% (p=0.001 n=7) Hash1M/New 179.1Mi ± 2% 202.3Mi ± 1% +12.94% (p=0.001 n=7) Hash1M/New-2 178.3Mi ± 1% 201.1Mi ± 1% +12.78% (p=0.001 n=7) Hash1M/New-4 177.8Mi ± 2% 200.6Mi ± 2% +12.87% (p=0.001 n=7) Hash1M/Sum224 179.3Mi ± 0% 202.1Mi ± 0% +12.71% (p=0.001 n=7) Hash1M/Sum224-2 178.5Mi ± 1% 200.6Mi ± 1% +12.39% (p=0.001 n=7) Hash1M/Sum224-4 178.0Mi ± 1% 201.0Mi ± 1% +12.92% (p=0.001 n=7) Hash1M/Sum256 178.9Mi ± 1% 202.4Mi ± 0% +13.13% (p=0.001 n=7) Hash1M/Sum256-2 179.2Mi ± 1% 200.8Mi ± 1% +12.04% (p=0.001 n=7) Hash1M/Sum256-4 178.0Mi ± 3% 201.3Mi ± 1% +13.12% (p=0.001 n=7) geomean 112.5Mi 126.6Mi +12.60% │ before │ after │ │ B/op │ B/op vs base │ Hash8Bytes/New 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/New-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/New-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum224 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum224-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum224-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum256 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum256-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum256-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/New 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/New-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/New-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum224 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum224-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum224-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum256 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum256-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum256-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/New 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/New-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/New-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum224 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum224-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum224-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum256 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum256-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum256-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/New 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/New-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/New-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/Sum224 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/Sum224-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/Sum224-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/Sum256 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/Sum256-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/Sum256-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/New 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/New-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/New-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/Sum224 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/Sum224-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/Sum224-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/Sum256 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/Sum256-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/Sum256-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean │ before │ after │ │ allocs/op │ allocs/op vs base │ Hash8Bytes/New 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/New-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/New-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum224 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum224-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum224-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum256 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum256-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum256-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/New 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/New-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/New-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum224 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum224-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum224-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum256 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum256-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum256-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/New 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/New-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/New-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum224 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum224-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum224-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum256 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum256-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum256-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/New 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/New-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/New-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/Sum224 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/Sum224-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/Sum224-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/Sum256 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/Sum256-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash256K/Sum256-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/New 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/New-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/New-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/Sum224 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/Sum224-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/Sum224-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/Sum256 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/Sum256-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1M/Sum256-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean Change-Id: I705f024221690532b2e891ab8e508d07eefe295b Reviewed-on: https://go-review.googlesource.com/c/go/+/711843 Reviewed-by: Filippo Valsorda Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI --- .../internal/fips140/sha256/sha256block.go | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/crypto/internal/fips140/sha256/sha256block.go b/src/crypto/internal/fips140/sha256/sha256block.go index 55a400e2502..c764b54829c 100644 --- a/src/crypto/internal/fips140/sha256/sha256block.go +++ b/src/crypto/internal/fips140/sha256/sha256block.go @@ -81,23 +81,20 @@ func blockGeneric(dig *Digest, p []byte) { var w [64]uint32 h0, h1, h2, h3, h4, h5, h6, h7 := dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7] for len(p) >= chunk { - // Can interlace the computation of w with the - // rounds below if needed for speed. - for i := 0; i < 16; i++ { - j := i * 4 - w[i] = uint32(p[j])<<24 | uint32(p[j+1])<<16 | uint32(p[j+2])<<8 | uint32(p[j+3]) - } - for i := 16; i < 64; i++ { - v1 := w[i-2] - t1 := (bits.RotateLeft32(v1, -17)) ^ (bits.RotateLeft32(v1, -19)) ^ (v1 >> 10) - v2 := w[i-15] - t2 := (bits.RotateLeft32(v2, -7)) ^ (bits.RotateLeft32(v2, -18)) ^ (v2 >> 3) - w[i] = t1 + w[i-7] + t2 + w[i-16] - } - a, b, c, d, e, f, g, h := h0, h1, h2, h3, h4, h5, h6, h7 - for i := 0; i < 64; i++ { + for i := range 64 { + if i < 16 { + j := i * 4 + w[i] = uint32(p[j])<<24 | uint32(p[j+1])<<16 | uint32(p[j+2])<<8 | uint32(p[j+3]) + } else { + v1 := w[i-2] + t1 := (bits.RotateLeft32(v1, -17)) ^ (bits.RotateLeft32(v1, -19)) ^ (v1 >> 10) + v2 := w[i-15] + t2 := (bits.RotateLeft32(v2, -7)) ^ (bits.RotateLeft32(v2, -18)) ^ (v2 >> 3) + w[i] = t1 + w[i-7] + t2 + w[i-16] + } + t1 := h + ((bits.RotateLeft32(e, -6)) ^ (bits.RotateLeft32(e, -11)) ^ (bits.RotateLeft32(e, -25))) + ((e & f) ^ (^e & g)) + _K[i] + w[i] t2 := ((bits.RotateLeft32(a, -2)) ^ (bits.RotateLeft32(a, -13)) ^ (bits.RotateLeft32(a, -22))) + ((a & b) ^ (a & c) ^ (b & c)) From 2c7c62b97235c376205653200c2bd14ac03baa41 Mon Sep 17 00:00:00 2001 From: Neal Patel Date: Tue, 14 Oct 2025 16:14:00 -0400 Subject: [PATCH 029/140] crypto/internal/fips140/sha512: interleave scheduling with rounds for 10.3% speed-up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit goos: linux goarch: amd64 pkg: crypto/sha512 cpu: AMD EPYC 7B13 │ before │ after │ │ sec/op │ sec/op vs base │ Hash8Bytes/New 486.7n ± 1% 440.3n ± 0% -9.53% (p=0.001 n=7) Hash8Bytes/New-2 487.3n ± 1% 442.6n ± 1% -9.17% (p=0.001 n=7) Hash8Bytes/New-4 488.0n ± 1% 442.3n ± 0% -9.36% (p=0.001 n=7) Hash8Bytes/Sum384 495.1n ± 0% 451.2n ± 1% -8.87% (p=0.001 n=7) Hash8Bytes/Sum384-2 495.0n ± 1% 450.8n ± 1% -8.93% (p=0.001 n=7) Hash8Bytes/Sum384-4 500.2n ± 2% 453.6n ± 2% -9.32% (p=0.001 n=7) Hash8Bytes/Sum512 497.3n ± 1% 452.1n ± 0% -9.09% (p=0.001 n=7) Hash8Bytes/Sum512-2 496.0n ± 1% 453.5n ± 0% -8.57% (p=0.001 n=7) Hash8Bytes/Sum512-4 498.5n ± 0% 452.3n ± 1% -9.27% (p=0.001 n=7) Hash1K/New 3.985µ ± 1% 3.543µ ± 1% -11.09% (p=0.001 n=7) Hash1K/New-2 4.004µ ± 2% 3.558µ ± 1% -11.14% (p=0.001 n=7) Hash1K/New-4 3.997µ ± 0% 3.563µ ± 1% -10.86% (p=0.001 n=7) Hash1K/Sum384 3.996µ ± 1% 3.560µ ± 1% -10.91% (p=0.001 n=7) Hash1K/Sum384-2 4.011µ ± 1% 3.576µ ± 1% -10.85% (p=0.001 n=7) Hash1K/Sum384-4 4.004µ ± 0% 3.564µ ± 0% -10.99% (p=0.001 n=7) Hash1K/Sum512 3.998µ ± 1% 3.555µ ± 1% -11.08% (p=0.001 n=7) Hash1K/Sum512-2 3.996µ ± 0% 3.560µ ± 1% -10.91% (p=0.001 n=7) Hash1K/Sum512-4 4.004µ ± 1% 3.573µ ± 1% -10.76% (p=0.001 n=7) Hash8K/New 28.34µ ± 1% 25.29µ ± 1% -10.77% (p=0.001 n=7) Hash8K/New-2 28.45µ ± 1% 25.48µ ± 1% -10.44% (p=0.001 n=7) Hash8K/New-4 28.42µ ± 1% 25.37µ ± 1% -10.71% (p=0.001 n=7) Hash8K/Sum384 28.38µ ± 0% 25.18µ ± 0% -11.28% (p=0.001 n=7) Hash8K/Sum384-2 28.50µ ± 1% 25.35µ ± 1% -11.07% (p=0.001 n=7) Hash8K/Sum384-4 28.51µ ± 1% 25.35µ ± 0% -11.07% (p=0.001 n=7) Hash8K/Sum512 28.34µ ± 1% 25.23µ ± 0% -10.96% (p=0.001 n=7) Hash8K/Sum512-2 28.33µ ± 1% 25.23µ ± 1% -10.95% (p=0.001 n=7) Hash8K/Sum512-4 28.48µ ± 1% 25.31µ ± 1% -11.13% (p=0.001 n=7) geomean 3.828µ 3.433µ -10.34% │ before │ after │ │ B/s │ B/s vs base │ Hash8Bytes/New 15.68Mi ± 1% 17.33Mi ± 0% +10.52% (p=0.001 n=7) Hash8Bytes/New-2 15.66Mi ± 1% 17.23Mi ± 1% +10.05% (p=0.001 n=7) Hash8Bytes/New-4 15.63Mi ± 0% 17.25Mi ± 0% +10.37% (p=0.001 n=7) Hash8Bytes/Sum384 15.41Mi ± 0% 16.91Mi ± 1% +9.72% (p=0.001 n=7) Hash8Bytes/Sum384-2 15.41Mi ± 1% 16.93Mi ± 1% +9.84% (p=0.001 n=7) Hash8Bytes/Sum384-4 15.25Mi ± 2% 16.82Mi ± 2% +10.32% (p=0.001 n=7) Hash8Bytes/Sum512 15.34Mi ± 1% 16.87Mi ± 0% +9.94% (p=0.001 n=7) Hash8Bytes/Sum512-2 15.38Mi ± 1% 16.82Mi ± 0% +9.36% (p=0.001 n=7) Hash8Bytes/Sum512-4 15.31Mi ± 0% 16.87Mi ± 1% +10.22% (p=0.001 n=7) Hash1K/New 245.0Mi ± 1% 275.6Mi ± 1% +12.47% (p=0.001 n=7) Hash1K/New-2 243.9Mi ± 2% 274.5Mi ± 1% +12.55% (p=0.001 n=7) Hash1K/New-4 244.3Mi ± 0% 274.1Mi ± 1% +12.21% (p=0.001 n=7) Hash1K/Sum384 244.4Mi ± 0% 274.3Mi ± 1% +12.24% (p=0.001 n=7) Hash1K/Sum384-2 243.5Mi ± 1% 273.1Mi ± 1% +12.16% (p=0.001 n=7) Hash1K/Sum384-4 243.9Mi ± 0% 274.0Mi ± 0% +12.35% (p=0.001 n=7) Hash1K/Sum512 244.3Mi ± 1% 274.7Mi ± 1% +12.46% (p=0.001 n=7) Hash1K/Sum512-2 244.4Mi ± 0% 274.3Mi ± 1% +12.25% (p=0.001 n=7) Hash1K/Sum512-4 243.9Mi ± 1% 273.3Mi ± 1% +12.08% (p=0.001 n=7) Hash8K/New 275.7Mi ± 1% 309.0Mi ± 1% +12.07% (p=0.001 n=7) Hash8K/New-2 274.6Mi ± 1% 306.6Mi ± 1% +11.67% (p=0.001 n=7) Hash8K/New-4 274.9Mi ± 1% 307.9Mi ± 1% +11.99% (p=0.001 n=7) Hash8K/Sum384 275.3Mi ± 0% 310.3Mi ± 0% +12.71% (p=0.001 n=7) Hash8K/Sum384-2 274.1Mi ± 1% 308.2Mi ± 1% +12.45% (p=0.001 n=7) Hash8K/Sum384-4 274.1Mi ± 1% 308.2Mi ± 0% +12.44% (p=0.001 n=7) Hash8K/Sum512 275.7Mi ± 1% 309.6Mi ± 0% +12.31% (p=0.001 n=7) Hash8K/Sum512-2 275.8Mi ± 1% 309.7Mi ± 1% +12.29% (p=0.001 n=7) Hash8K/Sum512-4 274.3Mi ± 1% 308.7Mi ± 1% +12.52% (p=0.001 n=7) geomean 101.2Mi 112.9Mi +11.53% │ before │ after │ │ B/op │ B/op vs base │ Hash8Bytes/New 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/New-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/New-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum384 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum384-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum384-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum512 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum512-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum512-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/New 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/New-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/New-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum384 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum384-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum384-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum512 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum512-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum512-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/New 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/New-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/New-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum384 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum384-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum384-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum512 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum512-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum512-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean │ before │ after │ │ allocs/op │ allocs/op vs base │ Hash8Bytes/New 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/New-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/New-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum384 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum384-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum384-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum512 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum512-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8Bytes/Sum512-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/New 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/New-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/New-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum384 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum384-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum384-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum512 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum512-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash1K/Sum512-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/New 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/New-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/New-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum384 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum384-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum384-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum512 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum512-2 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ Hash8K/Sum512-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=7) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean Change-Id: I3791244b3f69e093203f6aa46dc59428afcb9223 Reviewed-on: https://go-review.googlesource.com/c/go/+/711844 LUCI-TryBot-Result: Go LUCI Reviewed-by: Filippo Valsorda Reviewed-by: Roland Shoemaker --- .../internal/fips140/sha512/sha512block.go | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/crypto/internal/fips140/sha512/sha512block.go b/src/crypto/internal/fips140/sha512/sha512block.go index 517e8389f7e..88b75574d79 100644 --- a/src/crypto/internal/fips140/sha512/sha512block.go +++ b/src/crypto/internal/fips140/sha512/sha512block.go @@ -97,23 +97,22 @@ func blockGeneric(dig *Digest, p []byte) { var w [80]uint64 h0, h1, h2, h3, h4, h5, h6, h7 := dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7] for len(p) >= chunk { - for i := 0; i < 16; i++ { - j := i * 8 - w[i] = uint64(p[j])<<56 | uint64(p[j+1])<<48 | uint64(p[j+2])<<40 | uint64(p[j+3])<<32 | - uint64(p[j+4])<<24 | uint64(p[j+5])<<16 | uint64(p[j+6])<<8 | uint64(p[j+7]) - } - for i := 16; i < 80; i++ { - v1 := w[i-2] - t1 := bits.RotateLeft64(v1, -19) ^ bits.RotateLeft64(v1, -61) ^ (v1 >> 6) - v2 := w[i-15] - t2 := bits.RotateLeft64(v2, -1) ^ bits.RotateLeft64(v2, -8) ^ (v2 >> 7) - - w[i] = t1 + w[i-7] + t2 + w[i-16] - } - a, b, c, d, e, f, g, h := h0, h1, h2, h3, h4, h5, h6, h7 - for i := 0; i < 80; i++ { + for i := range 80 { + if i < 16 { + j := i * 8 + w[i] = uint64(p[j])<<56 | uint64(p[j+1])<<48 | uint64(p[j+2])<<40 | uint64(p[j+3])<<32 | + uint64(p[j+4])<<24 | uint64(p[j+5])<<16 | uint64(p[j+6])<<8 | uint64(p[j+7]) + } else { + v1 := w[i-2] + t1 := bits.RotateLeft64(v1, -19) ^ bits.RotateLeft64(v1, -61) ^ (v1 >> 6) + v2 := w[i-15] + t2 := bits.RotateLeft64(v2, -1) ^ bits.RotateLeft64(v2, -8) ^ (v2 >> 7) + + w[i] = t1 + w[i-7] + t2 + w[i-16] + } + t1 := h + (bits.RotateLeft64(e, -14) ^ bits.RotateLeft64(e, -18) ^ bits.RotateLeft64(e, -41)) + ((e & f) ^ (^e & g)) + _K[i] + w[i] t2 := (bits.RotateLeft64(a, -28) ^ bits.RotateLeft64(a, -34) ^ bits.RotateLeft64(a, -39)) + ((a & b) ^ (a & c) ^ (b & c)) From 6e5cfe94b0635e07466a8b8ebeacae4600d273d7 Mon Sep 17 00:00:00 2001 From: Neal Patel Date: Wed, 8 Oct 2025 14:13:56 -0400 Subject: [PATCH 030/140] crypto: fix dead links and correct SHA-512 algorithm comment Change-Id: I71d63b0b78a9fc4895574f6df465e22c9585e77c Reviewed-on: https://go-review.googlesource.com/c/go/+/710196 Reviewed-by: Filippo Valsorda Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI --- .../internal/fips140/sha256/_asm/sha256block_amd64_avx2.go | 2 +- .../internal/fips140/sha512/_asm/sha512block_amd64_asm.go | 4 ++-- src/crypto/sha1/_asm/sha1block_amd64_asm.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/crypto/internal/fips140/sha256/_asm/sha256block_amd64_avx2.go b/src/crypto/internal/fips140/sha256/_asm/sha256block_amd64_avx2.go index 0e6f1c74cf5..c82baf9c8e5 100644 --- a/src/crypto/internal/fips140/sha256/_asm/sha256block_amd64_avx2.go +++ b/src/crypto/internal/fips140/sha256/_asm/sha256block_amd64_avx2.go @@ -15,7 +15,7 @@ import ( // To find it, surf to http://www.intel.com/p/en_US/embedded // and search for that title. // AVX2 version by Intel, same algorithm as code in Linux kernel: -// https://github.com/torvalds/linux/blob/master/arch/x86/crypto/sha256-avx2-asm.S +// https://github.com/torvalds/linux/blob/master/lib/crypto/x86/sha256-avx2-asm.S // by // James Guilford // Kirk Yap diff --git a/src/crypto/internal/fips140/sha512/_asm/sha512block_amd64_asm.go b/src/crypto/internal/fips140/sha512/_asm/sha512block_amd64_asm.go index 7e7572cb1ee..bc6b38decaa 100644 --- a/src/crypto/internal/fips140/sha512/_asm/sha512block_amd64_asm.go +++ b/src/crypto/internal/fips140/sha512/_asm/sha512block_amd64_asm.go @@ -21,7 +21,7 @@ import ( // https://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf // // Wt = Mt; for 0 <= t <= 15 -// Wt = SIGMA1(Wt-2) + SIGMA0(Wt-15) + Wt-16; for 16 <= t <= 79 +// Wt = SIGMA1(Wt-2) + Wt-7 + SIGMA0(Wt-15) + Wt-16; for 16 <= t <= 79 // // a = H0 // b = H1 @@ -154,7 +154,7 @@ func main() { // Architecture Processors" White-paper // https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/fast-sha512-implementations-ia-processors-paper.pdf // AVX2 version by Intel, same algorithm in Linux kernel: -// https://github.com/torvalds/linux/blob/master/arch/x86/crypto/sha512-avx2-asm.S +// https://github.com/torvalds/linux/blob/master/lib/crypto/x86/sha512-avx2-asm.S // James Guilford // Kirk Yap diff --git a/src/crypto/sha1/_asm/sha1block_amd64_asm.go b/src/crypto/sha1/_asm/sha1block_amd64_asm.go index dbd171c08bd..12eb4dee4a7 100644 --- a/src/crypto/sha1/_asm/sha1block_amd64_asm.go +++ b/src/crypto/sha1/_asm/sha1block_amd64_asm.go @@ -13,7 +13,7 @@ import ( //go:generate go run . -out ../sha1block_amd64.s -pkg sha1 // AVX2 version by Intel, same algorithm as code in Linux kernel: -// https://github.com/torvalds/linux/blob/master/arch/x86/crypto/sha1_avx2_x86_64_asm.S +// https://github.com/torvalds/linux/blob/master/lib/crypto/x86/sha1-avx2-asm.S // Authors: // Ilya Albrekht // Maxim Locktyukhin From 113eb42efca8e14355f57c89cd38d31616728a27 Mon Sep 17 00:00:00 2001 From: Taichi Maeda Date: Thu, 20 Nov 2025 23:56:29 +0000 Subject: [PATCH 031/140] strconv: replace Ryu ftoa with Dragonbox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dragonbox is a faster ftoa algorithm that provides the same guarantees as Ryu: round-trip conversion, shortest length, and correct rounding. Dragonbox only supports shortest-precision conversion, so we continue to use Ryu-printf for fixed precision. The new implementation has been fuzz-tested against the current Ryu implementation in addition to the existing test suite. Benchmarks show at least ~15-20% performance improvement. The following shows the relevant output from benchstat. Full benchmark results and plots are available at: https://github.com/taichimaeda/dragonbox-bench/ goos: darwin goarch: arm64 pkg: strconv cpu: Apple M1 │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ FormatFloat/Decimal-8 32.71n ± 14% 31.89n ± 12% ~ (p=0.165 n=10) FormatFloat/Float-8 45.54n ± 1% 42.48n ± 0% -6.70% (p=0.000 n=10) FormatFloat/Exp-8 50.06n ± 0% 32.27n ± 1% -35.54% (p=0.000 n=10) FormatFloat/NegExp-8 47.15n ± 1% 31.33n ± 0% -33.56% (p=0.000 n=10) FormatFloat/LongExp-8 46.15n ± 1% 43.66n ± 0% -5.38% (p=0.000 n=10) FormatFloat/Big-8 50.02n ± 0% 39.36n ± 0% -21.31% (p=0.000 n=10) FormatFloat/BinaryExp-8 27.89n ± 0% 27.88n ± 1% ~ (p=0.798 n=10) FormatFloat/32Integer-8 31.41n ± 0% 23.00n ± 3% -26.79% (p=0.000 n=10) FormatFloat/32ExactFraction-8 44.93n ± 1% 29.91n ± 0% -33.43% (p=0.000 n=10) FormatFloat/32Point-8 43.22n ± 1% 33.82n ± 0% -21.74% (p=0.000 n=10) FormatFloat/32Exp-8 45.91n ± 0% 25.48n ± 0% -44.50% (p=0.000 n=10) FormatFloat/32NegExp-8 44.66n ± 0% 25.12n ± 0% -43.76% (p=0.000 n=10) FormatFloat/32Shortest-8 37.96n ± 0% 27.83n ± 1% -26.68% (p=0.000 n=10) FormatFloat/Slowpath64-8 47.74n ± 2% 45.85n ± 0% -3.96% (p=0.000 n=10) FormatFloat/SlowpathDenormal64-8 42.78n ± 1% 41.46n ± 0% -3.07% (p=0.000 n=10) FormatFloat/ShorterIntervalCase32-8 25.49n ± 2% FormatFloat/ShorterIntervalCase64-8 27.72n ± 1% geomean 41.95n 31.89n -22.11% Fixes #74886 Co-authored-by: Junekey Jeon Change-Id: I923f7259c9cecd0896b2340a43d1041cc2ed7787 GitHub-Last-Rev: fd735db0b1e3fab5fbad4d8b75c8e29247069d94 GitHub-Pull-Request: golang/go#75195 Reviewed-on: https://go-review.googlesource.com/c/go/+/700075 Reviewed-by: Russ Cox Reviewed-by: Alan Donovan TryBot-Bypass: Russ Cox --- src/internal/strconv/ftoa.go | 6 +- src/internal/strconv/ftoa_test.go | 4 + src/internal/strconv/ftoadbox.go | 349 ++++++++++++++++++++++++++++++ 3 files changed, 357 insertions(+), 2 deletions(-) create mode 100644 src/internal/strconv/ftoadbox.go diff --git a/src/internal/strconv/ftoa.go b/src/internal/strconv/ftoa.go index 64be29e23ef..c8c98c13804 100644 --- a/src/internal/strconv/ftoa.go +++ b/src/internal/strconv/ftoa.go @@ -86,6 +86,7 @@ func genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte { neg := bits>>(flt.expbits+flt.mantbits) != 0 exp := int(bits>>flt.mantbits) & (1<> 32) + yl := uint32(y) + + xyh := umul64(x, yh) + xyl := umul64(x, yl) + + return xyh + (xyl >> 32) +} + +// umul96Lower64 returns the lower 64 bits (out of 96 bits) of x * y. +func umul96Lower64(x uint32, y uint64) uint64 { + return uint64(uint64(x) * y) +} + +// umul128Upper64 returns the upper 64 bits (out of 128 bits) of x * y. +func umul128Upper64(x, y uint64) uint64 { + a := uint32(x >> 32) + b := uint32(x) + c := uint32(y >> 32) + d := uint32(y) + + ac := umul64(a, c) + bc := umul64(b, c) + ad := umul64(a, d) + bd := umul64(b, d) + + intermediate := (bd >> 32) + uint64(uint32(ad)) + uint64(uint32(bc)) + + return ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32) +} + +// umul192Upper128 returns the upper 128 bits (out of 192 bits) of x * y. +func umul192Upper128(x uint64, y uint128) uint128 { + r := umul128(x, y.Hi) + t := umul128Upper64(x, y.Lo) + return uadd128(r, t) +} + +// umul192Lower128 returns the lower 128 bits (out of 192 bits) of x * y. +func umul192Lower128(x uint64, y uint128) uint128 { + high := x * y.Hi + highLow := umul128(x, y.Lo) + return uint128{uint64(high + highLow.Hi), highLow.Lo} +} + +// dboxMulPow64 computes x^(i), y^(i), z^(i) +// from the precomputed value of φ̃k for float64 +// and also checks if x^(f), y^(f), z^(f) == 0 (section 5.2.1). +func dboxMulPow64(u uint64, phi uint128) (intPart uint64, isInt bool) { + r := umul192Upper128(u, phi) + intPart = r.Hi + isInt = r.Lo == 0 + return +} + +// dboxMulPow32 computes x^(i), y^(i), z^(i) +// from the precomputed value of φ̃k for float32 +// and also checks if x^(f), y^(f), z^(f) == 0 (section 5.2.1). +func dboxMulPow32(u uint32, phi uint64) (intPart uint32, isInt bool) { + r := umul96Upper64(u, phi) + intPart = uint32(r >> 32) + isInt = uint32(r) == 0 + return +} + +// dboxParity64 computes only the parity of x^(i), y^(i), z^(i) +// from the precomputed value of φ̃k for float64 +// and also checks if x^(f), y^(f), z^(f) = 0 (section 5.2.1). +func dboxParity64(mant2 uint64, phi uint128, beta int) (parity bool, isInt bool) { + r := umul192Lower128(mant2, phi) + parity = ((r.Hi >> (64 - beta)) & 1) != 0 + isInt = ((uint64(r.Hi << beta)) | (r.Lo >> (64 - beta))) == 0 + return +} + +// dboxParity32 computes only the parity of x^(i), y^(i), z^(i) +// from the precomputed value of φ̃k for float32 +// and also checks if x^(f), y^(f), z^(f) = 0 (section 5.2.1). +func dboxParity32(mant2 uint32, phi uint64, beta int) (parity bool, isInt bool) { + r := umul96Lower64(mant2, phi) + parity = ((r >> (64 - beta)) & 1) != 0 + isInt = uint32(r>>(32-beta)) == 0 + return +} + +// dboxDelta64 returns δ^(i) from the precomputed value of φ̃k for float64. +func dboxDelta64(φ uint128, β int) uint32 { + return uint32(φ.Hi >> (64 - 1 - β)) +} + +// dboxDelta32 returns δ^(i) from the precomputed value of φ̃k for float32. +func dboxDelta32(φ uint64, β int) uint32 { + return uint32(φ >> (64 - 1 - β)) +} + +// mulLog10_2MinusLog10_4Over3 computes +// ⌊e*log10(2)-log10(4/3)⌋ = ⌊log10(2^e)-log10(4/3)⌋ (section 6.3). +func mulLog10_2MinusLog10_4Over3(e int) int { + // e should be in the range [-2985, 2936]. + return (e*631305 - 261663) >> 21 +} + +const ( + floatMantBits64 = 52 // p = 52 for float64. + floatMantBits32 = 23 // p = 23 for float32. +) + +// dboxRange64 returns the left and right float64 endpoints. +func dboxRange64(φ uint128, β int) (left, right uint64) { + left = (φ.Hi - (φ.Hi >> (float64MantBits + 2))) >> (64 - float64MantBits - 1 - β) + right = (φ.Hi + (φ.Hi >> (float64MantBits + 1))) >> (64 - float64MantBits - 1 - β) + return left, right +} + +// dboxRange32 returns the left and right float32 endpoints. +func dboxRange32(φ uint64, β int) (left, right uint32) { + left = uint32((φ - (φ >> (floatMantBits32 + 2))) >> (64 - floatMantBits32 - 1 - β)) + right = uint32((φ + (φ >> (floatMantBits32 + 1))) >> (64 - floatMantBits32 - 1 - β)) + return left, right +} + +// dboxRoundUp64 computes the round up of y (i.e., y^(ru)). +func dboxRoundUp64(phi uint128, beta int) uint64 { + return (phi.Hi>>(128/2-floatMantBits64-2-beta) + 1) / 2 +} + +// dboxRoundUp32 computes the round up of y (i.e., y^(ru)). +func dboxRoundUp32(phi uint64, beta int) uint32 { + return uint32(phi>>(64-floatMantBits32-2-beta)+1) / 2 +} + +// dboxPow64 gets the precomputed value of φ̃̃k for float64. +func dboxPow64(k, e int) (φ uint128, β int) { + φ, e1, _ := pow10(k) + if k < 0 || k > 55 { + φ.Lo++ + } + β = e + e1 - 1 + return φ, β +} + +// dboxPow32 gets the precomputed value of φ̃̃k for float32. +func dboxPow32(k, e int) (mant uint64, exp int) { + m, e1, _ := pow10(k) + if k < 0 || k > 27 { + m.Hi++ + } + exp = e + e1 - 1 + return m.Hi, exp +} From eec40aae4517affc52a110ea5b988eb297a67bf2 Mon Sep 17 00:00:00 2001 From: guoguangwu Date: Tue, 20 Feb 2024 06:13:08 +0000 Subject: [PATCH 032/140] maps: use strings.EqualFold in example Change-Id: I40a9684a9465e844ff1de46601edf23de7b637e3 GitHub-Last-Rev: 15ef023853d1f18160b290f32468f1ce0c38d12d GitHub-Pull-Request: golang/go#65541 Reviewed-on: https://go-review.googlesource.com/c/go/+/561855 Auto-Submit: Sean Liao Reviewed-by: Ian Lance Taylor LUCI-TryBot-Result: Go LUCI Reviewed-by: Mark Freeman Reviewed-by: Sean Liao Reviewed-by: Cherry Mui --- src/maps/example_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/maps/example_test.go b/src/maps/example_test.go index 0795941d906..b4ccff6d45c 100644 --- a/src/maps/example_test.go +++ b/src/maps/example_test.go @@ -128,7 +128,7 @@ func ExampleEqualFunc() { 1000: []byte("Thousand"), } eq := maps.EqualFunc(m1, m2, func(v1 string, v2 []byte) bool { - return strings.ToLower(v1) == strings.ToLower(string(v2)) + return strings.EqualFold(v1, string(v2)) }) fmt.Println(eq) // Output: From a572d571fae7b269b8b7e3b8c6ec41c01cd0f25b Mon Sep 17 00:00:00 2001 From: Louis Nyffenegger Date: Sun, 6 Jul 2025 11:18:42 +0000 Subject: [PATCH 033/140] path: add more examples for path.Clean Clarify that the function path.Clean only normalises paths and does not protect against directory-traversal attacks. Change-Id: I66f1267ac15900ac0cb6011ace0c79aabaebc68b GitHub-Last-Rev: d669e1edf6221e7c8e0a9aa7bf46493c8ea85ba0 GitHub-Pull-Request: golang/go#74397 Reviewed-on: https://go-review.googlesource.com/c/go/+/684376 Reviewed-by: Cherry Mui Reviewed-by: Mark Freeman LUCI-TryBot-Result: Go LUCI Reviewed-by: Rob Pike Reviewed-by: Sean Liao Auto-Submit: Sean Liao --- src/path/example_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/path/example_test.go b/src/path/example_test.go index e30ebd13dcd..479241a3820 100644 --- a/src/path/example_test.go +++ b/src/path/example_test.go @@ -25,6 +25,8 @@ func ExampleClean() { "a//c", "a/c/.", "a/c/b/..", + "../a/c", + "../a/b/../././/c", "/../a/c", "/../a/b/../././/c", "", @@ -39,6 +41,8 @@ func ExampleClean() { // Clean("a//c") = "a/c" // Clean("a/c/.") = "a/c" // Clean("a/c/b/..") = "a/c" + // Clean("../a/c") = "../a/c" + // Clean("../a/b/../././/c") = "../a/c" // Clean("/../a/c") = "/a/c" // Clean("/../a/b/../././/c") = "/a/c" // Clean("") = "." From 4ca048cc326bf310f873315dfb1e2644ec243299 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Mon, 10 Nov 2025 10:07:51 +0100 Subject: [PATCH 034/140] cmd/internal/obj/riscv: document compressed instructions We update the RISC-V assembler documentation to describe how the RISC-V compressed instruction set is implemented by the assembler and how compressed instructions can be disabled. Change-Id: Ic7b1cb1586e6906af78adb8ff5fa10f5fbfde292 Reviewed-on: https://go-review.googlesource.com/c/go/+/719221 Reviewed-by: Mark Freeman Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI Reviewed-by: Joel Sing Auto-Submit: Joel Sing Reviewed-by: Meng Zhuo --- src/cmd/internal/obj/riscv/doc.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/cmd/internal/obj/riscv/doc.go b/src/cmd/internal/obj/riscv/doc.go index 365bedd2999..898a45b0e6f 100644 --- a/src/cmd/internal/obj/riscv/doc.go +++ b/src/cmd/internal/obj/riscv/doc.go @@ -289,6 +289,33 @@ the constant literal is 0.0, MOVF and MOVD will be encoded as FLW and FLD instructions that load the constant from a location within the program's binary. +# Compressed instructions + +The Go assembler converts 32 bit RISC-V instructions to compressed +instructions when generating machine code. This conversion happens +automatically without the need for any direct involvement from the programmer, +although judicious choice of registers can improve the compression rate for +certain instructions (see the [RISC-V ISA Manual] for more details). This +behaviour is enabled by default for all of the supported RISC-V profiles, i.e., +it is not affected by the value of the GORISCV64 environment variable. + +The use of compressed instructions can be disabled via a debug flag, +compressinstructions: + + - Use -gcflags=all=-d=compressinstructions=0 to disable compressed + instructions in Go code. + - Use -asmflags=all=-d=compressinstructions=0 to disable compressed + instructions in assembly code. + +To completely disable automatic instruction compression in a Go binary both +options must be specified. + +The assembler also permits the use of compressed instructions in hand coded +assembly language, but this should generally be avoided. Note that the +compressinstructions flag only prevents the automatic compression of 32 +bit instructions. It has no effect on compressed instructions that are +hand coded directly into an assembly file. + [RISC-V ISA Manual]: https://github.com/riscv/riscv-isa-manual [rva20u64]: https://github.com/riscv/riscv-profiles/blob/main/src/profiles.adoc#51-rva20u64-profile [rva22u64]: https://github.com/riscv/riscv-profiles/blob/main/src/profiles.adoc#rva22u64-profile From 8d6d14f5d68c6011eb0ae1c9fac6857475aae7a8 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Sun, 28 Sep 2025 21:38:53 -0700 Subject: [PATCH 035/140] compress/flate: move big non-pointer arrays to end of compressor The compressor type is fairly large: 656616 bytes on amd64. Before this patch, it had fields of slice and interface type near the end of the struct. As those types always contain pointers, the ptrBytes value in the type descriptor was quite large. That forces the garbage collector to do extra work scanning for pointers, and wastes a bit of executable space recording the gcmask for the type. This patch moves the arrays to the end of the type, fixing those minor issues. Change-Id: I849a75a19cc61137c8797a1ea5a4c97e0f69b4db Reviewed-on: https://go-review.googlesource.com/c/go/+/707596 LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui Reviewed-by: Mark Freeman Auto-Submit: Ian Lance Taylor Reviewed-by: Joseph Tsai --- src/compress/flate/deflate.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/compress/flate/deflate.go b/src/compress/flate/deflate.go index 6697f3a7913..c3862a680ff 100644 --- a/src/compress/flate/deflate.go +++ b/src/compress/flate/deflate.go @@ -89,16 +89,6 @@ type compressor struct { step func(*compressor) // process window bestSpeed *deflateFast // Encoder for BestSpeed - // Input hash chains - // hashHead[hashValue] contains the largest inputIndex with the specified hash value - // If hashHead[hashValue] is within the current window, then - // hashPrev[hashHead[hashValue] & windowMask] contains the previous index - // with the same hash value. - chainHead int - hashHead [hashSize]uint32 - hashPrev [windowSize]uint32 - hashOffset int - // input window: unprocessed data is window[index:windowEnd] index int window []byte @@ -117,6 +107,18 @@ type compressor struct { maxInsertIndex int err error + // Input hash chains + // hashHead[hashValue] contains the largest inputIndex with the specified hash value + // If hashHead[hashValue] is within the current window, then + // hashPrev[hashHead[hashValue] & windowMask] contains the previous index + // with the same hash value. + // These are large and do not contain pointers, so put them + // near the end of the struct so the GC has to scan less. + chainHead int + hashHead [hashSize]uint32 + hashPrev [windowSize]uint32 + hashOffset int + // hashMatch must be able to contain hashes for the maximum match length. hashMatch [maxMatchLength - 1]uint32 } From 6954be0baacd0f05edfd3015cc3ecfbf237b3967 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Mon, 24 Nov 2025 14:03:34 -0500 Subject: [PATCH 036/140] internal/strconv: delete ftoaryu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CL 700075 made this dead code. Benchmarks below for CL 700075, testing Dragonbox vs the old Ryu being deleted. The "Fixed" benchmarks are unchanged, which gives a sense of the noise level. benchmark \ host linux-amd64 s7 linux-arm64 local linux-386 s7:GOARCH=386 linux-arm vs base vs base vs base vs base vs base vs base vs base AppendFloat/Decimal -2.68% +2.76% +4.99% -7.44% +11.93% +10.51% +21.84% AppendFloat/Float -21.98% -13.32% -16.50% -11.54% -33.37% -28.66% -15.64% AppendFloat/Exp -32.44% -25.54% -28.85% -31.79% -39.60% -35.92% -20.89% AppendFloat/NegExp -33.31% -25.91% -28.90% -31.29% -41.17% -35.52% -21.32% AppendFloat/LongExp -19.35% -9.51% -15.29% -12.36% -30.46% -25.10% -10.18% AppendFloat/Big -24.40% -15.84% -22.56% -24.05% -43.23% -36.28% -26.45% AppendFloat/BinaryExp -0.52% -1.20% ~ ~ ~ +0.96% +1.94% AppendFloat/32Integer -14.24% -7.01% -12.82% -18.99% -12.12% -10.85% -0.32% AppendFloat/32ExactFraction -34.53% -28.47% -34.50% -30.50% -43.75% -38.73% -25.44% AppendFloat/32Point -25.83% -18.54% -23.52% -21.26% -36.74% -33.11% -20.72% AppendFloat/32Exp -37.55% -33.36% -37.74% -39.06% -51.37% -44.53% -31.76% AppendFloat/32NegExp -35.99% -31.96% -36.02% -37.13% -44.62% -39.03% -26.91% AppendFloat/32Shortest -23.25% -18.02% -21.41% -23.07% -35.56% -32.89% -20.13% AppendFloat/32Fixed8Hard +1.09% -1.94% ~ ~ -2.33% -1.36% -0.10% AppendFloat/32Fixed9Hard +1.45% -2.10% +0.10% ~ -4.20% -0.72% +1.31% AppendFloat/64Fixed1 +0.45% ~ ~ -1.66% -3.74% -2.13% ~ AppendFloat/64Fixed2 +0.32% -0.92% +0.53% -1.75% -2.69% ~ -0.49% AppendFloat/64Fixed2.5 +0.38% -0.38% ~ ~ -5.14% -1.15% -0.97% AppendFloat/64Fixed3 +0.97% -0.53% ~ +0.23% -3.57% -4.04% -0.27% AppendFloat/64Fixed4 +0.95% -2.77% +0.45% -1.57% -3.99% -2.58% -0.91% AppendFloat/64Fixed5Hard +0.52% -1.22% ~ -0.87% -3.20% -1.60% +0.49% AppendFloat/64Fixed12 +1.15% -0.62% ~ ~ -3.37% -1.43% -0.72% AppendFloat/64Fixed16 +1.13% ~ -0.21% -0.59% -3.65% ~ +0.74% AppendFloat/64Fixed12Hard +0.78% -1.26% ~ -0.95% -4.82% -2.98% +0.26% AppendFloat/64Fixed17Hard ~ ~ -0.32% -6.34% -2.44% -2.19% +1.00% AppendFloat/64Fixed18Hard ~ ~ ~ ~ ~ ~ +0.06% AppendFloat/64FixedF1 +0.44% ~ +0.43% -1.87% -2.75% ~ -1.24% AppendFloat/64FixedF2 +1.35% -1.04% +0.81% +1.26% -2.21% -2.36% ~ AppendFloat/64FixedF3 ~ -1.14% +0.39% -1.58% -3.46% ~ -1.08% AppendFloat/Slowpath64 -15.51% -7.05% -14.59% -7.86% -22.54% -19.63% -5.90% AppendFloat/SlowpathDenormal64 -15.10% -8.19% -14.62% -9.36% -26.86% -23.10% -14.48% host: linux-amd64 goos: linux goarch: amd64 pkg: internal/strconv cpu: Intel(R) Xeon(R) CPU @ 2.30GHz │ 3c26aef8fba │ 8a958b0d9c1 │ │ sec/op │ sec/op vs base │ AppendFloat/Decimal-16 63.37n ± 0% 61.67n ± 0% -2.68% (p=0.000 n=20) AppendFloat/Float-16 92.83n ± 0% 72.43n ± 0% -21.98% (p=0.000 n=20) AppendFloat/Exp-16 98.60n ± 0% 66.61n ± 0% -32.44% (p=0.000 n=20) AppendFloat/NegExp-16 100.15n ± 0% 66.79n ± 0% -33.31% (p=0.000 n=20) AppendFloat/LongExp-16 105.35n ± 0% 84.96n ± 0% -19.35% (p=0.000 n=20) AppendFloat/Big-16 108.50n ± 0% 82.03n ± 0% -24.40% (p=0.000 n=20) AppendFloat/BinaryExp-16 47.27n ± 0% 47.03n ± 0% -0.52% (p=0.000 n=20) AppendFloat/32Integer-16 63.29n ± 0% 54.28n ± 0% -14.24% (p=0.000 n=20) AppendFloat/32ExactFraction-16 89.72n ± 0% 58.74n ± 0% -34.53% (p=0.000 n=20) AppendFloat/32Point-16 87.32n ± 0% 64.77n ± 0% -25.83% (p=0.000 n=20) AppendFloat/32Exp-16 94.89n ± 0% 59.26n ± 0% -37.55% (p=0.000 n=20) AppendFloat/32NegExp-16 92.68n ± 0% 59.32n ± 0% -35.99% (p=0.000 n=20) AppendFloat/32Shortest-16 82.12n ± 0% 63.04n ± 0% -23.25% (p=0.000 n=20) AppendFloat/32Fixed8Hard-16 57.76n ± 0% 58.38n ± 0% +1.09% (p=0.000 n=20) AppendFloat/32Fixed9Hard-16 66.44n ± 0% 67.41n ± 0% +1.45% (p=0.000 n=20) AppendFloat/64Fixed1-16 51.00n ± 0% 51.24n ± 0% +0.45% (p=0.000 n=20) AppendFloat/64Fixed2-16 50.86n ± 0% 51.03n ± 0% +0.32% (p=0.000 n=20) AppendFloat/64Fixed2.5-16 49.31n ± 0% 49.49n ± 0% +0.38% (p=0.000 n=20) AppendFloat/64Fixed3-16 51.98n ± 0% 52.48n ± 0% +0.97% (p=0.000 n=20) AppendFloat/64Fixed4-16 50.05n ± 0% 50.52n ± 0% +0.95% (p=0.000 n=20) AppendFloat/64Fixed5Hard-16 58.01n ± 0% 58.31n ± 0% +0.52% (p=0.000 n=20) AppendFloat/64Fixed12-16 82.81n ± 0% 83.77n ± 0% +1.15% (p=0.000 n=20) AppendFloat/64Fixed16-16 70.66n ± 0% 71.46n ± 0% +1.13% (p=0.000 n=20) AppendFloat/64Fixed12Hard-16 68.25n ± 0% 68.79n ± 0% +0.78% (p=0.000 n=20) AppendFloat/64Fixed17Hard-16 79.78n ± 0% 79.82n ± 0% ~ (p=0.136 n=20) AppendFloat/64Fixed18Hard-16 4.881µ ± 0% 4.876µ ± 0% ~ (p=0.432 n=20) AppendFloat/64FixedF1-16 68.74n ± 0% 69.04n ± 0% +0.44% (p=0.000 n=20) AppendFloat/64FixedF2-16 57.36n ± 0% 58.13n ± 0% +1.35% (p=0.000 n=20) AppendFloat/64FixedF3-16 52.59n ± 0% 52.77n ± 0% ~ (p=0.001 n=20) AppendFloat/Slowpath64-16 99.56n ± 0% 84.12n ± 0% -15.51% (p=0.000 n=20) AppendFloat/SlowpathDenormal64-16 97.35n ± 0% 82.65n ± 0% -15.10% (p=0.000 n=20) AppendFloat/ShorterIntervalCase32-16 56.27n ± 0% AppendFloat/ShorterIntervalCase64-16 57.42n ± 0% geomean 82.53n 71.80n -11.68% host: s7 cpu: AMD Ryzen 9 7950X 16-Core Processor │ 3c26aef8fba │ 8a958b0d9c1 │ │ sec/op │ sec/op vs base │ AppendFloat/Decimal-32 22.30n ± 0% 22.91n ± 0% +2.76% (p=0.000 n=20) AppendFloat/Float-32 34.54n ± 0% 29.94n ± 0% -13.32% (p=0.000 n=20) AppendFloat/Exp-32 34.55n ± 0% 25.72n ± 0% -25.54% (p=0.000 n=20) AppendFloat/NegExp-32 35.08n ± 0% 25.99n ± 1% -25.91% (p=0.000 n=20) AppendFloat/LongExp-32 36.85n ± 0% 33.35n ± 1% -9.51% (p=0.000 n=20) AppendFloat/Big-32 38.28n ± 0% 32.21n ± 1% -15.84% (p=0.000 n=20) AppendFloat/BinaryExp-32 17.52n ± 0% 17.30n ± 0% -1.20% (p=0.000 n=20) AppendFloat/32Integer-32 22.31n ± 0% 20.75n ± 0% -7.01% (p=0.000 n=20) AppendFloat/32ExactFraction-32 32.74n ± 1% 23.41n ± 1% -28.47% (p=0.000 n=20) AppendFloat/32Point-32 32.88n ± 0% 26.79n ± 0% -18.54% (p=0.000 n=20) AppendFloat/32Exp-32 34.10n ± 0% 22.72n ± 1% -33.36% (p=0.000 n=20) AppendFloat/32NegExp-32 33.17n ± 1% 22.57n ± 0% -31.96% (p=0.000 n=20) AppendFloat/32Shortest-32 29.85n ± 1% 24.47n ± 0% -18.02% (p=0.000 n=20) AppendFloat/32Fixed8Hard-32 22.62n ± 1% 22.19n ± 1% -1.94% (p=0.000 n=20) AppendFloat/32Fixed9Hard-32 25.75n ± 1% 25.21n ± 0% -2.10% (p=0.000 n=20) AppendFloat/64Fixed1-32 19.02n ± 1% 18.98n ± 0% ~ (p=0.351 n=20) AppendFloat/64Fixed2-32 18.94n ± 0% 18.76n ± 0% -0.92% (p=0.000 n=20) AppendFloat/64Fixed2.5-32 18.23n ± 0% 18.16n ± 0% -0.38% (p=0.001 n=20) AppendFloat/64Fixed3-32 19.79n ± 0% 19.68n ± 0% -0.53% (p=0.000 n=20) AppendFloat/64Fixed4-32 18.93n ± 0% 18.40n ± 1% -2.77% (p=0.000 n=20) AppendFloat/64Fixed5Hard-32 21.81n ± 0% 21.54n ± 1% -1.22% (p=0.000 n=20) AppendFloat/64Fixed12-32 30.58n ± 1% 30.39n ± 0% -0.62% (p=0.000 n=20) AppendFloat/64Fixed16-32 26.98n ± 1% 26.80n ± 1% ~ (p=0.010 n=20) AppendFloat/64Fixed12Hard-32 26.20n ± 0% 25.86n ± 1% -1.26% (p=0.000 n=20) AppendFloat/64Fixed17Hard-32 30.01n ± 1% 30.10n ± 1% ~ (p=0.112 n=20) AppendFloat/64Fixed18Hard-32 1.809µ ± 1% 1.806µ ± 0% ~ (p=0.713 n=20) AppendFloat/64FixedF1-32 26.78n ± 1% 26.59n ± 0% ~ (p=0.005 n=20) AppendFloat/64FixedF2-32 20.24n ± 1% 20.03n ± 0% -1.04% (p=0.000 n=20) AppendFloat/64FixedF3-32 18.88n ± 0% 18.67n ± 0% -1.14% (p=0.000 n=20) AppendFloat/Slowpath64-32 35.37n ± 0% 32.88n ± 1% -7.05% (p=0.000 n=20) AppendFloat/SlowpathDenormal64-32 35.17n ± 0% 32.29n ± 1% -8.19% (p=0.000 n=20) AppendFloat/ShorterIntervalCase32-32 21.76n ± 0% AppendFloat/ShorterIntervalCase64-32 22.11n ± 0% geomean 30.34n 27.23n -8.96% host: linux-arm64 goarch: arm64 cpu: unknown │ 3c26aef8fba │ 8a958b0d9c1 │ │ sec/op │ sec/op vs base │ AppendFloat/Decimal-8 60.08n ± 0% 63.07n ± 0% +4.99% (p=0.000 n=20) AppendFloat/Float-8 88.53n ± 0% 73.92n ± 0% -16.50% (p=0.000 n=20) AppendFloat/Exp-8 93.07n ± 0% 66.22n ± 0% -28.85% (p=0.000 n=20) AppendFloat/NegExp-8 93.35n ± 0% 66.38n ± 0% -28.90% (p=0.000 n=20) AppendFloat/LongExp-8 100.15n ± 0% 84.84n ± 0% -15.29% (p=0.000 n=20) AppendFloat/Big-8 103.80n ± 0% 80.38n ± 0% -22.56% (p=0.000 n=20) AppendFloat/BinaryExp-8 47.36n ± 0% 47.34n ± 0% ~ (p=0.033 n=20) AppendFloat/32Integer-8 60.28n ± 0% 52.55n ± 0% -12.82% (p=0.000 n=20) AppendFloat/32ExactFraction-8 86.11n ± 0% 56.40n ± 0% -34.50% (p=0.000 n=20) AppendFloat/32Point-8 82.88n ± 0% 63.39n ± 0% -23.52% (p=0.000 n=20) AppendFloat/32Exp-8 89.33n ± 0% 55.62n ± 0% -37.74% (p=0.000 n=20) AppendFloat/32NegExp-8 87.48n ± 0% 55.97n ± 0% -36.02% (p=0.000 n=20) AppendFloat/32Shortest-8 76.31n ± 0% 59.97n ± 0% -21.41% (p=0.000 n=20) AppendFloat/32Fixed8Hard-8 52.83n ± 0% 52.82n ± 0% ~ (p=0.370 n=20) AppendFloat/32Fixed9Hard-8 60.90n ± 0% 60.96n ± 0% +0.10% (p=0.000 n=20) AppendFloat/64Fixed1-8 46.96n ± 0% 46.95n ± 0% ~ (p=0.702 n=20) AppendFloat/64Fixed2-8 46.96n ± 0% 47.21n ± 0% +0.53% (p=0.000 n=20) AppendFloat/64Fixed2.5-8 44.24n ± 0% 44.29n ± 0% ~ (p=0.006 n=20) AppendFloat/64Fixed3-8 47.73n ± 0% 47.78n ± 0% ~ (p=0.020 n=20) AppendFloat/64Fixed4-8 44.40n ± 0% 44.60n ± 0% +0.45% (p=0.000 n=20) AppendFloat/64Fixed5Hard-8 52.52n ± 0% 52.50n ± 0% ~ (p=0.722 n=20) AppendFloat/64Fixed12-8 78.57n ± 0% 78.56n ± 0% ~ (p=0.222 n=20) AppendFloat/64Fixed16-8 65.36n ± 0% 65.22n ± 0% -0.21% (p=0.000 n=20) AppendFloat/64Fixed12Hard-8 62.04n ± 0% 61.97n ± 0% ~ (p=0.004 n=20) AppendFloat/64Fixed17Hard-8 74.30n ± 0% 74.06n ± 0% -0.32% (p=0.000 n=20) AppendFloat/64Fixed18Hard-8 4.282µ ± 0% 4.284µ ± 0% ~ (p=0.296 n=20) AppendFloat/64FixedF1-8 66.05n ± 0% 66.33n ± 0% +0.43% (p=0.000 n=20) AppendFloat/64FixedF2-8 53.67n ± 0% 54.11n ± 0% +0.81% (p=0.000 n=20) AppendFloat/64FixedF3-8 47.41n ± 0% 47.59n ± 0% +0.39% (p=0.000 n=20) AppendFloat/Slowpath64-8 97.42n ± 0% 83.21n ± 0% -14.59% (p=0.000 n=20) AppendFloat/SlowpathDenormal64-8 94.74n ± 0% 80.88n ± 0% -14.62% (p=0.000 n=20) AppendFloat/ShorterIntervalCase32-8 53.77n ± 0% AppendFloat/ShorterIntervalCase64-8 55.22n ± 0% geomean 77.14n 67.89n -10.73% host: local goos: darwin cpu: Apple M3 Pro │ 3c26aef8fba │ 8a958b0d9c1 │ │ sec/op │ sec/op vs base │ AppendFloat/Decimal-12 21.09n ± 0% 19.52n ± 0% -7.44% (p=0.000 n=20) AppendFloat/Float-12 32.36n ± 0% 28.63n ± 1% -11.54% (p=0.000 n=20) AppendFloat/Exp-12 31.77n ± 0% 21.67n ± 0% -31.79% (p=0.000 n=20) AppendFloat/NegExp-12 31.56n ± 1% 21.68n ± 0% -31.29% (p=0.000 n=20) AppendFloat/LongExp-12 33.33n ± 0% 29.21n ± 0% -12.36% (p=0.000 n=20) AppendFloat/Big-12 35.24n ± 1% 26.77n ± 0% -24.05% (p=0.000 n=20) AppendFloat/BinaryExp-12 18.88n ± 1% 19.38n ± 2% ~ (p=0.031 n=20) AppendFloat/32Integer-12 21.32n ± 1% 17.27n ± 0% -18.99% (p=0.000 n=20) AppendFloat/32ExactFraction-12 30.85n ± 1% 21.44n ± 0% -30.50% (p=0.000 n=20) AppendFloat/32Point-12 31.02n ± 1% 24.42n ± 0% -21.26% (p=0.000 n=20) AppendFloat/32Exp-12 31.55n ± 0% 19.23n ± 0% -39.06% (p=0.000 n=20) AppendFloat/32NegExp-12 30.32n ± 1% 19.06n ± 0% -37.13% (p=0.000 n=20) AppendFloat/32Shortest-12 26.68n ± 0% 20.52n ± 0% -23.07% (p=0.000 n=20) AppendFloat/32Fixed8Hard-12 17.34n ± 1% 17.24n ± 0% ~ (p=0.017 n=20) AppendFloat/32Fixed9Hard-12 19.05n ± 1% 19.25n ± 1% ~ (p=0.155 n=20) AppendFloat/64Fixed1-12 15.66n ± 0% 15.40n ± 0% -1.66% (p=0.000 n=20) AppendFloat/64Fixed2-12 15.39n ± 0% 15.12n ± 0% -1.75% (p=0.000 n=20) AppendFloat/64Fixed2.5-12 15.14n ± 0% 15.14n ± 0% ~ (p=0.645 n=20) AppendFloat/64Fixed3-12 15.53n ± 0% 15.56n ± 0% +0.23% (p=0.000 n=20) AppendFloat/64Fixed4-12 15.28n ± 0% 15.04n ± 0% -1.57% (p=0.000 n=20) AppendFloat/64Fixed5Hard-12 18.32n ± 0% 18.16n ± 0% -0.87% (p=0.000 n=20) AppendFloat/64Fixed12-12 25.51n ± 1% 25.48n ± 0% ~ (p=0.256 n=20) AppendFloat/64Fixed16-12 21.32n ± 0% 21.20n ± 0% -0.59% (p=0.000 n=20) AppendFloat/64Fixed12Hard-12 21.11n ± 1% 20.91n ± 1% -0.95% (p=0.001 n=20) AppendFloat/64Fixed17Hard-12 26.89n ± 1% 25.18n ± 3% -6.34% (p=0.000 n=20) AppendFloat/64Fixed18Hard-12 2.057µ ± 6% 2.065µ ± 1% ~ (p=0.856 n=20) AppendFloat/64FixedF1-12 24.65n ± 0% 24.19n ± 0% -1.87% (p=0.000 n=20) AppendFloat/64FixedF2-12 20.68n ± 0% 20.94n ± 0% +1.26% (p=0.000 n=20) AppendFloat/64FixedF3-12 16.44n ± 0% 16.18n ± 0% -1.58% (p=0.000 n=20) AppendFloat/Slowpath64-12 31.68n ± 0% 29.18n ± 0% -7.86% (p=0.000 n=20) AppendFloat/SlowpathDenormal64-12 29.92n ± 1% 27.12n ± 0% -9.36% (p=0.000 n=20) AppendFloat/ShorterIntervalCase32-12 18.44n ± 1% AppendFloat/ShorterIntervalCase64-12 18.57n ± 0% geomean 26.90n 23.50n -11.27% host: linux-386 goos: linux goarch: 386 cpu: Intel(R) Xeon(R) CPU @ 2.30GHz │ 3c26aef8fba │ 8a958b0d9c1 │ │ sec/op │ sec/op vs base │ AppendFloat/Decimal-16 128.2n ± 0% 143.5n ± 0% +11.93% (p=0.000 n=20) AppendFloat/Float-16 236.3n ± 0% 157.5n ± 0% -33.37% (p=0.000 n=20) AppendFloat/Exp-16 245.3n ± 0% 148.2n ± 0% -39.60% (p=0.000 n=20) AppendFloat/NegExp-16 251.2n ± 0% 147.8n ± 0% -41.17% (p=0.000 n=20) AppendFloat/LongExp-16 253.2n ± 0% 176.0n ± 0% -30.46% (p=0.000 n=20) AppendFloat/Big-16 278.6n ± 0% 158.1n ± 0% -43.23% (p=0.000 n=20) AppendFloat/BinaryExp-16 89.72n ± 0% 89.47n ± 0% ~ (p=0.155 n=20) AppendFloat/32Integer-16 127.1n ± 0% 111.7n ± 0% -12.12% (p=0.000 n=20) AppendFloat/32ExactFraction-16 206.9n ± 1% 116.3n ± 1% -43.75% (p=0.000 n=20) AppendFloat/32Point-16 196.9n ± 0% 124.5n ± 1% -36.74% (p=0.000 n=20) AppendFloat/32Exp-16 235.1n ± 1% 114.3n ± 0% -51.37% (p=0.000 n=20) AppendFloat/32NegExp-16 206.4n ± 0% 114.3n ± 1% -44.62% (p=0.000 n=20) AppendFloat/32Shortest-16 189.7n ± 0% 122.3n ± 0% -35.56% (p=0.000 n=20) AppendFloat/32Fixed8Hard-16 137.2n ± 0% 134.0n ± 0% -2.33% (p=0.000 n=20) AppendFloat/32Fixed9Hard-16 160.8n ± 0% 154.0n ± 0% -4.20% (p=0.000 n=20) AppendFloat/64Fixed1-16 140.2n ± 0% 135.0n ± 0% -3.74% (p=0.000 n=20) AppendFloat/64Fixed2-16 135.5n ± 0% 131.8n ± 0% -2.69% (p=0.000 n=20) AppendFloat/64Fixed2.5-16 133.3n ± 0% 126.5n ± 0% -5.14% (p=0.000 n=20) AppendFloat/64Fixed3-16 135.8n ± 0% 130.9n ± 0% -3.57% (p=0.000 n=20) AppendFloat/64Fixed4-16 127.9n ± 0% 122.8n ± 0% -3.99% (p=0.000 n=20) AppendFloat/64Fixed5Hard-16 140.7n ± 0% 136.2n ± 0% -3.20% (p=0.000 n=20) AppendFloat/64Fixed12-16 166.1n ± 0% 160.5n ± 0% -3.37% (p=0.000 n=20) AppendFloat/64Fixed16-16 160.1n ± 0% 154.2n ± 0% -3.65% (p=0.000 n=20) AppendFloat/64Fixed12Hard-16 156.6n ± 0% 149.0n ± 0% -4.82% (p=0.000 n=20) AppendFloat/64Fixed17Hard-16 173.9n ± 1% 169.6n ± 0% -2.44% (p=0.000 n=20) AppendFloat/64Fixed18Hard-16 10.59µ ± 1% 10.60µ ± 0% ~ (p=0.664 n=20) AppendFloat/64FixedF1-16 158.5n ± 0% 154.1n ± 0% -2.75% (p=0.000 n=20) AppendFloat/64FixedF2-16 147.1n ± 0% 143.8n ± 0% -2.21% (p=0.000 n=20) AppendFloat/64FixedF3-16 135.8n ± 0% 131.1n ± 0% -3.46% (p=0.000 n=20) AppendFloat/Slowpath64-16 244.9n ± 0% 189.7n ± 0% -22.54% (p=0.000 n=20) AppendFloat/SlowpathDenormal64-16 241.8n ± 0% 176.9n ± 0% -26.86% (p=0.000 n=20) AppendFloat/ShorterIntervalCase32-16 114.9n ± 0% AppendFloat/ShorterIntervalCase64-16 130.6n ± 0% geomean 195.7n 157.4n -18.30% host: s7:GOARCH=386 cpu: AMD Ryzen 9 7950X 16-Core Processor │ 3c26aef8fba │ 8a958b0d9c1 │ │ sec/op │ sec/op vs base │ AppendFloat/Decimal-32 42.76n ± 0% 47.25n ± 0% +10.51% (p=0.000 n=20) AppendFloat/Float-32 71.44n ± 1% 50.97n ± 0% -28.66% (p=0.000 n=20) AppendFloat/Exp-32 75.51n ± 0% 48.39n ± 1% -35.92% (p=0.000 n=20) AppendFloat/NegExp-32 74.70n ± 0% 48.17n ± 1% -35.52% (p=0.000 n=20) AppendFloat/LongExp-32 76.52n ± 0% 57.32n ± 1% -25.10% (p=0.000 n=20) AppendFloat/Big-32 83.05n ± 0% 52.92n ± 1% -36.28% (p=0.000 n=20) AppendFloat/BinaryExp-32 31.92n ± 1% 32.22n ± 0% +0.96% (p=0.000 n=20) AppendFloat/32Integer-32 41.29n ± 1% 36.81n ± 0% -10.85% (p=0.000 n=20) AppendFloat/32ExactFraction-32 62.29n ± 1% 38.16n ± 0% -38.73% (p=0.000 n=20) AppendFloat/32Point-32 60.45n ± 1% 40.44n ± 1% -33.11% (p=0.000 n=20) AppendFloat/32Exp-32 69.32n ± 1% 38.45n ± 1% -44.53% (p=0.000 n=20) AppendFloat/32NegExp-32 63.39n ± 0% 38.64n ± 1% -39.03% (p=0.000 n=20) AppendFloat/32Shortest-32 58.90n ± 1% 39.53n ± 0% -32.89% (p=0.000 n=20) AppendFloat/32Fixed8Hard-32 43.30n ± 0% 42.70n ± 1% -1.36% (p=0.000 n=20) AppendFloat/32Fixed9Hard-32 49.96n ± 1% 49.60n ± 0% -0.72% (p=0.000 n=20) AppendFloat/64Fixed1-32 42.99n ± 1% 42.08n ± 0% -2.13% (p=0.000 n=20) AppendFloat/64Fixed2-32 41.58n ± 0% 41.42n ± 1% ~ (p=0.077 n=20) AppendFloat/64Fixed2.5-32 40.47n ± 1% 40.00n ± 1% -1.15% (p=0.000 n=20) AppendFloat/64Fixed3-32 43.43n ± 1% 41.67n ± 0% -4.04% (p=0.000 n=20) AppendFloat/64Fixed4-32 40.44n ± 0% 39.40n ± 0% -2.58% (p=0.000 n=20) AppendFloat/64Fixed5Hard-32 43.41n ± 0% 42.72n ± 0% -1.60% (p=0.000 n=20) AppendFloat/64Fixed12-32 52.00n ± 0% 51.26n ± 0% -1.43% (p=0.000 n=20) AppendFloat/64Fixed16-32 50.62n ± 1% 50.55n ± 0% ~ (p=0.234 n=20) AppendFloat/64Fixed12Hard-32 49.36n ± 0% 47.89n ± 0% -2.98% (p=0.000 n=20) AppendFloat/64Fixed17Hard-32 56.91n ± 0% 55.66n ± 1% -2.19% (p=0.000 n=20) AppendFloat/64Fixed18Hard-32 3.983µ ± 0% 3.964µ ± 0% ~ (p=0.014 n=20) AppendFloat/64FixedF1-32 49.31n ± 1% 49.10n ± 1% ~ (p=0.005 n=20) AppendFloat/64FixedF2-32 45.06n ± 0% 44.00n ± 1% -2.36% (p=0.000 n=20) AppendFloat/64FixedF3-32 42.22n ± 0% 42.20n ± 1% ~ (p=0.644 n=20) AppendFloat/Slowpath64-32 75.77n ± 0% 60.89n ± 1% -19.63% (p=0.000 n=20) AppendFloat/SlowpathDenormal64-32 74.88n ± 1% 57.59n ± 1% -23.10% (p=0.000 n=20) AppendFloat/ShorterIntervalCase32-32 37.66n ± 1% AppendFloat/ShorterIntervalCase64-32 42.49n ± 1% geomean 61.34n 51.27n -15.08% host: linux-arm goarch: arm cpu: ARMv8 Processor rev 1 (v8l) │ 3c26aef8fba │ 8a958b0d9c1 │ │ sec/op │ sec/op vs base │ AppendFloat/Decimal-4 110.8n ± 0% 135.0n ± 0% +21.84% (p=0.000 n=20) AppendFloat/Float-4 172.0n ± 0% 145.1n ± 0% -15.64% (p=0.000 n=20) AppendFloat/Exp-4 172.1n ± 0% 136.2n ± 0% -20.89% (p=0.000 n=20) AppendFloat/NegExp-4 172.6n ± 0% 135.8n ± 0% -21.32% (p=0.000 n=20) AppendFloat/LongExp-4 180.2n ± 0% 161.9n ± 0% -10.18% (p=0.000 n=20) AppendFloat/Big-4 195.5n ± 0% 143.8n ± 0% -26.45% (p=0.000 n=20) AppendFloat/BinaryExp-4 84.75n ± 0% 86.40n ± 0% +1.94% (p=0.000 n=20) AppendFloat/32Integer-4 110.4n ± 0% 110.0n ± 0% -0.32% (p=0.000 n=20) AppendFloat/32ExactFraction-4 152.9n ± 0% 114.0n ± 0% -25.44% (p=0.000 n=20) AppendFloat/32Point-4 151.5n ± 0% 120.1n ± 0% -20.72% (p=0.000 n=20) AppendFloat/32Exp-4 163.1n ± 0% 111.3n ± 0% -31.76% (p=0.000 n=20) AppendFloat/32NegExp-4 152.0n ± 0% 111.1n ± 0% -26.91% (p=0.000 n=20) AppendFloat/32Shortest-4 145.8n ± 0% 116.5n ± 0% -20.13% (p=0.000 n=20) AppendFloat/32Fixed8Hard-4 104.1n ± 0% 104.0n ± 0% -0.10% (p=0.000 n=20) AppendFloat/32Fixed9Hard-4 114.2n ± 0% 115.7n ± 0% +1.31% (p=0.000 n=20) AppendFloat/64Fixed1-4 97.35n ± 0% 97.31n ± 0% ~ (p=0.357 n=20) AppendFloat/64Fixed2-4 95.74n ± 0% 95.28n ± 0% -0.49% (p=0.000 n=20) AppendFloat/64Fixed2.5-4 94.24n ± 0% 93.32n ± 0% -0.97% (p=0.000 n=20) AppendFloat/64Fixed3-4 95.56n ± 0% 95.30n ± 0% -0.27% (p=0.000 n=20) AppendFloat/64Fixed4-4 92.36n ± 0% 91.52n ± 0% -0.91% (p=0.000 n=20) AppendFloat/64Fixed5Hard-4 101.5n ± 0% 102.0n ± 0% +0.49% (p=0.000 n=20) AppendFloat/64Fixed12-4 125.5n ± 0% 124.6n ± 0% -0.72% (p=0.000 n=20) AppendFloat/64Fixed16-4 121.8n ± 0% 122.7n ± 0% +0.74% (p=0.000 n=20) AppendFloat/64Fixed12Hard-4 116.1n ± 0% 116.4n ± 0% +0.26% (p=0.000 n=20) AppendFloat/64Fixed17Hard-4 129.8n ± 0% 131.1n ± 0% +1.00% (p=0.000 n=20) AppendFloat/64Fixed18Hard-4 7.945µ ± 0% 7.950µ ± 0% +0.06% (p=0.000 n=20) AppendFloat/64FixedF1-4 112.8n ± 0% 111.4n ± 0% -1.24% (p=0.000 n=20) AppendFloat/64FixedF2-4 100.6n ± 0% 100.5n ± 0% ~ (p=0.066 n=20) AppendFloat/64FixedF3-4 96.45n ± 0% 95.41n ± 0% -1.08% (p=0.000 n=20) AppendFloat/Slowpath64-4 176.3n ± 0% 165.9n ± 0% -5.90% (p=0.000 n=20) AppendFloat/SlowpathDenormal64-4 178.2n ± 0% 152.4n ± 0% -14.48% (p=0.000 n=20) AppendFloat/ShorterIntervalCase32-4 112.8n ± 0% AppendFloat/ShorterIntervalCase64-4 119.0n ± 0% geomean 144.6n 132.1n -7.84% Change-Id: I1eb3c7b8756ad6cf938bc9b81180e01fd8a4cd9e Reviewed-on: https://go-review.googlesource.com/c/go/+/723861 Reviewed-by: Jorropo Reviewed-by: Alan Donovan LUCI-TryBot-Result: Go LUCI Auto-Submit: Russ Cox --- src/internal/strconv/ftoaryu.go | 307 -------------------------------- 1 file changed, 307 deletions(-) delete mode 100644 src/internal/strconv/ftoaryu.go diff --git a/src/internal/strconv/ftoaryu.go b/src/internal/strconv/ftoaryu.go deleted file mode 100644 index 9407bfec445..00000000000 --- a/src/internal/strconv/ftoaryu.go +++ /dev/null @@ -1,307 +0,0 @@ -// Copyright 2021 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 strconv - -import "math/bits" - -// binary to decimal conversion using the Ryū algorithm. -// -// See Ulf Adams, "Ryū: Fast Float-to-String Conversion" (doi:10.1145/3192366.3192369) - -// ryuFtoaShortest formats mant*2^exp with prec decimal digits. -func ryuFtoaShortest(d *decimalSlice, mant uint64, exp int, flt *floatInfo) { - if mant == 0 { - d.nd, d.dp = 0, 0 - return - } - // If input is an exact integer with fewer bits than the mantissa, - // the previous and next integer are not admissible representations. - if exp <= 0 && bits.TrailingZeros64(mant) >= -exp { - mant >>= uint(-exp) - ryuDigits(d, mant, mant, mant, true, false) - return - } - ml, mc, mu, e2 := computeBounds(mant, exp, flt) - if e2 == 0 { - ryuDigits(d, ml, mc, mu, true, false) - return - } - // Find 10^q *larger* than 2^-e2 - q := mulLog10_2(-e2) + 1 - - // We are going to multiply by 10^q using 128-bit arithmetic. - // The exponent is the same for all 3 numbers. - var dl, dc, du uint64 - var dl0, dc0, du0 bool - if flt == &float32info { - var dl32, dc32, du32 uint32 - dl32, _, dl0 = mult64bitPow10(uint32(ml), e2, q) - dc32, _, dc0 = mult64bitPow10(uint32(mc), e2, q) - du32, e2, du0 = mult64bitPow10(uint32(mu), e2, q) - dl, dc, du = uint64(dl32), uint64(dc32), uint64(du32) - } else { - dl, _, dl0 = mult128bitPow10(ml, e2, q) - dc, _, dc0 = mult128bitPow10(mc, e2, q) - du, e2, du0 = mult128bitPow10(mu, e2, q) - } - if e2 >= 0 { - panic("not enough significant bits after mult128bitPow10") - } - // Is it an exact computation? - if q > 55 { - // Large positive powers of ten are not exact - dl0, dc0, du0 = false, false, false - } - if q < 0 && q >= -24 { - // Division by a power of ten may be exact. - // (note that 5^25 is a 59-bit number so division by 5^25 is never exact). - if divisiblePow5(ml, -q) { - dl0 = true - } - if divisiblePow5(mc, -q) { - dc0 = true - } - if divisiblePow5(mu, -q) { - du0 = true - } - } - // Express the results (dl, dc, du)*2^e2 as integers. - // Extra bits must be removed and rounding hints computed. - extra := uint(-e2) - extraMask := uint64(1<>extra, dl&extraMask - dc, fracc := dc>>extra, dc&extraMask - du, fracu := du>>extra, du&extraMask - // Is it allowed to use 'du' as a result? - // It is always allowed when it is truncated, but also - // if it is exact and the original binary mantissa is even - // When disallowed, we can subtract 1. - uok := !du0 || fracu > 0 - if du0 && fracu == 0 { - uok = mant&1 == 0 - } - if !uok { - du-- - } - // Is 'dc' the correctly rounded base 10 mantissa? - // The correct rounding might be dc+1 - cup := false // don't round up. - if dc0 { - // If we computed an exact product, the half integer - // should round to next (even) integer if 'dc' is odd. - cup = fracc > 1<<(extra-1) || - (fracc == 1<<(extra-1) && dc&1 == 1) - } else { - // otherwise, the result is a lower truncation of the ideal - // result. - cup = fracc>>(extra-1) == 1 - } - // Is 'dl' an allowed representation? - // Only if it is an exact value, and if the original binary mantissa - // was even. - lok := dl0 && fracl == 0 && (mant&1 == 0) - if !lok { - dl++ - } - // We need to remember whether the trimmed digits of 'dc' are zero. - c0 := dc0 && fracc == 0 - // render digits - ryuDigits(d, dl, dc, du, c0, cup) - d.dp -= q -} - -// computeBounds returns a floating-point vector (l, c, u)×2^e2 -// where the mantissas are 55-bit (or 26-bit) integers, describing the interval -// represented by the input float64 or float32. -func computeBounds(mant uint64, exp int, flt *floatInfo) (lower, central, upper uint64, e2 int) { - if mant != 1< 5e8) || (clo == 5e8 && cup) - ryuDigits32(d, lhi, chi, uhi, c0, cup, 8) - d.dp += 9 - } else { - d.nd = 0 - // emit high part - n := uint(9) - for v := chi; v > 0; { - v1, v2 := v/10, v%10 - v = v1 - n-- - d.d[n] = byte(v2 + '0') - } - d.d = d.d[n:] - d.nd = int(9 - n) - // emit low part - ryuDigits32(d, llo, clo, ulo, - c0, cup, d.nd+8) - } - // trim trailing zeros - for d.nd > 0 && d.d[d.nd-1] == '0' { - d.nd-- - } - // trim initial zeros - for d.nd > 0 && d.d[0] == '0' { - d.nd-- - d.dp-- - d.d = d.d[1:] - } -} - -// ryuDigits32 emits decimal digits for a number less than 1e9. -func ryuDigits32(d *decimalSlice, lower, central, upper uint32, - c0, cup bool, endindex int) { - if upper == 0 { - d.dp = endindex + 1 - return - } - trimmed := 0 - // Remember last trimmed digit to check for round-up. - // c0 will be used to remember zeroness of following digits. - cNextDigit := 0 - for upper > 0 { - // Repeatedly compute: - // l = Ceil(lower / 10^k) - // c = Round(central / 10^k) - // u = Floor(upper / 10^k) - // and stop when c goes out of the (l, u) interval. - l := (lower + 9) / 10 - c, cdigit := central/10, central%10 - u := upper / 10 - if l > u { - // don't trim the last digit as it is forbidden to go below l - // other, trim and exit now. - break - } - // Check that we didn't cross the lower boundary. - // The case where l < u but c == l-1 is essentially impossible, - // but may happen if: - // lower = ..11 - // central = ..19 - // upper = ..31 - // and means that 'central' is very close but less than - // an integer ending with many zeros, and usually - // the "round-up" logic hides the problem. - if l == c+1 && c < u { - c++ - cdigit = 0 - cup = false - } - trimmed++ - // Remember trimmed digits of c - c0 = c0 && cNextDigit == 0 - cNextDigit = int(cdigit) - lower, central, upper = l, c, u - } - // should we round up? - if trimmed > 0 { - cup = cNextDigit > 5 || - (cNextDigit == 5 && !c0) || - (cNextDigit == 5 && c0 && central&1 == 1) - } - if central < upper && cup { - central++ - } - // We know where the number ends, fill directly - endindex -= trimmed - v := central - n := endindex - for n > d.nd { - v1, v2 := v/100, v%100 - d.d[n] = smalls[2*v2+1] - d.d[n-1] = smalls[2*v2+0] - n -= 2 - v = v1 - } - if n == d.nd { - d.d[n] = byte(v + '0') - } - d.nd = endindex + 1 - d.dp = d.nd + trimmed -} - -// mult64bitPow10 takes a floating-point input with a 25-bit -// mantissa and multiplies it with 10^q. The resulting mantissa -// is m*P >> 57 where P is a 64-bit truncated power of 10. -// It is typically 31 or 32-bit wide. -// The returned boolean is true if all trimmed bits were zero. -// -// That is: -// -// m*2^e2 * round(10^q) = resM * 2^resE + ε -// exact = ε == 0 -func mult64bitPow10(m uint32, e2, q int) (resM uint32, resE int, exact bool) { - if q == 0 { - // P == 1<<63 - return m << 6, e2 - 6, true - } - pow, exp2, ok := pow10(q) - if !ok { - // This never happens due to the range of float32/float64 exponent - panic("mult64bitPow10: power of 10 is out of range") - } - if q < 0 { - // Inverse powers of ten must be rounded up. - pow.Hi++ - } - hi, lo := bits.Mul64(uint64(m), pow.Hi) - e2 += exp2 - 64 + 57 - return uint32(hi<<7 | lo>>57), e2, lo<<7 == 0 -} - -// mult128bitPow10 takes a floating-point input with a 55-bit -// mantissa and multiplies it with 10^q. The resulting mantissa -// is m*P >> 119 where P is a 128-bit truncated power of 10. -// It is typically 63 or 64-bit wide. -// The returned boolean is true is all trimmed bits were zero. -// -// That is: -// -// m*2^e2 * round(10^q) = resM * 2^resE + ε -// exact = ε == 0 -func mult128bitPow10(m uint64, e2, q int) (resM uint64, resE int, exact bool) { - if q == 0 { - // P == 1<<127 - return m << 8, e2 - 8, true - } - pow, exp2, ok := pow10(q) - if !ok { - // This never happens due to the range of float32/float64 exponent - panic("mult128bitPow10: power of 10 is out of range") - } - if q < 0 { - // Inverse powers of ten must be rounded up. - pow.Lo++ - } - e2 += exp2 - 128 + 119 - - hi, mid, lo := umul192(m, pow) - return hi<<9 | mid>>55, e2, mid<<9 == 0 && lo == 0 -} From 89f6dba7e6a52f14f269349e1dd0d342aa132e1a Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Tue, 11 Nov 2025 05:47:14 -0800 Subject: [PATCH 037/140] internal/strconv: add testbase tests Add ability to test against inputs chosen by from the stress tests computed by Vern Paxson's testbase program. Checked that 'go test -testbase' passes. Change-Id: I81057e55df6cd369b40ce623a59884e6ead0ed76 Reviewed-on: https://go-review.googlesource.com/c/go/+/719620 Reviewed-by: Alan Donovan Auto-Submit: Russ Cox LUCI-TryBot-Result: Go LUCI --- src/internal/strconv/fp_test.go | 154 +++- src/internal/strconv/ftoa_test.go | 7 + src/internal/strconv/testdata/README | 27 + src/internal/strconv/testdata/atof1k.txt | 1000 ++++++++++++++++++++++ src/internal/strconv/testdata/ftoa1k.txt | 1000 ++++++++++++++++++++++ src/internal/strconv/testdata/testfp.txt | 2 +- 6 files changed, 2174 insertions(+), 16 deletions(-) create mode 100644 src/internal/strconv/testdata/README create mode 100644 src/internal/strconv/testdata/atof1k.txt create mode 100644 src/internal/strconv/testdata/ftoa1k.txt diff --git a/src/internal/strconv/fp_test.go b/src/internal/strconv/fp_test.go index ba739941cc8..92a663c6e33 100644 --- a/src/internal/strconv/fp_test.go +++ b/src/internal/strconv/fp_test.go @@ -5,10 +5,13 @@ package strconv_test import ( - "bufio" _ "embed" + "flag" "fmt" - "internal/strconv" + . "internal/strconv" + "io" + "net/http" + "os" "strings" "testing" ) @@ -25,15 +28,15 @@ func pow2(i int) float64 { return pow2(i/2) * pow2(i-i/2) } -// Wrapper around strconv.ParseFloat(x, 64). Handles dddddp+ddd (binary exponent) -// itself, passes the rest on to strconv.ParseFloat. +// Wrapper around ParseFloat(x, 64). Handles dddddp+ddd (binary exponent) +// itself, passes the rest on to ParseFloat. func myatof64(s string) (f float64, ok bool) { if mant, exp, ok := strings.Cut(s, "p"); ok { - n, err := strconv.ParseInt(mant, 10, 64) + n, err := ParseInt(mant, 10, 64) if err != nil { return 0, false } - e, err1 := strconv.Atoi(exp) + e, err1 := Atoi(exp) if err1 != nil { println("bad e", exp) return 0, false @@ -61,7 +64,7 @@ func myatof64(s string) (f float64, ok bool) { } return v * pow2(e), true } - f1, err := strconv.ParseFloat(s, 64) + f1, err := ParseFloat(s, 64) if err != nil { return 0, false } @@ -72,19 +75,19 @@ func myatof64(s string) (f float64, ok bool) { // itself, passes the rest on to strconv.ParseFloat. func myatof32(s string) (f float32, ok bool) { if mant, exp, ok := strings.Cut(s, "p"); ok { - n, err := strconv.Atoi(mant) + n, err := Atoi(mant) if err != nil { println("bad n", mant) return 0, false } - e, err1 := strconv.Atoi(exp) + e, err1 := Atoi(exp) if err1 != nil { println("bad p", exp) return 0, false } return float32(float64(n) * pow2(e)), true } - f64, err1 := strconv.ParseFloat(s, 32) + f64, err1 := ParseFloat(s, 32) f1 := float32(f64) if err1 != nil { return 0, false @@ -96,9 +99,9 @@ func myatof32(s string) (f float32, ok bool) { var testfp string func TestFp(t *testing.T) { - s := bufio.NewScanner(strings.NewReader(testfp)) - for lineno := 1; s.Scan(); lineno++ { - line := s.Text() + lineno := 0 + for line := range strings.Lines(testfp) { + lineno++ line, _, _ = strings.Cut(line, "#") line = strings.TrimSpace(line) if line == "" { @@ -133,7 +136,128 @@ func TestFp(t *testing.T) { t.Errorf("testdata/testfp.txt:%d: %s %s %s %s: have %s want %s", lineno, a[0], a[1], a[2], a[3], s, a[3]) } } - if s.Err() != nil { - t.Fatal("testfp: read testdata/testfp.txt: ", s.Err()) +} + +// The -testbase flag runs the full testbase input set instead of the +// random sample in testdata/*1k.txt. See testdata/README for details. +var testbase = flag.Bool("testbase", false, "download and test full testbase testdata") + +// testbaseURL is the URL for downloading the full testbase testdata. +// There is also a copy on "https://swtch.com/testbase/". +var testbaseURL = "https://gist.githubusercontent.com/rsc/606b378b0bf95c24a6fd6cef99e262e1/raw/128a03890e536bdf403e6cc768b0737405c6734d/" + +//go:embed testdata/atof1k.txt +var atof1ktxt string + +//go:embed testdata/ftoa1k.txt +var ftoa1ktxt string + +// openTestbase opens the named testbase data file. +// By default it opens testdata/name1k.txt, +// but if the -testbase flag has been set, +// then it opens the full testdata/name.txt, +// downloading that file if necessary. +func openTestbase(t *testing.T, name string) (file, data string) { + if !*testbase { + switch name { + case "atof": + return "testdata/atof1k.txt", atof1ktxt + case "ftoa": + return "testdata/ftoa1k.txt", ftoa1ktxt + } + t.Fatalf("unknown file %s", name) + } + + // Use cached copy if present. + file = "testdata/" + name + ".txt" + if data, err := os.ReadFile(file); err == nil { + return file, string(data) + } + + // Download copy. + url := testbaseURL + name + ".txt" + resp, err := http.Get(url) + if err != nil { + t.Fatalf("%s: %s", url, err) + } + if resp.StatusCode != 200 { + t.Fatalf("%s: %s", url, resp.Status) + } + bytes, err := io.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + t.Fatalf("%s: %s", url, err) + } + if err := os.WriteFile(file, bytes, 0666); err != nil { + t.Fatal(err) + } + return file, string(bytes) +} + +func TestParseFloatTestdata(t *testing.T) { + // Test testbase inputs, optimized against not. + name, data := openTestbase(t, "atof") + fail := 0 + lineno := 0 + for line := range strings.Lines(data) { + lineno++ + s := strings.TrimSpace(line) + if strings.HasPrefix(s, "#") || s == "" { + continue + } + SetOptimize(false) + want, err1 := ParseFloat(s, 64) + SetOptimize(true) + have, err2 := ParseFloat(s, 64) + if err1 != nil { + // Error in test data; should not happen. + t.Errorf("%s:%d: ParseFloat(%#q): %v", name, lineno, s, err1) + continue + } + if err2 != nil { + t.Errorf("ParseFloat(%#q): %v", s, err2) + if fail++; fail > 100 { + t.Fatalf("too many failures") + } + continue + } + if have != want { + t.Errorf("ParseFloat(%#q) = %#x, want %#x", s, have, want) + if fail++; fail > 100 { + t.Fatalf("too many failures") + } + } + } +} + +func TestFormatFloatTestdata(t *testing.T) { + // Test testbase inputs, optimized against not. + name, data := openTestbase(t, "ftoa") + fail := 0 + lineno := 0 + for line := range strings.Lines(data) { + lineno++ + s := strings.TrimSpace(line) + if strings.HasPrefix(s, "#") || s == "" { + continue + } + f, err := ParseFloat(s, 64) + if err != nil { + // Error in test data; should not happen. + t.Errorf("%s:%d: ParseFloat(%#q): %v", name, lineno, s, err) + continue + } + for i := range 19 { + SetOptimize(false) + want := FormatFloat(f, 'e', i, 64) + SetOptimize(true) + have := FormatFloat(f, 'e', i, 64) + if have != want { + t.Errorf("FormatFloat(%#x, 'e', %d) = %s, want %s", f, i, have, want) + if fail++; fail > 100 { + t.Fatalf("too many failures") + } + } + } } } diff --git a/src/internal/strconv/ftoa_test.go b/src/internal/strconv/ftoa_test.go index b3c7904eb22..d5179a1bbd8 100644 --- a/src/internal/strconv/ftoa_test.go +++ b/src/internal/strconv/ftoa_test.go @@ -206,10 +206,17 @@ var ftoatests = []ftoaTest{ {8393378656576888. * (1 << 1), 'e', 15, "1.678675731315378e+16"}, {8738676561280626. * (1 << 4), 'e', 16, "1.3981882498049002e+17"}, {8291032395191335. / (1 << 30), 'e', 5, "7.72163e+06"}, + {8880392441509914. / (1 << 80), 'e', 16, "7.3456884594794477e-09"}, // Exercise divisiblePow5 case in fixedFtoa {2384185791015625. * (1 << 12), 'e', 5, "9.76562e+18"}, {2384185791015625. * (1 << 13), 'e', 5, "1.95312e+19"}, + + // Exercise potential mistakes in fixedFtoa. + // Found by introducing mistakes and running 'go test -testbase'. + {0x1.000000000005p+71, 'e', 16, "2.3611832414348645e+21"}, + {0x1.0000p-27, 'e', 17, "7.45058059692382812e-09"}, + {0x1.0000p-41, 'e', 17, "4.54747350886464119e-13"}, } func TestFtoa(t *testing.T) { diff --git a/src/internal/strconv/testdata/README b/src/internal/strconv/testdata/README new file mode 100644 index 00000000000..4219189c450 --- /dev/null +++ b/src/internal/strconv/testdata/README @@ -0,0 +1,27 @@ +testfp.txt contains conversion tests from Vern Paxson's paper +“A Program for Testing IEEE Decimal-Binary Conversion” +https://www.icir.org/vern/papers/testbase-report.pdf + +That paper from 1991 describes a tester called 'testbase', +written in non-64-bit-safe pre-ANSI C. +As of 2025, it is still available at ftp://ftp.ee.lbl.gov/testbase.tar.Z. + +The files + + https://swtch.com/testbase/atof.txt + https://swtch.com/testbase/ftoa.txt + +are the test inputs that testbase generates and checks, +logged during an actual run, totaling about 10 MB. + +The files atof1k.txt and ftoa1k.txt in this directory each contain +1000 random samples of the full trace. They are used during +'go test internal/strconv'. + +Running 'go test internal/strconv -testbase' downloads the +complete files into this directory as atof.txt and ftoa.txt and +then uses those instead of the sampled versions. +The complete tests take about 10 seconds on a Macbook Pro. + +Backup copies of the files are also posted at +https://gist.github.com/rsc/606b378b0bf95c24a6fd6cef99e262e1. diff --git a/src/internal/strconv/testdata/atof1k.txt b/src/internal/strconv/testdata/atof1k.txt new file mode 100644 index 00000000000..d6847674bda --- /dev/null +++ b/src/internal/strconv/testdata/atof1k.txt @@ -0,0 +1,1000 @@ +-0.00000819394172599 +-1.0000000000000000e-118 +-1.0000000000000000e13 +-1.0000000000000001e-306 +-1.0000000000000002e-169 +-1.0000000000000003e-224 +-1.0000000000000005e-119 +-1.0000000000000007e269 +-1.0000000000000015e-162 +-1.0000000000000053e94 +-1.0000000000000070e-209 +-1.0000000000000074e-272 +-1.0000000000000151e258 +-1.0000000000000157e-87 +-1.0000000000000244e305 +-1.0000000000000247e109 +-1.0000000000000259e22 +-1.0000000000000374e-45 +-1.0000000000001310e265 +-1.0000000000012207e-264 +-1.0000000000018181e-99 +-1.0000000000020219e55 +-1.0000000000043295e-197 +-1.0000000000060985e127 +-1.0000000000063610e-282 +-1.0000000000138745e-132 +-1.0000000000178366e110 +-1.0000000000199157e222 +-1.0000000000524390e231 +-1.0000000001128339e55 +-1.0000000001257625e-78 +-1.0000000001841019e-24 +-1.0000000002042513e156 +-1.0000000002178947e296 +-1.0000000005252450e183 +-1.0000000007341792e-222 +-1.0000000007557179e30 +-1.0000000009130913e27 +-1.0000000009286410e-181 +-1.0000000012612041e-9 +-1.0000000017686445e307 +-1.0000000024539642e-87 +-1.0000000036313613e213 +-1.0000000051875403e-192 +-1.0000000068280381e-167 +-1.0000000137737877e-296 +-1.0000000203827849e212 +-1.0000000656113395e-288 +-1.0000001250270252e137 +-1.0000006092667001e125 +-1.0000012347036108e144 +-1.0000012879627292e137 +-1.0000031438588004e48 +-1.0000048347147956e-164 +-1.0000055132235515e119 +-1.0000062265721875e269 +-1.0000076400516098e-177 +-1.0000102462401065e-3 +-1.0000304177511984e-249 +-1.0000377132796035e-229 +-1.0000676698921361e256 +-1.0001178249557717e295 +-1.0001503824641194e-157 +-1.0001564810441064e-26 +-1.0002740230158000e-172 +-1.0003060625489550e-271 +-1.0005855984239872e-206 +-1.0007762872442528e-243 +-1.0014681940548644e101 +-1.0017296015751174e201 +-1.0027291406758574e85 +-1.0073573099051029e161 +-1.0107936573001087e-174 +-1.0110889712100324e-28 +-1.0184517875750861e-217 +-1.0194655318257447e177 +-1.0218711930174675e295 +-1.0247852029469519e51 +-1.0250665447340628e-143 +-1.0253678480248741e-159 +-1.0261342003245940e-289 +-1.0363864972853306e218 +-1.0451361413042084e-199 +-1.0520271803103380e211 +-1.0531040860997630e282 +-1.0553380643526282e-227 +-1.0565890622720884e270 +-1.0604518446766494e+37 +-1.0645421971700518e259 +-1.0668823097098311e242 +-1.0680005900413916e96 +-1.0700955781678005e118 +-1.0773684869589011e+219 +-1.0795878591039497e+281 +-1.0806454419566787e-224 +-1.0877724690253475e186 +-1.0894591358125493e40 +-1.0900170253925623e196 +-1.0900377190427586e-106 +-1.0984615428606908e-11 +-1.1102230246252072e-16 +-1.1115396975891537e+123 +-1.1166118190794353e-119 +-1.1307821286128172e74 +-1.1331388749452486e-218 +-1.1345252186628974e279 +-1.1392378155556928e-305 +-1.1411581581217047e279 +-1.1491393400802991e-187 +-1.1559948446936695e-82 +-1.1629419594362053e136 +-1.1704191077875603e-97 +-1.1844224803261949e-183 +-1.1971601492124910e257 +-1.1985000301749824e-196 +-1.2114457794496854e-268 +-1.2187105741089266e288 +-1.2194331667323035e142 +-1.2216508887579013e-4 +-1.2285516299477129e-237 +-1.2325954958382788e-32 +-1.2392294127518073e-119 +-1.2407816828843072e-119 +-1.2446030893156224e-60 +-1.2498378540275245e145 +-1.2608641982846246e117 +-1.2702926122620307e-262 +-1.2994262207056140e-113 +-1.3107200000003686e5 +-1.3221359453684107e123 +-1.3336739821276408e241 +-1.3393857589828343e300 +-1.3407807959789255e154 +-1.3449767578629110e-284 +-1.3772540105163118e-281 +-1.3862823061691740e66 +-1.4053426107917795e+112 +-1.4061424920421403e+60 +-1.4114154106508960e-132 +-1.4257626930069368e191 +-1.4257627542520628e191 +-1.4302223338085472e-247 +-1.4338241998255733e-265 +-1.4426529102360446e-129 +-1.4521649497546632e281 +-1.4630503047069520e-98 +-1.4850810903809023e-142 +-1.4961399611738038e-262 +-1.4969245099707719e-154 +-1.5061999509545513e-182 +-1.5080088329780567e104 +-1.5161842617979498e228 +-1.5309010345804195e+200 +-1.5371825583508312e-238 +-1.5596686845759136e+254 +-1.5749223551297590e-221 +-1.5829145694285486e175 +-1.6035500713562762e-176 +-1.6155876674652461e-27 +-1.6199525161564176e216 +-1.6437139998502656e-90 +-1.6543447510093434e-107 +-1.6905424996345524e271 +-1.7236413322193712e215 +-1.7329185643784573e128 +-1.7380954013562253e-201 +-1.7404329748619822e+187 +-1.7500663835588639e+259 +-1.7628851326804975e-279 +-1.7650087249736553e218 +-1.8189945292645253e-12 +-1.8371266456347602e-03 +-1.8485190408855856e-273 +-1.8588035574996465e283 +-1.8665339810511912e-301 +-1.8738346896762349e-287 +-1.8782726813068610e-155 +-1.8783480629202137e+134 +-1.8832962494497295e+203 +-1.8870787131460155e168 +-1.9167254572097985e+156 +-1.9176348546142558e-93 +-1.9254938881734810e199 +-1.9259309530639721e-34 +-1.9362959574246592e-121 +-1.9392092330729084e+32 +-1.9670265800610649e+232 +-1.9670401943221783e-236 +-1.9680505079406295e261 +-1.9721522673565606e-31 +-1.9723571388903490e-31 +-1.9797340704075851e246 +-1.9982456802088651e151 +-1000723016793534037834995379097202132341693333075540458367973324541104253082625595095039027033274909301001197110542003644198024616631930719556830883128473939523485225049995061312790159809447959751263166669043968721944984869923565734628771642242074203530365792315179431624703.99999999999999998 +-10689966.03058851324021815 +-1100139488982922738834289740730418934296138626575391260672.00000000000000000 +-11846475518374777493154032103506632631202296283574585908967721825819890197073805003681517155675602669975477895947990646090662423982350785053194421615262128894112568019869117573168211123310902151863554107506688.00000000000000002 +-12472531819875696640.00000000000000002 +-127205938999984661418248590729202649063483946840892894661174977372504374375401503529250247457077452802057952432635425189681594718126566119833599.99999999999999998 +-1506182320534417286856590521159502169552665588932047572031032814818711444697621745052224892530833528388620091723950795733033751075407842220483765425843480214594713100658397226645524839930053787647.99999999999999999 +-154242113935917954537595572992312328107713327935533153522607629848787282733282521638565892531640726676005938023692209423053124061604719444748459059930631295347724490342425212246406179387095658946517844504309723143521877282505423633962935368444362506299272925119209682961522214337004000860176383.99999999999999998 +-1771333706527622204801219358859145186933358304799227904.00000000000000001 +-18288500159366410423662280704.00000000000000002 +-1835416865419945821827909474316425227203489366807216407288090925884590342243006481057945448176877157769960385194004092711352645394008847531268106578411464492070699609366493299706338303979750736794570030222350249007587423652532193316948488153103416642836141530407392121845579919402863454846976.00000000000000000 +-1913353075029172287762512079706658192686245139971850034512663321657091065978481154591244298244715652022590231628469165025936324275055318217865000428515800882808237013912926441600621216013377020680729501106175.99999999999999998 +-199064274911907744.00000000000000000 +-2.0173827180628887e118 +-2.0218430483364897e-174 +-2.0261306488679542e177 +-2.0349165186358120e236 +-2.0391576462595674e-56 +-2.0547405865423350e208 +-2.0822879481779269e-144 +-2.0873858787704865e+196 +-2.0925231841407906e-176 +-2.1062566095145223e65 +-2.1488523056330681e+187 +-2.1568814515592454e+269 +-2.1638944010275404e273 +-2.1661481985318907e127 +-2.1974110227820779e+50 +-2.1984666253918596e304 +-2.2013136429275873e-134 +-2.2195242053863557e276 +-2.2444127733852618e-190 +-2.2633062701940334e+23 +-2.2662777505598332e-218 +-2.2761049594727194e-159 +-2.2958884535716606e-41 +-2.3272039434737061e+162 +-2.3283064368392154e-10 +-2.3509887153452340e-38 +-2.3540454113733815e+151 +-2.3738919364399510e-66 +-2.4014828559570531e-240 +-2.4250123656006512e-92 +-2.4258098409972255e229 +-2.4432452418567516e+25 +-2.4439592799881548e-150 +-2.4464945801831953e-296 +-2.4733061489451302e173 +-2.4733103065296330e173 +-2.4866161820490014e86 +-2.4888729169312240e257 +-2.5052122375152283e-293 +-2.5108406942289848e58 +-2.5134560567177559e-88 +-2.5191046295150400e263 +-2.5315339359873020e-16 +-2.5379418394182237e-116 +-2.5653355008114854e-290 +-2.5876317516494062e-172 +-2.5934472305510476e179 +-2.5934478284611590e179 +-2.5965112085943846e+230 +-2.5988577850285964e-113 +-2.6074060506745971e92 +-2.6129273918414988e-200 +-2.6382945360271677e-228 +-2.6854635827575890e+277 +-2.7222589353675207e39 +-2.7664523314090336e-222 +-2.7755863546485759e303 +-2.7875931498321466e42 +-2.8118211215895018e160 +-2.8199004184636637e+179 +-2.8298997121334359e-73 +-2.8362596673541728e278 +-2.8451314701236690e-160 +-2.8851703848476622e142 +-2.8883110013837277e-275 +-2.9433943453038452e-162 +-2.9625201359906805e-221 +-2.9642774844760387e79 +-2.9842443787005975e-278 +-2.9881687549691105e-300 +-2.9993936277912729e-241 +-2.9997322051968669e-241 +-2011089691479306774035386587566463314913075849896920272594189035769527336663228626034094956590822216384275223825438736384.00000000000000001 +-21661064190104792139036249094355967405715297178368033011582618854128246528337381558844135464018754518548539439540998564584100014238068159330978394668298330886605103268387031905727118791671808.00000000000000001 +-21756777117802916291377544477335301099243908152024474620697474304810976090862267970285038625936568623209396150995230717378559.99999999999999998 +-224251152838407924079491459686319348502925615552014678821129881535407705961601756544618009296105114898411083004044846122512056591478164696007295993507804115917285426785741486691678114442127469286984672870400.00000000000000000 +-236980758563984794879174397959962834990829517353158093035516720079599292384550709467025506303.99999999999999998 +-3.0027780729547298e91 +-3.0030068099491618e256 +-3.0073102263475842e-95 +-3.0111386918904280e-154 +-3.0454106285630275e287 +-3.0517578169067997e-5 +-3.0549370567339579e-151 +-3.0649910817317780e54 +-3.0681834944310330e-92 +-3.0727275046881117e-92 +-3.0916300184138077e172 +-3.1163812501609675e-207 +-3.1250000000000004e-2 +-3.1295427272938514e+151 +-3.1404513161707658e-148 +-3.1540701437499392e-55 +-3.1733451975416556e-89 +-3.2733906078961495e150 +-3.3232565422208806e122 +-3.3444356521734996e-198 +-3.3846065602176859e125 +-3.4175792574734641e97 +-3.4580654142612909e-223 +-3.5158917038924799e+221 +-3.5221018286847411e-133 +-3.5263063197031244e+05 +-3.5373746921405982e-74 +-3.5618238155840791e-307 +-3.5801584930829642e+260 +-3.6222718257120196e-71 +-3.6341936214780397e134 +-3.6341937444352399e134 +-3.7092061506874218e-68 +-3.7166143387846012e+165 +-3.7330550035424388e-301 +-3.7654997975871829e-183 +-3.7655943600996420e-183 +-3.7709583301097453e-301 +-3.7857618983696588e-124 +-3.7903273737810323e227 +-3.7942756039314077e81 +-3.8226477813891846e-298 +-3.8323994192404670e-71 +-3.8380092462712086e-233 +-3.8558737215564027e-180 +-3.8645375230279670e171 +-3.8725919148493187e-121 +-3.8725936821277545e-121 +-3.8766254036313033e-267 +-3.9237102026692406e-58 +-3.9313652158187841e-236 +-3.9656734172441988e-118 +-3.9917457456324272e+131 +-3221714150872568231717312512739869355813147067038302643059305896461643336148095730611220724624546021728530529514741016755299504070717722431780734540383583205400568927593057475055823761112419831777524640120831.99999999999999999 +-3242968614098390614392827624049446943180287456919258888410193728179150651840561401389553906098435231143142603433335752505540410090136365013601579445367016041177696079284266374529010431241249307437103046676000581993658023425085879768147067795047765656021126333928905577840271055650194127646912109608960.00000000000000001 +-335400797299085278224550814498799182930461589504.00000000000000002 +-35286118482790496048024152237383389957358364310327020545932848993310392152082176055369518354779052392807070711289942214341381351371205730946066870369676688268390735548092294258697418485319980024059741251373910194336886290894093802889063432192.00000000000000000 +-372230331926687316958802730505255142107260357976037822801816527675652867768795625020540548801943011120646534413413009228099237580589453836656857542059791890167031045099140475668066887149522899847929571914283350921276321331797922702677844732548446598704063220181714214274377711615.99999999999999999 +-37674900992465998685923099894039678864574100322374156871559343441667821489922437654925800692832373505686310450093422392634289062666697650474012710244336451411408459479213748611627408330869724523272266914268489905757653151539950664832467723205183502153732003714841667127556425353468575744.00000000000000002 +-4.0305674067374219e264 +-4.0347656653639492e118 +-4.0473857707314917e-320 +-4.0598513237949676e54 +-4.0783153922211762e-56 +-4.1071021240708678e207 +-4.1094811942226636e208 +-4.1180460715756251e-84 +-4.1214571998568817e+187 +-4.1686028807165680e-207 +-4.1761948595190608e-53 +-4.1855804968302923e298 +-4.2330845495000107e-32 +-4.2860363289117792e301 +-4.3135914667443854e68 +-4.3368086899420182e-19 +-4.3556142965887285e40 +-4.4501477170144030e-308 +-4.4989208546517718e161 +-4.5019668289993685e-99 +-4.5278395394133567e-72 +-4.5325554997820119e-218 +-4.5477343028127467e-13 +-4.5588941028350998e+139 +-4.5767114681884880e-246 +-4.6865905838642075e-243 +-4.7068747365302991e-184 +-4.7174530310269272e167 +-4.7322087446671004e-271 +-4.7428439751604719e80 +-4.7629966423126188e+305 +-4.7940365910968541e-94 +-4.7963220106395224e-234 +-4.8702989943235266e+202 +-4.8777321179563408e142 +-4.9039857307715015e55 +-4.9040119724151232e55 +-4.9252507745493192e114 +-4.9784122248897233e-60 +-47027937241.921019 +-489386180361331583.99999999999999998 +-5.0321474762477632e-234 +-5.0321474773580906e-234 +-5.1036713304975857e+225 +-5.1118485587594315e294 +-5.1626267643551890e61 +-5.1910704244216346e+36 +-5.2538071061427221e-287 +-5.2656145834278599e64 +-5.2765920629406940e-228 +-5.2971463532868544e+145 +-5.3224498000102008e-110 +-5.3799155802472393e-284 +-5.4097359990968575e272 +-5.4238339233078879e-273 +-5.4388530464491459e185 +-5.4882250169064633e+262 +-5.5395696628011741e275 +-5.5552900977344411e+250 +-5.5751862996330315e42 +-5.6195581564443140e+57 +-5.6196106292069341e-178 +-5.6412489526677889e-278 +-5.7149369567141850e-101 +-5.7337465426386587e104 +-5.7706996064226712e+100 +-5.7766220027728861e-275 +-5.7899102883249526e76 +-5.7972823131292720e+125 +-5.8346095642605634e291 +-5.8520954520121543e-98 +-5.8652488376576363e253 +-5.8904385464229671e+177 +-5.9052281868458261e+18 +-5.9223865215333377e225 +-5.9666534264410662e284 +-5.9863107065091655e51 +-5.9885206393200962e-8 +-5.9925457340060139e-95 +-50631767574206273111398199815925280245212067713240118921652941302797319903805425150130155404838664269029730983553626879567902972619252854783258008448701698129049666253920874955860562095292665767444157011267856216687539390159168500325373148395661360779527864941804551546686992578311005968247635836927.99999999999999998 +-6.0065774398044362e256 +-6.0196833915438315e+239 +-6.0446290980740807e23 +-6.0572271931738865e-269 +-6.0572370067179759e-269 +-6.1299821638559221e54 +-6.1693949837636328e-179 +-6.1832604031778164e172 +-6.2075916273746976e-300 +-6.2746875923976968e203 +-6.2836396355810906e-89 +-6.2977615970544888e262 +-6.3382530011425959e29 +-6.3448545956260820e-117 +-6.3640996661242644e-192 +-6.5038981755303326e-260 +-6.6036902721819479e268 +-6.6517609002559370e265 +-6.6600462088715267e-257 +-6.7329739539821401e212 +-6.8127357440189582e-108 +-6.8351585238343743e97 +-6.8435218434149841e+180 +-6.8510918716099656e-195 +-6.9529416513224266e-164 +-6.9617318994480557e187 +-6.9919215437301718e246 +-6.9992023444261474e100 +-601288067969656142524634186651015087849705532496165825309714355361255082726709246866729438232288051281275607040044836238927171595283958319132599729269121421339447520281093172806369279272571641956659388559039129333609955593563700917914217287254015.99999999999999998 +-6178374573583523343804204251071617897834844530665772902321753065660667462631520554766423268636642251126407186813012073502992913187685768569519985937774363636790389567325506268174375898082575569125375.99999999999999999 +-61896048272358644187123845600367843178118230083009576369443650683647654953216575207485682270077492156316424380042788152913873162123345919.99999999999999998 +-6498192273090010308060956053247255201071459272859855423562297173589759554245926013741930622498017064677117820358333495114441623800011595763746778672627979297338404138463588282411349227507171380878316141765075970245611066190987263.99999999999999998 +-7.0547949227833642e+114 +-7.1362545977443677e44 +-7.2132645451693465e-130 +-7.2833951700007054e+190 +-7.3710203621007968e165 +-7.5589418491745561e-202 +-7.5806594632656575e227 +-7.6041005265888359e-275 +-7.6805995824117848e+294 +-7.6876972326960129e+258 +-7.6957043352333215e112 +-7.7706755689029163e84 +-7.8043886230886035e143 +-7.8463815926304028e56 +-7.8886559990623734e-31 +-7.8915761509955232e-36 +-7.9145728609616277e174 +-7.9166862688373622e174 +-7.9488926325805149e233 +-7.9571717825565863e87 +-767277995718003774770839016447586679154511011184785171625332719324265738634288430069660077124485120.00000000000000001 +-8.0166734400358911e-292 +-8.0514359626336370e-233 +-8.0553920873495449e-233 +-8.0695314944604797e118 +-8.1071198426586951e+34 +-8.1214409832916855e-115 +-8.1366158110078354e-146 +-8.2189623461802862e208 +-8.2189634878607472e208 +-8.2275227866062266e62 +-8.2912479881481722e-53 +-8.3249912587959475e-258 +-8.3798799563618010e152 +-8.4615164072784126e124 +-8.4791517238087317e-168 +-8.5247895887248960e-255 +-8.5533941091368719e+146 +-8.6000360776847096e-137 +-8.6222747319973097e214 +-8.6451747326703224e-224 +-8.6645927941275474e127 +-8.7293852695009602e-252 +-8.7734970849817881e194 +-8.7844080698834549e-65 +-8.8633114605015138e276 +-845067907891123480287255592746572528204342976094481678389689414331809021145188968802401139999564925666443726178256016076546420094154265591807.99999999999999999 +-858099029106788816317436469101754624892960966275479187273376854254394945900569787545911218973675582031554731100610059195458998402108235256368123275547043731881540222857302211351774620600156370273371714713704414024180961421011284015579044457250066257363865653522523488255.99999999999999999 +-9.0007138248002011e-44 +-9.0556790788269426e-72 +-9.0882063039984683e+226 +-9.1194693702437450e-246 +-9.2329786177857358e-128 +-9.3844835694634575e254 +-9.4447329657392905e21 +-9.4447329657393481e21 +-9.5864108690786900e+108 +-9.5980596135006976e-240 +-9.8094518980521739e189 +-9.9729108093786329e+175 +-914278489467605196711776190699156809707611307018061536644806881168528095090661010721125292932331722749008583575203629530383848546021089414409853861257385864198036127744.00000000000000002 +-93480204946649186516058277333707595717563406600054621547323710048419001110226634096836532299927950196736.00000000000000002 +-9585261338315720501128964588933724618007327729250939661086508321604130114966832686019967617287520361216493032630268287630965295079998398422210489836223539970048.00000000000000002 ++1.0000000000000000e-95 ++1.0000000000000000e252 ++1.0000000000000000e51 ++1.0000000000000001e-181 ++1.0000000000000003e-61 ++1.0000000000000005e-282 ++1.0000000000000005e-303 ++1.0000000000000008e-236 ++1.0000000000000026e213 ++1.0000000000000029e74 ++1.0000000000000031e-200 ++1.0000000000000056e-295 ++1.0000000000000061e31 ++1.0000000000000080e171 ++1.0000000000000095e171 ++1.0000000000000099e-250 ++1.0000000000000112e-282 ++1.0000000000000231e275 ++1.0000000000000281e66 ++1.0000000000000293e112 ++1.0000000000000911e-66 ++1.0000000000001024e79 ++1.0000000000002559e-284 ++1.0000000000003332e-28 ++1.0000000000004434e66 ++1.0000000000005224e95 ++1.0000000000006424e93 ++1.0000000000009796e277 ++1.0000000000010140e-278 ++1.0000000000015826e-304 ++1.0000000000019713e-53 ++1.0000000000022391e27 ++1.0000000000031380e-25 ++1.0000000000059974e-176 ++1.0000000000129393e285 ++1.0000000000147856e-268 ++1.0000000000227482e-224 ++1.0000000000315817e-186 ++1.0000000000483808e195 ++1.0000000000639971e166 ++1.0000000000753653e154 ++1.0000000000817533e276 ++1.0000000001222522e-189 ++1.0000000001845575e-218 ++1.0000000005476850e-148 ++1.0000000008165293e123 ++1.0000000010760010e-136 ++1.0000000013495755e-103 ++1.0000000015647055e29 ++1.0000000020637930e127 ++1.0000000042310215e120 ++1.0000000043987967e212 ++1.0000000051201376e-125 ++1.0000000061871488e-208 ++1.0000000074788608e199 ++1.0000000085334538e122 ++1.0000000092101579e57 ++1.0000000476434802e-173 ++1.0000000653031208e247 ++1.0000001336427698e-207 ++1.0000002736322727e266 ++1.0000004314716792e172 ++1.0000007570578105e-171 ++1.0000009560455604e202 ++1.0000013754421860e-29 ++1.0000036238207377e234 ++1.0000038463677754e-23 ++1.0000051409189847e209 ++1.0000055762566199e-304 ++1.0000064199591711e-29 ++1.0000146021406361e-294 ++1.0000224096800424e-74 ++1.0000588345977816e242 ++1.0000618620276404e91 ++1.0000998392170324e-160 ++1.0001266485208702e-248 ++1.0001363915135134e46 ++1.0001368484285566e-26 ++1.0001546528147956e66 ++1.0002646845526817e-82 ++1.0003042753339845e284 ++1.0003210025395783e-190 ++1.0003263830402253e-18 ++1.0005498837447954e-252 ++1.0007215690337904e166 ++1.0007754422911610e195 ++1.0008001133527162e133 ++1.0009534132086890e-301 ++1.0010655211104345e-250 ++1.0011911957725601e-9 ++1.0014633900758613e165 ++1.0032850898733881e92 ++1.0037499191261908e-192 ++1.0042121900253769e234 ++1.0064295114468123e-233 ++1.0078715878786927e-265 ++1.0097419586831423e-28 ++1.0097606772983604e-83 ++1.0105363609691768e44 ++1.0147694646435554e177 ++1.0150220028683183e-214 ++1.0204527472007986e-63 ++1.0229345649675474e149 ++1.0247852029469519e51 ++1.0261342003245960e-289 ++1.0312304049206829e-230 ++1.0350770594745183e-171 ++1.0395409770554606e-112 ++1.0406237147937721e-258 ++1.0418772551479255e239 ++1.0470716105482203e50 ++1.0531351631642681e65 ++1.0542197988190816e-81 ++1.0618071394880016e-193 ++1.0633824000755699e37 ++1.0640223847730622e103 ++1.0702194087067375e-196 ++1.0772758326491387e214 ++1.0853314262796310e-165 ++1.0956018383227638e-47 ++1.1222081400435116e-190 ++1.1296056096846734e220 ++1.1307821306602373e74 ++1.1411581581217047e279 ++1.1665795231296954e-302 ++1.1730495335130330e254 ++1.1754947707999090e-38 ++1.1793632577567321e167 ++1.1793632578348988e167 ++1.2129535712513911e229 ++1.2194330274671849e142 ++1.2194979832131120e142 ++1.2232472900561464e-296 ++1.2258328904101529e44 ++1.2338789709984741e-178 ++1.2433080910244753e86 ++1.2701223359640334e232 ++1.2800000245649899e2 ++1.2807726613969039e-203 ++1.2920455464503130e266 ++1.3050694214883736e-54 ++1.3164635336008803e64 ++1.3319983470043321e-256 ++1.3363823550461226e-51 ++1.3524339997073032e272 ++1.3557610843276973e-20 ++1.3767601546827528e-135 ++1.3789130657757557e216 ++1.4103081061444039e-278 ++1.4239101729162254e-301 ++1.4257646351743884e191 ++1.4302228744203735e-247 ++1.4678391145196259e107 ++1.4678395589138857e107 ++1.4742040721959164e166 ++1.4901161211297023e-8 ++1.4981364413368197e-95 ++1.5112704990792096e-101 ++1.5357434032918358e54 ++1.5410633289293022e284 ++1.5423487136659782e-179 ++1.5506501614533308e-266 ++1.5608743694588296e144 ++1.5767613836366577e262 ++1.6033346880072909e-291 ++1.6526399229583385e122 ++1.6668259809225355e-26 ++1.6687398718133657e94 ++1.6891231689549086e-64 ++1.6940658945086020e-21 ++1.7014118386723535e38 ++1.7070116948275973e243 ++1.7236413390002172e215 ++1.7347234856909256e-18 ++1.7441147214853887e-105 ++1.7456728259286473e81 ++1.7661648525132621e218 ++1.7705294921078185e-220 ++1.7800590868549821e-307 ++1.7859178583402432e-102 ++1.7995655180626376e162 ++1.8526734277970762e78 ++1.8746210737206809e-242 ++1.8807909615274075e-37 ++1.8939394049649387e-52 ++1.9176146374899787e-93 ++1.9322687615106013e171 ++1.9958403095371765e292 ++1.9958403401586918e292 ++2.0041683600089779e-292 ++2.0391576462499960e-56 ++2.0391576969909373e-56 ++2.0421865861119024e264 ++2.0445833531125311e161 ++2.0586959991737170e208 ++2.0859248873723273e93 ++2.1040543607655519e211 ++2.1106356288394976e-227 ++2.1191277041264928e270 ++2.1464632134096588e155 ++2.1706628412940213e-165 ++2.1858341748145889e-106 ++2.1895288505076154e-47 ++2.1895290388530934e-47 ++2.1918093490085342e-193 ++2.2085601698204788e71 ++2.2713710233303248e133 ++2.2761049594727195e-159 ++2.2936055140795820e105 ++2.3345636868687071e-302 ++2.3384026197598100e49 ++2.3485425827818716e108 ++2.4494416553286714e201 ++2.4519928655558805e55 ++2.4600816186435033e260 ++2.4652865612520607e-32 ++2.4784588255450890e-119 ++2.4948003869184017e291 ++2.5026038689798889e-147 ++2.5516044565250176e-203 ++2.5600000464311060e2 ++2.5795631403703940e266 ++2.6074065231019483e92 ++2.6442379894545973e123 ++2.6612480890804496e-110 ++2.6640038673046172e-256 ++2.6656170409966712e208 ++2.6672057731519959e241 ++2.6988217130912281e-79 ++2.7193612706977928e-20 ++2.7194265232219034e185 ++2.7726855021908189e129 ++2.7854302894801094e188 ++2.8055159894590418e-191 ++2.8176814633355238e-132 ++2.8451311993408995e-160 ++2.8948022310583820e76 ++2.9251882874774308e-157 ++2.9484081450765495e166 ++2.9485589496602850e166 ++3.0123998313986634e-182 ++3.0417465061138528e-210 ++3.0417471025944579e-210 ++3.0613582416023958e-64 ++3.1050361846014183e231 ++3.1315130625140968e-294 ++3.1828687335166120e88 ++3.2661592398018735e-201 ++3.2768000000001226e4 ++3.2944368572598227e-83 ++3.3374889538120615e94 ++3.3409558876152464e-52 ++3.3409565410392690e-52 ++3.3444356521822617e-198 ++3.3624679003587458e-285 ++3.3956115772419846e-52 ++3.4557053711611569e185 ++3.5111194043054040e305 ++3.5711205381752165e302 ++3.6185027886661757e75 ++3.7214156380931639e137 ++3.7654997910023202e-183 ++3.8312388949373807e53 ++3.8352292697719292e-93 ++3.8812952309429944e230 ++3.8934355277724895e-208 ++3.9550917848180109e-192 ++3.9980762393424024e-177 ++4.0257179931442307e-233 ++4.0347654345109304e118 ++4.1675090208215600e239 ++4.2719740850850789e96 ++4.2719743618461970e96 ++4.2896183549156612e183 ++4.3100001245266217e-293 ++4.3322969081320086e127 ++4.3503081085035284e-115 ++4.3646921823884176e-252 ++4.5281643172928210e-72 ++4.5767114681874301e-246 ++4.5917765982691170e-41 ++4.6105116604743898e-238 ++4.6335424175591848e-13 ++4.7174530310275578e167 ++4.7594120840250900e-184 ++4.7841588491365112e198 ++4.8200306928283084e-181 ++4.8566722305643227e83 ++4.9039857379401000e55 ++4.9201262289254488e260 ++4.9518677864664215e173 ++5.0000000000565753e-1 ++5.0000145494004683e-1 ++5.0099516978137073e-299 ++5.0262775240026799e58 ++5.0270349448746516e-88 ++5.0774352249514846e108 ++5.0811704505187165e-262 ++5.0908252481330895e-268 ++5.1529198765195367e-231 ++5.1708897464751894e-26 ++5.1754109574026984e-172 ++5.2656152967654665e64 ++5.2662493431053110e238 ++5.2884561580381798e123 ++5.3344115464700034e241 ++5.4153704974913380e126 ++5.4213782492152147e-20 ++5.4558652260153289e-253 ++5.5693855195724954e188 ++5.5868064075627747e-250 ++5.6051938572992683e-45 ++5.6177910468886464e306 ++5.6236422432581599e160 ++5.7168694795271452e-144 ++5.7956346126048718e-70 ++5.9091063153829306e-126 ++5.9728871584218097e-300 ++6.0247997751524974e-182 ++6.0645237983547362e228 ++6.1236060177455385e200 ++6.1427581497165269e-238 ++6.2301477843273642e108 ++6.2705706460651517e203 ++6.3043634448322763e116 ++6.3339080196865445e175 ++6.4971311047163139e-114 ++6.5402389921326789e304 ++6.5888764267223543e-83 ++6.7762643523797387e-21 ++6.8310505470487594e-142 ++6.8945653288774842e215 ++6.9089348508947782e-77 ++6.9244620785013969e274 ++6.9762414026393656e-105 ++6.9762473715799576e-105 ++6.9992023193056405e100 ++7.0137899168268983e-192 ++7.0295528039737451e159 ++7.0831793822381585e-220 ++7.1054273576017142e-15 ++7.1054273576408701e-15 ++7.2370055773322807e75 ++7.4350845423889343e283 ++7.4828356904844554e275 ++7.5287198514781086e-245 ++7.5636560836900755e-124 ++7.5806547475620562e227 ++7.5806548251594312e227 ++7.6135265985861196e286 ++7.6357316740789920e-21 ++7.7039117304191108e258 ++7.7787690983400931e-62 ++7.8545495444764244e-90 ++7.8722019674185116e261 ++7.9228162527225970e28 ++7.9434349552687785e-31 ++7.9833612381425070e292 ++7.9949173441800953e292 ++8.0263304437302779e205 ++8.4249833342707936e65 ++8.4703294799134235e-22 ++8.4982217658101593e183 ++8.5410407743642932e-255 ++8.5439481448674779e96 ++8.6645927941278129e127 ++8.6706749128919882e127 ++8.7777985100705636e304 ++8.9295889953429698e-103 ++8.9589789687112258e102 ++9.0031904437507887e276 ++9.0556790788267375e-72 ++9.0651109995687198e-218 ++9.0760312805916595e279 ++9.2137756189910449e164 ++9.3283939020068056e256 ++9.3326361850321919e-302 ++9.4758187442769945e226 ++9.6915635090879851e-268 ++9.7335888414041366e-209 ++9.8607613152626486e-32 ++9.8727298203834584e248 ++9.8785803746157368e-286 ++9.9568245704914409e-60 +1.0261136167743508e+140 +1.0439392987829447e+162 +1.0497013251542606e-271 +1.0604037773210862e+36 +1.0972248137587375e+304 +1.1179613656582808e-280 +1.1367165345180558e+77 +1.1448751382669565e+164 +1.1840975184056067e+289 +1.1887684488954232e+204 +1.1910137026909764e-290 +1.1952604110507963e-77 +1.1998859021234444e+112 +1.2072179984698462e+16 +1.4745121109795924e+156 +1.4830989419339932e+273 +1.5169197057064787e+244 +1.5506501614525150e-266 +1.6092283012626404e+140 +1.7454136538900147e+25 +1.9019309499047582e-293 +1.9279676168685551e+30 +1.9754149823857450e-133 +10213766063407725571752033390583176687380687954525579237549295365104860985759819435285567405222230528681073499777070598286531607665601241822105222417530885342901513767310380653735357943406409228524931240538548260044799.99999999999999999 +105534842523244438079649178609675555808051232572949713171097576446100947077389608011097385205683332048098925617924193713114564496950848793414050068226650723872158829305497466805372128130126952859180573731050067522838386813336914480599985139815805193387984928331199749916983295.99999999999999998 +123639368250133036125922952199849115648.00000000000000001 +124188289800854441929581961065582789398868803328951583894470458523117142671494640519034874681331879510015.99999999999999998 +1299252705018370079342844374357038476558335.99999999999999999 +13302994476475950442690477863842262726401309045753860112942680025053219541900464677945278970544465127233851957968896.00000000000000002 +148350832853227464606913824045933107429003394316546002974951358649258462989554147036424926532954063671809443745938886357928915223039825836261868166446297220425055622224455540868579342297590307323061862664913052878737601288942286536704.00000000000000000 +172823554087934511366020126300801179456451935689904635258770662858763694648164408563139915086484410464533952835230240435369779330709152503273227103595056143041954106518725929949114763676787760305228583385700077836985506233870752025067605616049875173842710801239565974370626094235647.99999999999999999 +179128386117908430119946671882239.99999999999999999 +185926000046640224.00000000000000000 +2.0407004453111789e+28 +2.1031821506816879e+117 +2.1070756576452100e+153 +2.1584920050392826e-289 +2.1699257161277757e+163 +2.2635856110481347e+155 +2.2911123134779967e+251 +2.3444329718259433e+191 +2.3647165618487725e+306 +2.3774251659109187e-282 +2.3926799346679074e+81 +2.5696152532094427e-202 +2.6571817482462492e-308 +2.6979865748216904e-26 +2.7265219354096404e-279 +2.8635994224706551e+144 +217622608161479275412702365125014831541028042729083457024548952860910904347562855741486364944798057848836619220254992207360003437691440575932410222847815003724124184046258369468128911142907605302840072632989599644717255866893885375855190633315146137599.99999999999999999 +226593684075036047080730941351996937802983388879029316075775801828228127231333536059261669360849979433381676329188059421131382622369151236758281344969921191743586304.00000000000000000 +3.0751948411849839e-127 +3.1062666643241956e-103 +3.2731069888927001e-14 +3.4372698089445951e+299 +3.5766451948363446e+155 +3.8815154565966364e+284 +3.9241505073601831e-127 +3.9801812042108394e+147 +3187827407525684870015066660332609332762398132965641906295128174966358754752976711491036830474908684694384616048539368632730432181948804872515357711608215356332502886198682412729478603226016662658670863064337998366437982095696579546032232412349299464572239871.99999999999999999 +33739483486281250933530666722149830905167150836592802928399192290536598487479816574348105976315424725656986774484037966750375461389600146504067557483629283993143125546999829705444594735748860870370787327.99999999999999998 +4.0083367200179458e-292 +4.1414919333114381e-81 +4.4737375852818270e-85 +4.7378174478431181e+51 +4.8988833106573423e+201 +4002618797620379734502057418156560897427026461620199486818141120733077142636437417066776600680265135606783323841927594561029717656552610811869135455917459543613610920564948122150429397140858548824843440571477363409603300596679594194181998682154930749041758802471357088941090782120782505967615.99999999999999999 +4864743212149930896031209030830303574284436121540786691075737073013131567606749484309792423936.00000000000000002 +5.1178454216981880e+211 +5.2138725084752660e+204 +5.2465206172022391e+216 +5.4142655787843691e-200 +5.5180289145934289e+87 +5.7519885786121598e-246 +5.9993228832115195e+98 +5006115866956079891478792762938162804727223115786455728783360.00000000000000002 +52456851617184041979541223476012108893587482613535352548620319577312890305189268155801889342120737042965282700955955478368112800309228840222350144072740282562173052464301867008.00000000000000002 +540504143483242003509747643061025920407884552380350463.99999999999999998 +6.1242942360915167e-24 +6.3908198463548303e+156 +6.4219744343019288e+39 +6.4489078507771639e+265 +6.5425884281537316e+300 +6.5431852428897110e+230 +60128253873317772235143578969264894208655557483242356702048436100302077361650823565427291803615232.00000000000000002 +61749505473.326011 +7.3887615364673810e+279 +7.5345745074834568e-39 +7.7312007910647605e+27 +7.9941591034792767e+36 +79089337911263052236378411596654444314375160760649141987460475213298172952882775494149014776925538584521615889885734904392910496007134299597472187079221874493026958279632249421824.00000000000000000 +8.1356903335827978e-92 +8.3043331626469751e-266 +8014569067942507269301568963002828917142830908643526476971717698082000811757378519039226902128623616.00000000000000000 +84562003828621292197228592165236240761339593778105192169769066798610808510147307881420877062422401177485901605969395602914132354688302076513765582031168045736180839282012391219178504192.00000000000000002 +9.8130491442123809e+276 +9.9792933087426791e-161 +964328380.26764047145843507 +9645837197126426467751195142699890080136330210558522354063005553675120790208512.00000000000000001 diff --git a/src/internal/strconv/testdata/ftoa1k.txt b/src/internal/strconv/testdata/ftoa1k.txt new file mode 100644 index 00000000000..79c37126a2a --- /dev/null +++ b/src/internal/strconv/testdata/ftoa1k.txt @@ -0,0 +1,1000 @@ +-0x1.0000000000001p-229 +-0x1.0000000000001p-631 +-0x1.0000000000001p-841 +-0x1.0000000000001p+367 +-0x1.0000000000001p+537 +-0x1.0000000000001p+948 +-0x1.0000000000001p+996 +-0x1.0000000000002p-1015 +-0x1.0000000000002p-194 +-0x1.0000000000002p-261 +-0x1.0000000000002p-489 +-0x1.0000000000002p-955 +-0x1.0000000000002p+478 +-0x1.0000000000002p+52 +-0x1.0000000000003p-24 +-0x1.0000000000003p-313 +-0x1.0000000000003p+13 +-0x1.0000000000003p+181 +-0x1.0000000000003p+545 +-0x1.0000000000005p+394 +-0x1.0000000000005p+606 +-0x1.0000000000006p+219 +-0x1.0000000000006p+722 +-0x1.0000000000007p-36 +-0x1.0000000000008p-898 +-0x1.0000000000008p+266 +-0x1.0000000000009p+400 +-0x1.000000000000ap+828 +-0x1.000000000000cp-328 +-0x1.0000000000015p+459 +-0x1.0000000000016p+582 +-0x1.000000000001bp+332 +-0x1.000000000001dp-531 +-0x1.000000000001dp-706 +-0x1.0000000000021p+925 +-0x1.0000000000022p+965 +-0x1.0000000000023p+174 +-0x1.000000000002fp-677 +-0x1.000000000002p-603 +-0x1.000000000003bp+513 +-0x1.000000000003cp+851 +-0x1.0000000000046p-288 +-0x1.000000000004cp-919 +-0x1.000000000005ep-82 +-0x1.0000000000061p-437 +-0x1.000000000006ap-478 +-0x1.000000000007bp+858 +-0x1.000000000007p+924 +-0x1.0000000000083p-378 +-0x1.0000000000083p-527 +-0x1.000000000008dp-264 +-0x1.000000000008dp+289 +-0x1.000000000008dp+75 +-0x1.00000000000b2p-216 +-0x1.00000000000c6p+123 +-0x1.00000000000cfp-722 +-0x1.00000000000d6p+694 +-0x1.00000000000d8p+255 +-0x1.0000000000128p-786 +-0x1.0000000000157p+74 +-0x1.00000000001afp+20 +-0x1.0000000000203p+348 +-0x1.0000000000222p-377 +-0x1.000000000022bp+341 +-0x1.0000000000232p-482 +-0x1.0000000000254p-889 +-0x1.0000000000287p+956 +-0x1.00000000002b8p+286 +-0x1.000000000038bp-629 +-0x1.00000000003a6p+390 +-0x1.00000000003ep-139 +-0x1.0000000000448p+922 +-0x1.0000000000474p+287 +-0x1.0000000000494p+501 +-0x1.0000000000656p-229 +-0x1.00000000008b3p-719 +-0x1.0000000000927p-86 +-0x1.0000000000958p-287 +-0x1.00000000009eap+943 +-0x1.0000000000a12p+831 +-0x1.0000000000a46p-127 +-0x1.0000000000b07p+76 +-0x1.0000000000bb8p+792 +-0x1.0000000000c1cp+21 +-0x1.0000000001141p-459 +-0x1.00000000011b5p-285 +-0x1.00000000013bcp-507 +-0x1.000000000251bp+195 +-0x1.00000000036fbp+546 +-0x1.000000000387ap-698 +-0x1.000000000485ap+468 +-0x1.0000000004ce9p+508 +-0x1.0000000005b3bp+144 +-0x1.0000000005dcfp-655 +-0x1.0000000007a35p-230 +-0x1.0000000007eeap-107 +-0x1.0000000008995p+793 +-0x1.0000000009af2p-288 +-0x1.000000000d2efp+334 +-0x1.000000000e23cp-74 +-0x1.000000000ec4dp+886 +-0x1.000000001042fp+01 +-0x1.00000000107d8p-832 +-0x1.0000000012493p+714 +-0x1.00000000161ecp-41 +-0x1.0000000017a55p+258 +-0x1.0000000025536p-601 +-0x1.000000002a3ccp+891 +-0x1.000000002b2a9p-901 +-0x1.000000002e917p-46 +-0x1.0000000038508p+764 +-0x1.000000004b589p+376 +-0x1.000000004fa44p-297 +-0x1.0000000055b3ep-866 +-0x1.000000007a954p-409 +-0x1.0000000088d2ep+294 +-0x1.00000000bce2cp-15 +-0x1.00000000e0935p-798 +-0x1.0000000102141p-601 +-0x1.000000011918ep-467 +-0x1.000000012a8e3p-331 +-0x1.000000013a03ep-916 +-0x1.000000016fe45p+113 +-0x1.000000018822cp+341 +-0x1.0000000203a1fp-438 +-0x1.000000020fa86p-04 +-0x1.0000000260016p+325 +-0x1.00000002f00d1p-981 +-0x1.00000003c9138p+527 +-0x1.0000000422a32p-153 +-0x1.00000004f598bp-879 +-0x1.0000000553b9cp+148 +-0x1.000000056cff8p-29 +-0x1.00000005c1e06p-697 +-0x1.00000006e671dp-356 +-0x1.00000007a91dp-346 +-0x1.000000091598cp+571 +-0x1.00000009ea715p+532 +-0x1.0000000a3f851p-439 +-0x1.0000000a58946p-64 +-0x1.0000000ac0f35p-36 +-0x1.0000000b7a2c3p+607 +-0x1.0000000bc0fb4p+893 +-0x1.0000000d05215p+1003 +-0x1.0000000e6f517p-430 +-0x1.0000001425957p-357 +-0x1.0000001460817p-956 +-0x1.0000001487b1dp-1001 +-0x1.0000001776682p-859 +-0x1.00000019a9811p+831 +-0x1.0000001b4963dp+867 +-0x1.0000001bcc043p+11 +-0x1.0000001d11576p+519 +-0x1.000000210630ep-226 +-0x1.00000025616bap+472 +-0x1.0000002da3352p+270 +-0x1.0000003798411p+957 +-0x1.0000003f5b415p+254 +-0x1.0000004141b23p+734 +-0x1.00000041a6267p+912 +-0x1.00000050a8187p+826 +-0x1.00000050bc144p-720 +-0x1.000000615758ap-802 +-0x1.000000831bebep-496 +-0x1.0000008e6427ep-560 +-0x1.00000092e51cbp+440 +-0x1.00000099ce26fp+429 +-0x1.000000a60f57ep-446 +-0x1.000000e138f25p+174 +-0x1.000000e3bab22p+929 +-0x1.000000fdad036p-676 +-0x1.00000119748fdp+952 +-0x1.000001226fe6fp-880 +-0x1.00000127844b4p+971 +-0x1.0000013d753fep+453 +-0x1.0000014a3034fp-455 +-0x1.0000016e4f726p+636 +-0x1.0000017c45a2p+979 +-0x1.00000198d9932p+871 +-0x1.0000019a8a31ep+476 +-0x1.0000020b127cdp-539 +-0x1.0000021a9a098p+406 +-0x1.000002270ca08p-228 +-0x1.00000245e2b9ap+995 +-0x1.0000024ff974cp+542 +-0x1.0000027a34158p+809 +-0x1.000002b28d033p-650 +-0x1.000002f9f3099p-46 +-0x1.0000037d266b7p-39 +-0x1.0000043912a1p-415 +-0x1.0000045a9a055p-505 +-0x1.000005964d2ccp-68 +-0x1.000005aae1e1dp-979 +-0x1.000007a71933bp-249 +-0x1.000007b023de2p-632 +-0x1.000008ac3877cp+748 +-0x1.00000e428b46fp+820 +-0x1.0000114962f51p-1011 +-0x1.000015f083b2bp-862 +-0x1.000016f389a07p+513 +-0x1.000017282de5bp-875 +-0x1.0000173ae4d3bp+760 +-0x1.000025cd7330ep+859 +-0x1.00002bb78b378p+957 +-0x1.00002ef21ba3ep+413 +-0x1.00003ce891eaep-901 +-0x1.000044c6a1702p+990 +-0x1.000050028029ep+179 +-0x1.00005cb8ab70ep+579 +-0x1.0000829afa589p+883 +-0x1.0000958bd528ep-298 +-0x1.0000abafd59eep-573 +-0x1.0000fda5c771p+608 +-0x1.0000p-176 +-0x1.0000p-193 +-0x1.0000p-218 +-0x1.0000p-367 +-0x1.0000p-507 +-0x1.0000p-712 +-0x1.0000p-791 +-0x1.0000p-80 +-0x1.0000p-838 +-0x1.0000p-933 +-0x1.0000p+538 +-0x1.0000p+564 +-0x1.0000p+731 +-0x1.0000p+766 +-0x1.0000p+947 +-0x1.0001139e74a9cp+976 +-0x1.00013e49c5087p-927 +-0x1.000167d927693p+529 +-0x1.00017c35f9ed4p-296 +-0x1.0001c2cdb465ap-579 +-0x1.0001c9aa27f54p-211 +-0x1.0001db2e92a4bp-917 +-0x1.0003388568319p-843 +-0x1.0003630d744eep+473 +-0x1.0003ae825ac01p+839 +-0x1.00042cca489fp+816 +-0x1.000471524bb81p-281 +-0x1.0004cca6ccafep-381 +-0x1.0005392a8da7bp+462 +-0x1.0005616ba47bbp+593 +-0x1.0005991343cfcp+1018 +-0x1.0005b51cacfedp+228 +-0x1.00062b17a22a9p+751 +-0x1.0006c7f88b8f9p-56 +-0x1.0007bf2aaf9c9p-707 +-0x1.0007fcca53d15p-346 +-0x1.0008e807e72c1p+952 +-0x1.000b118bdd059p+974 +-0x1.000b1825a25d6p+772 +-0x1.000d78db1f161p-189 +-0x1.00120c1c8c7d6p-686 +-0x1.00172d9b2c09ap-544 +-0x1.0017d6d2008d9p-345 +-0x1.00187e6f50ea4p+968 +-0x1.00188b53cd182p-315 +-0x1.001cbeb38042fp-878 +-0x1.002007607bc2ep+542 +-0x1.00239c9f7f5e1p-526 +-0x1.00248d3e94083p+447 +-0x1.0026494b9386ap-602 +-0x1.002eea1acfe78p-744 +-0x1.002eef4aaf75cp-157 +-0x1.0034d8c630111p-415 +-0x1.0038b0a1d6ffdp+503 +-0x1.003ec0da08e95p-1016 +-0x1.00403e04e6878p+531 +-0x1.004c276b1ad1cp-893 +-0x1.004e9369ffde1p+271 +-0x1.004eaf8f2722fp+444 +-0x1.005aacc69acbep-449 +-0x1.00654b395a52fp+955 +-0x1.0067a1ad57a27p+847 +-0x1.006a5bea58343p+202 +-0x1.006fad2e348a9p-743 +-0x1.0075e30e0f6p-222 +-0x1.00aa606a565cep+810 +-0x1.00c0e3ce04349p+616 +-0x1.00ca60a6cfc87p+709 +-0x1.00ca98c74c5e9p+893 +-0x1.00d37668b2bfcp+298 +-0x1.00fd480d3edf5p-966 +-0x1.0120cc889581dp-850 +-0x1.013230f5ed50ap+710 +-0x1.0155faae15e76p-422 +-0x1.0160bcb58c4d9p+289 +-0x1.016a1623d1aap-96 +-0x1.0174e2dfb627ep-831 +-0x1.017fad2e374b2p+142 +-0x1.01f77dae8ec9p+299 +-0x1.01fafbf478bffp+185 +-0x1.0238b55495883p-187 +-0x1.023998cd10538p-392 +-0x1.039d66589e23dp-103 +-0x1.040b25351e121p-344 +-0x1.046d590243963p+719 +-0x1.0470a846d0748p+03 +-0x1.04eaab2368e19p-143 +-0x1.050ca8bc66efp+770 +-0x1.0548b68a6bd4fp+671 +-0x1.05b54488676fbp-980 +-0x1.05ef6572b09ap-665 +-0x1.0624dd2fc917fp-10 +-0x1.065de7b9c7b1ep+53 +-0x1.0734e972955fbp-787 +-0x1.079fa0cfe0549p+678 +-0x1.0836da5617bd1p+718 +-0x1.0882c6e291ff1p+844 +-0x1.08c8b6fcd8541p-739 +-0x1.08da3c610efe2p+477 +-0x1.095abda5406d5p-333 +-0x1.09d879523f779p-113 +-0x1.09e5b51dd84b6p-560 +-0x1.0a6650e624f4ep+857 +-0x1.0a75f770bc557p-829 +-0x1.0bfd599094d49p-753 +-0x1.0c6f81df33b49p-20 +-0x1.0f6ca84d572f7p-318 +-0x1.108167929e84dp+532 +-0x1.108269fd210d3p+362 +-0x1.111d7bf5fab99p-767 +-0x1.111f4e79eea48p-804 +-0x1.12adc770f4117p+987 +-0x1.138c3e7fa4c37p+269 +-0x1.15d70879fe158p+805 +-0x1.15d847ad00092p+548 +-0x1.175721752198ap+837 +-0x1.182a852f2767fp+809 +-0x1.18352262653f8p+19 +-0x1.18bbd16f0b9ffp+506 +-0x1.199d05a266577p-980 +-0x1.1b48e353bf9c7p+734 +-0x1.1bc7f52c79563p-525 +-0x1.1bf17a54d9193p-432 +-0x1.1d270d27adeb5p-628 +-0x1.1dbf31704664ap+342 +-0x1.1eaff4a987529p-339 +-0x1.1fa182dec1875p-1020 +-0x1.20d4c363b8bap+920 +-0x1.21b5e02bfb083p-53 +-0x1.22a48c7d821a8p-82 +-0x1.24e8d737cb75p+817 +-0x1.26c11dda58615p+641 +-0x1.26dbe4a7b9ap-253 +-0x1.27748f9301d3p+425 +-0x1.279c8edd62cdcp-535 +-0x1.2a5568ba12e05p+518 +-0x1.2ab5c530b886dp+239 +-0x1.2b877da83077fp-432 +-0x1.2d9484ed0a3bep+82 +-0x1.2f39e794d9fbfp-751 +-0x1.3478410f4ffafp+601 +-0x1.357cdd8da8f8ap-80 +-0x1.37269876661a3p+209 +-0x1.37da088ba069bp-957 +-0x1.39dae6f76fb6fp-183 +-0x1.3ae3591f6146bp-864 +-0x1.3b3750887305ep-379 +-0x1.3ccf269aa3d0bp-324 +-0x1.3d3e23ce4a26dp+395 +-0x1.3d3e24547eb35p+395 +-0x1.3f559e7bee6c6p-967 +-0x1.3fb77206ebffp+522 +-0x1.400000096cd8fp+03 +-0x1.410d9f9ec7a56p-678 +-0x1.420eb449c8846p+777 +-0x1.431e0fbd582c3p+96 +-0x1.4374374f3f338p+581 +-0x1.437437bf6fbd8p+581 +-0x1.45e931c5daef7p+267 +-0x1.47014bc7b76f2p-977 +-0x1.4bf61430b1d3fp-595 +-0x1.4e37983fbf48fp+01 +-0x1.503e602979ed3p+953 +-0x1.532a83810dc46p+561 +-0x1.5374105e1bf54p-891 +-0x1.5568e1e808477p-801 +-0x1.55e190923d6cfp-525 +-0x1.5620fa946f1fp+757 +-0x1.56e1fc2f8fap-997 +-0x1.5798ee239554fp-27 +-0x1.57a340eb5d4f1p-760 +-0x1.59165a6dddaadp-223 +-0x1.5d1a4f2fccd7dp-133 +-0x1.5e858b79e1c5p+159 +-0x1.621b1c28ac20dp+737 +-0x1.621b1c28dae3ap+737 +-0x1.652efdc60477ap+345 +-0x1.67e9c127b6e72p-532 +-0x1.681db7f4e3023p+588 +-0x1.682d1cb670ef3p-40 +-0x1.69ad824da1acfp-429 +-0x1.6a9e8e72cdb6fp-724 +-0x1.6df02d2fd33dbp+22 +-0x1.6e230d05b76cfp+820 +-0x1.6ef5b4fb1c2cap-346 +-0x1.6f61cef504e8bp-188 +-0x1.6f620e962d177p+139 +-0x1.6ff406c702454p+760 +-0x1.71d95ca2910b6p-831 +-0x1.7288e920e6acbp-253 +-0x1.73ded10d1b645p-945 +-0x1.755a4c37bc32dp+169 +-0x1.758ef6ceb83d5p+854 +-0x1.7639c33f61d26p+242 +-0x1.77bc3d4ca153p+253 +-0x1.78287f49c4a1fp+129 +-0x1.788ccb6b302e8p+614 +-0x1.78ef88e46d161p+154 +-0x1.796655ae4429p-552 +-0x1.79ca10ca89172p-67 +-0x1.7d12a4670c137p-459 +-0x1.808209ad4a1e8p-851 +-0x1.83a9a2ab25486p+893 +-0x1.8557f31326be7p+697 +-0x1.8557f3139e5cap+697 +-0x1.8637f620c96bep-469 +-0x1.86381a3234f8p-469 +-0x1.869f5cfc4d8ep-344 +-0x1.88ba3bf285c52p+305 +-0x1.8922f31411546p+790 +-0x1.8bba93699a46fp-572 +-0x1.8c8dac6a0343p+398 +-0x1.8e45e1df3b016p+202 +-0x1.91510d89dc5c5p-675 +-0x1.91bc0c516495ap-190 +-0x1.91c0237db8127p-190 +-0x1.922726dbaae39p+295 +-0x1.9866b5baf7e32p+29 +-0x1.999999a460e3ep-04 +-0x1.9a74381ea2ce9p+966 +-0x1.9bbd95ea117ep-356 +-0x1.9bce5656cc6a1p+285 +-0x1.9be0731ea8ce3p+08 +-0x1.9d58c5133dd2p+175 +-0x1.9d971e4fec00cp+89 +-0x1.9fc552aeef9c9p-519 +-0x1.a53fc9631e176p-210 +-0x1.a6208b5068552p+760 +-0x1.a951a7747745p-602 +-0x1.ab0ccd28f797dp+171 +-0x1.ac1e69cb437e5p+768 +-0x1.ac9a7b3b7302fp-994 +-0x1.ad0cc339bdafdp-509 +-0x1.ad7f29abcb6b5p-24 +-0x1.ae3ebff7b74e1p-31 +-0x1.b0c764ac6d3afp-901 +-0x1.b0ec762a5b464p-748 +-0x1.b38fbc35959ep-127 +-0x1.b609bfc2cdaa6p-183 +-0x1.bba8a882edf21p-590 +-0x1.bd0bb289fd8d3p-228 +-0x1.bd8d1f624aa8dp-622 +-0x1.be7abd3781f05p+348 +-0x1.c1b64a8eb037fp+637 +-0x1.c34c7114b2b37p+926 +-0x1.c5416bd40c484p+730 +-0x1.c73a8edc80b9p+534 +-0x1.c9a15fdfd643dp+441 +-0x1.cd2b2d2a5578bp-54 +-0x1.cd3208b9562p-54 +-0x1.d0223911582a9p+720 +-0x1.d12d41afca3c8p-446 +-0x1.d2a1be4048f95p+1009 +-0x1.d6329f1c35cd1p+132 +-0x1.d9388b3aa3171p+906 +-0x1.db45dc38e0e1fp+710 +-0x1.dd739189b75b8p-974 +-0x1.ddb2222d140f3p-642 +-0x1.e311336378814p-559 +-0x1.e91d1750185e8p-353 +-0x1.eb1ab0aa350dap-898 +-0x1.ed09bead89199p+112 +-0x1.ed09beadad74ap+112 +-0x1.ed516635bf96p+155 +-0x1.eff2cd5b5700ep-740 +-0x1.f152bf9f1132fp-280 +-0x1.f6e460ad2ac6p+229 +-0x1.f965966bce059p+587 +-0x1.f9b513bb09b7p-628 +-0x1.fdca16e04b885p+195 +-0x1.ff7d95ff442a4p-610 +-0x1.ffffffffffffep-639 +-0x1.ffffffffffffep-736 +-0x1.ffffffffffffep-86 +-0x1.ffffffffffffep+426 +-0x1.ffffffffffffep+918 +-0x1.fffffffffffffp-45 +-0x1.fffffffffffffp-468 +-0x1.fffffffffffffp-601 +-0x1.fffffffffffffp-804 +-0x1.fffffffffffffp-924 +-0x1.fffffffffffffp-941 +-0x1.fffffffffffffp-96 +-0x1.fffffffffffffp+266 +-0x1.fffffffffffffp+571 +-0x1.fffffffffffffp+60 +0x1.0000000000001p-43 +0x1.0000000000001p+882 +0x1.0000000000002p-427 +0x1.0000000000002p-52 +0x1.0000000000002p-624 +0x1.0000000000002p-684 +0x1.0000000000002p-977 +0x1.0000000000002p+258 +0x1.0000000000002p+697 +0x1.0000000000002p+720 +0x1.0000000000002p+75 +0x1.0000000000002p+836 +0x1.0000000000002p+854 +0x1.0000000000002p+858 +0x1.0000000000003p-226 +0x1.0000000000003p+561 +0x1.0000000000004p+123 +0x1.0000000000005p-333 +0x1.0000000000006p-643 +0x1.0000000000006p-759 +0x1.0000000000007p-906 +0x1.0000000000008p-1010 +0x1.0000000000008p-581 +0x1.0000000000008p+566 +0x1.0000000000009p-270 +0x1.0000000000009p-37 +0x1.0000000000009p-889 +0x1.0000000000009p+573 +0x1.000000000000cp-191 +0x1.000000000000cp+321 +0x1.000000000000cp+421 +0x1.000000000000dp+293 +0x1.0000000000012p+563 +0x1.0000000000014p-760 +0x1.0000000000014p+785 +0x1.0000000000018p-234 +0x1.0000000000018p-607 +0x1.0000000000019p-765 +0x1.000000000001bp+862 +0x1.0000000000021p-790 +0x1.0000000000028p+648 +0x1.000000000002bp-487 +0x1.000000000002p+732 +0x1.0000000000036p-835 +0x1.000000000003bp-17 +0x1.000000000003p+818 +0x1.0000000000044p+304 +0x1.0000000000044p+461 +0x1.0000000000054p+894 +0x1.0000000000072p+382 +0x1.000000000007ep-481 +0x1.00000000000adp-438 +0x1.00000000000b6p+852 +0x1.00000000000c9p-623 +0x1.00000000000cp+546 +0x1.00000000000d1p-820 +0x1.00000000000d6p-386 +0x1.00000000000dap+174 +0x1.0000000000131p+390 +0x1.0000000000162p+878 +0x1.000000000016ep+14 +0x1.0000000000192p-545 +0x1.00000000001b3p-392 +0x1.00000000001ccp+94 +0x1.000000000020ap+232 +0x1.000000000022ap+850 +0x1.00000000002e6p-67 +0x1.00000000002ffp+93 +0x1.0000000000364p-685 +0x1.0000000000408p+00 +0x1.00000000004bbp+80 +0x1.0000000000559p+787 +0x1.00000000006b6p+566 +0x1.000000000079p-41 +0x1.00000000007dbp+161 +0x1.000000000083bp-326 +0x1.000000000085cp+616 +0x1.00000000008ddp-158 +0x1.00000000009d4p-306 +0x1.0000000000a65p-636 +0x1.0000000000acap+420 +0x1.0000000000bbdp+465 +0x1.0000000000ccp+178 +0x1.0000000000cdfp+346 +0x1.0000000000d79p+308 +0x1.0000000000f99p+318 +0x1.00000000010d3p-140 +0x1.00000000010d3p+784 +0x1.000000000125fp-96 +0x1.0000000001606p+110 +0x1.00000000016a5p-18 +0x1.0000000001852p+419 +0x1.0000000001a0bp+206 +0x1.0000000001d24p+512 +0x1.0000000002c1cp-649 +0x1.0000000002d75p+722 +0x1.0000000002e76p-957 +0x1.000000000361fp+334 +0x1.0000000003a3ap+325 +0x1.0000000005edfp+338 +0x1.0000000007769p+942 +0x1.0000000008f1ap+340 +0x1.000000000934dp-473 +0x1.000000000c7cdp+245 +0x1.000000000d642p-90 +0x1.0000000010007p-341 +0x1.000000001b51p+857 +0x1.000000001bf0fp-110 +0x1.000000001cbd9p-150 +0x1.0000000020bb8p-621 +0x1.0000000023107p-950 +0x1.00000000276aep+515 +0x1.000000002b2cbp-445 +0x1.000000002d61ep-822 +0x1.000000003258fp+598 +0x1.0000000032dbdp-523 +0x1.0000000034edep+897 +0x1.0000000035d35p-620 +0x1.0000000039a67p-16 +0x1.0000000059345p+329 +0x1.000000005a61cp-164 +0x1.00000000604d9p-647 +0x1.000000006769bp-1000 +0x1.0000000075735p+892 +0x1.0000000076af2p-1006 +0x1.0000000084aeep+907 +0x1.000000008d45ep+1008 +0x1.000000009ab34p-09 +0x1.00000000a89f7p+597 +0x1.00000000bc994p+930 +0x1.00000000e6e28p+385 +0x1.0000000101aap+377 +0x1.0000000107d47p-49 +0x1.000000013a03ep-917 +0x1.0000000154052p+580 +0x1.00000001718fep+769 +0x1.000000018262fp-613 +0x1.000000018502dp+709 +0x1.00000001afc57p+222 +0x1.00000001c00dfp+206 +0x1.00000001d987p+722 +0x1.00000001ea21dp-01 +0x1.00000001ef027p+367 +0x1.00000002521f6p-19 +0x1.00000002601dcp-974 +0x1.0000000268df5p+442 +0x1.0000000269fdp+952 +0x1.0000000428a5cp-924 +0x1.000000049811fp-456 +0x1.0000000503eb1p+15 +0x1.00000005ad13fp+840 +0x1.00000006a0902p+580 +0x1.000000072ba2dp-228 +0x1.000000081f947p-919 +0x1.0000000995406p+408 +0x1.00000009dd7b3p-100 +0x1.0000000af7a85p-974 +0x1.0000000b1818cp+914 +0x1.0000000b590d3p+664 +0x1.0000000e01289p+257 +0x1.0000000e31223p+609 +0x1.00000010822acp-720 +0x1.00000011ccbf3p+525 +0x1.00000015bf7b6p+583 +0x1.00000015ef3f8p+461 +0x1.0000001a87216p-389 +0x1.0000001c8ea6dp-452 +0x1.0000001f69e57p-46 +0x1.00000021915afp+530 +0x1.00000027bd096p+861 +0x1.0000003214f09p-627 +0x1.0000003599105p-191 +0x1.000000368f1afp-710 +0x1.000000397edbcp+422 +0x1.00000053bb4e2p-625 +0x1.0000005a193d6p+739 +0x1.00000077138dp+658 +0x1.000000860ad47p-882 +0x1.00000086dcc2fp+259 +0x1.0000008ce5052p+185 +0x1.000000a69591bp+152 +0x1.000000b3feb4ep+564 +0x1.000000c83db44p-772 +0x1.000000f6a410ep-285 +0x1.0000010498ce1p+898 +0x1.000001c8060ffp+823 +0x1.000001ca741f6p-77 +0x1.0000023922be7p-240 +0x1.000002421fc05p+741 +0x1.000002b2ebb8p+377 +0x1.000002e916128p+395 +0x1.000003e1fe29ap+693 +0x1.000004012e199p-822 +0x1.000004caa2902p+380 +0x1.000004da8bddep-05 +0x1.00000577febfap+622 +0x1.00000608eadaap-17 +0x1.000006670a449p-399 +0x1.000006fdf88d4p+506 +0x1.000007621285bp+675 +0x1.000007ca8fb6ep+754 +0x1.000007db18341p-723 +0x1.00000911d4a87p+862 +0x1.000009291ff3fp+703 +0x1.0000096dc6db6p+749 +0x1.0000098fef6bfp-639 +0x1.00000a8e46c64p+574 +0x1.00000ae8e71e5p+670 +0x1.00000b486b0fbp-963 +0x1.00000bcb6b065p+211 +0x1.00000cb515f4bp+116 +0x1.00000d18e0c4cp+513 +0x1.00000d854fb69p+399 +0x1.00000f6eabd0bp-333 +0x1.0000109138ae9p+497 +0x1.0000131329a0bp+288 +0x1.0000134b8b7dcp-557 +0x1.000013dd4995p-397 +0x1.00001455c0223p+339 +0x1.0000197e15e38p+798 +0x1.0000205c28a33p+820 +0x1.0000211b6e016p+384 +0x1.000023545317ep+311 +0x1.0000253036d7fp-217 +0x1.000026f9a09e3p-480 +0x1.00002aece648ap+406 +0x1.00002f7c6cc22p+251 +0x1.000034adb8091p-891 +0x1.00003fafd7c17p-381 +0x1.00004090c997cp-858 +0x1.000045bb4b96ap+780 +0x1.000056a0ade86p+878 +0x1.00005e4837f9bp-859 +0x1.0000740fa717dp+275 +0x1.00008280d5ae2p+920 +0x1.0000c80341debp+382 +0x1.0000dd797e96dp+170 +0x1.0000ef3c72696p+357 +0x1.0000p-1008 +0x1.0000p-220 +0x1.0000p-337 +0x1.0000p-541 +0x1.0000p-566 +0x1.0000p-617 +0x1.0000p-640 +0x1.0000p-956 +0x1.0000p+152 +0x1.0000p+293 +0x1.0000p+373 +0x1.0000p+39 +0x1.0000p+495 +0x1.0000p+545 +0x1.0000p+63 +0x1.0000p+800 +0x1.0000p+916 +0x1.000103dd09032p+511 +0x1.000110b973452p+150 +0x1.00013cd72b6cap-115 +0x1.00021e8e7dbb3p-855 +0x1.000232ecc7b47p-157 +0x1.0002993f49252p+163 +0x1.000333e5e725fp-871 +0x1.000366b64d90fp-891 +0x1.00047e1d07d17p-816 +0x1.0004c5efdf98cp-219 +0x1.0005002553954p+141 +0x1.0005180941d97p+410 +0x1.000725ad93e99p-710 +0x1.000766def7e25p+185 +0x1.00096b1665e85p-1005 +0x1.000b1edfaa1afp+708 +0x1.000b8b0ae6769p+476 +0x1.000bff9c604f4p+961 +0x1.0010aaffb40b6p-811 +0x1.00144f91c5bcep-998 +0x1.0014cd7669416p-663 +0x1.00199123a42e9p+381 +0x1.001c0e3f4bb3cp+632 +0x1.001d17c9a9f18p-889 +0x1.00224b6305c6dp+822 +0x1.0028850a553ecp+938 +0x1.002a7fa6f9364p-105 +0x1.00338eafdf527p-1019 +0x1.003d4017ccb78p+404 +0x1.0040859344642p+383 +0x1.004c5d9ab71a1p+739 +0x1.00523e609bc55p+381 +0x1.005b4bfeb2d01p-44 +0x1.005f6ab6268ebp+924 +0x1.0061e9df1df95p+987 +0x1.006c751a24d4ap+624 +0x1.006d78603cf5fp-859 +0x1.007eeacf0265fp+695 +0x1.009175f54e90ap-928 +0x1.00a6a889bc8b4p-353 +0x1.00bc75981c52bp-170 +0x1.00c18e1a28be2p-823 +0x1.00e9f48e0ec1cp-70 +0x1.0112ed37bf20bp+894 +0x1.011c2ebab15d1p-196 +0x1.013b10ac50e57p+131 +0x1.013de1c2724c6p-429 +0x1.0160bcb58d4p+289 +0x1.018bf3319cfc7p+305 +0x1.01903d1657b69p-829 +0x1.019dbdd92528p+56 +0x1.01a55d0cd08a3p+774 +0x1.01eb89cfa5e02p-541 +0x1.0226705972bcfp-951 +0x1.0265804e7bc97p+493 +0x1.0281127ca95d2p+458 +0x1.037f09e2f5b4bp-929 +0x1.039b53cb9bd83p+269 +0x1.03a0379edbf9dp-755 +0x1.04109755e7fa9p-635 +0x1.04cd5a7cfc76cp+702 +0x1.05a6416f9fe49p-378 +0x1.066ac2d5db08fp+475 +0x1.0789df4757da7p-884 +0x1.0825b33664659p-887 +0x1.08847f89b1384p+34 +0x1.08c72790be8cdp+145 +0x1.091e097573b86p-334 +0x1.0954542378ac2p-456 +0x1.0a106a3f7efc4p+22 +0x1.0a6651c62ce59p+857 +0x1.0a998336fea29p+730 +0x1.0c3a86c83886p-563 +0x1.0d3b113fa7a4cp-628 +0x1.0d51a73def3e2p-701 +0x1.0ebc3b1c08a8bp+41 +0x1.1054ae84589d1p+123 +0x1.107ba0bd455cap-735 +0x1.107de503df205p-01 +0x1.10f2d27643673p-495 +0x1.1123a7ec598e3p-804 +0x1.1169b3a53961fp-319 +0x1.11efbe93a228ep+406 +0x1.12978794bc676p-515 +0x1.131fd9df8f1b4p-625 +0x1.147224dd8ad2cp-218 +0x1.14a52dffc86ap+744 +0x1.15f36e85167a7p+08 +0x1.165e2411017f2p+46 +0x1.16ab7145d2b92p+393 +0x1.16c262779acadp-133 +0x1.1763558ec79bbp-318 +0x1.191101309b83ap-90 +0x1.1afd6ec0e1586p+249 +0x1.1e0b63add7575p+827 +0x1.1ecc348613fdbp-143 +0x1.1efc659d0b589p+146 +0x1.1fa182c40c65p-1020 +0x1.1fa1cc04af745p-1020 +0x1.20267966bc87bp+772 +0x1.20f5fb2fb3998p+920 +0x1.2192db14a99d9p+699 +0x1.21b3ba7a53205p-633 +0x1.21c81f7deb095p+239 +0x1.22ac85db3efb9p+993 +0x1.23ff06eea847bp-638 +0x1.244a393c96adbp+230 +0x1.2776b9b7d972dp+425 +0x1.27fbe228da601p+682 +0x1.281e8c2783e06p-741 +0x1.2927a292d89e4p-89 +0x1.29fa84d76e138p-572 +0x1.2a55690d79014p+518 +0x1.2b5f9c01ba2eap+25 +0x1.2bf0d5b50f493p+807 +0x1.2dc21ab8d7dedp+49 +0x1.2f8ac174d612bp-266 +0x1.31cfd3999f8a8p+993 +0x1.3230b0e1458b9p+662 +0x1.32d17ed585a68p+312 +0x1.3426172c7725bp+116 +0x1.3637b9149ee7fp+122 +0x1.3726987666b7fp+209 +0x1.37d99cc5cae1p-957 +0x1.38bcb1b7b8ddp+42 +0x1.395f47d212935p-854 +0x1.39dae6f76d881p-183 +0x1.39eb2afd8c6f5p+884 +0x1.3b374f06526dcp-379 +0x1.3b374f06be39ep-379 +0x1.3d3eec19204e3p+395 +0x1.3e08a42a8e288p+983 +0x1.3e9e4e4c2f34dp+199 +0x1.3f559f07c42d5p-967 +0x1.3faac3e4268fbp-482 +0x1.40aabc6c32e19p+973 +0x1.45ed1f223e843p-296 +0x1.488ab81dddb81p+36 +0x1.48f9dacb9d631p-688 +0x1.4a759f0dcba5dp+713 +0x1.4ed8c8fcc963cp-987 +0x1.4f3ca0a7e4ecap+848 +0x1.50425bd5d6771p-03 +0x1.50a6110d6a9b9p-698 +0x1.5109fe46434c8p-381 +0x1.518bbb10c84ccp-02 +0x1.53eda61407221p-605 +0x1.56786d7ccd9c7p+654 +0x1.5721039fbf168p+02 +0x1.573d68f9f0413p-512 +0x1.57f48bb41dbcfp+458 +0x1.59725db272f81p+262 +0x1.59725db272fa7p+262 +0x1.59dfd25c96bfdp-408 +0x1.5bf2ff083a346p-512 +0x1.5cc9442d2b209p+985 +0x1.6093b802d57bap+933 +0x1.60d1801f50ddbp+262 +0x1.6345785d8a002p+56 +0x1.64576250e6921p-898 +0x1.64f1f5e48f13p+358 +0x1.690a1ed7a4873p+29 +0x1.69794a1613ad7p-728 +0x1.6c8e5ca23aa96p+1016 +0x1.6ef09b26d3d42p-316 +0x1.70ef5464763dap-57 +0x1.7424348d29ffep-449 +0x1.7688bb5394c7bp+325 +0x1.788cd4927b681p+614 +0x1.796655ae4429p-552 +0x1.7b6d71d20b96bp-263 +0x1.7e747a2ad5fbap+511 +0x1.8000p-1072 +0x1.82742934fc5c3p-562 +0x1.842142419a02ap-758 +0x1.88ba3bf284e26p+305 +0x1.8c240c4aecb19p-87 +0x1.8cf23d1a96bb6p-467 +0x1.8eb0138e0b711p+687 +0x1.9379fee3e753fp-386 +0x1.93e838c059d66p-682 +0x1.95aa0a1009d95p-238 +0x1.967e5eec85cd6p+873 +0x1.9692c9706062fp-01 +0x1.992c7fdc216fbp-489 +0x1.99348dd65a31p-04 +0x1.998c4fbe21f7fp+22 +0x1.99e46124954a4p-244 +0x1.9b6056729af7p-200 +0x1.9bcdfb5e26c7cp+285 +0x1.a050254dd535dp+35 +0x1.a102abef73abdp-788 +0x1.a2fe76a3f9589p-499 +0x1.a53fc963550b3p-210 +0x1.a6a2b85063ccbp-891 +0x1.a6cb1908caaf3p+212 +0x1.a6fcf33d58242p-02 +0x1.ab328946fc216p-313 +0x1.aca9160b990ecp-862 +0x1.ad9d3d509a26ap-107 +0x1.b8e5c91bd5b28p+361 +0x1.ba3e5933f77ecp+607 +0x1.bbf5514612f65p-01 +0x1.bc5c22a4f1345p+28 +0x1.be03d0bf27b29p-137 +0x1.c2e4e8e6a8411p+632 +0x1.c6fb34907fdc3p+08 +0x1.c94a20242966p+869 +0x1.c950c088a48f3p-879 +0x1.c9c57381560c7p-04 +0x1.ce49bcb573934p-154 +0x1.cf6b7cc9b192p+02 +0x1.cfa917863e1f1p+235 +0x1.d308ff9c32116p+1009 +0x1.d53845195fffp-838 +0x1.d632b70ff2cp+132 +0x1.d9388b3aa3b3p+906 +0x1.d9388fb8d94a7p+906 +0x1.d9bb64fb288d5p+906 +0x1.dc574d80cf16fp-456 +0x1.de681541264f7p-652 +0x1.df2826ef05546p-508 +0x1.e1f6fed366db6p+478 +0x1.eb6bafd94a46bp+793 +0x1.ed09bead87c31p+112 +0x1.f41561f45b82dp+05 +0x1.f5302f06cd971p+624 +0x1.f6932370dd1ep+867 +0x1.f7fe42f7606aap-01 +0x1.fb07c5aec9a37p-260 +0x1.fc1df6ae675f7p+876 +0x1.fdc04ab5671d4p-290 +0x1.ffffffffep-1039 +0x1.ffffffffffffep-293 +0x1.ffffffffffffep-476 +0x1.ffffffffffffep-764 +0x1.ffffffffffffep+572 +0x1.ffffffffffffep+746 +0x1.fffffffffffffp-237 +0x1.fffffffffffffp-47 +0x1.fffffffffffffp-629 +0x1.fffffffffffffp+135 +0x1.fffffffffffffp+584 +0x1.fffffffffffffp+796 +0x1.fffffffffffffp+85 +0x1.fffffffffffffp+950 diff --git a/src/internal/strconv/testdata/testfp.txt b/src/internal/strconv/testdata/testfp.txt index 08d3c4ef098..7a51f8a4143 100644 --- a/src/internal/strconv/testdata/testfp.txt +++ b/src/internal/strconv/testdata/testfp.txt @@ -18,7 +18,7 @@ # Difficult boundary cases, derived from tables given in # Vern Paxson, A Program for Testing IEEE Decimal-Binary Conversion -# ftp://ftp.ee.lbl.gov/testbase-report.ps.Z +# https://www.icir.org/vern/papers/testbase-report.pdf # Table 1: Stress Inputs for Conversion to 53-bit Binary, < 1/2 ULP float64 %b 5e+125 6653062250012735p+365 From e7358c6cf4ff6ae478e415d55b15835a19e72440 Mon Sep 17 00:00:00 2001 From: Ian Alexander Date: Tue, 18 Nov 2025 12:40:06 -0500 Subject: [PATCH 038/140] cmd/go: remove fips140 dependency on global Fetcher_ This commit makes Unzip a method on the *Fetcher type, and updates fips140 initialization to use a new Fetcher instance instead of the global Fetcher_ variable. Change-Id: Iea8d9ee4dd6e6a2be43520c144aaec6e75c9cd63 Reviewed-on: https://go-review.googlesource.com/c/go/+/722581 LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Matloob --- src/cmd/go/internal/fips140/fips140.go | 14 +++++++------- src/cmd/go/internal/modfetch/fetch.go | 19 +++++++++---------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/cmd/go/internal/fips140/fips140.go b/src/cmd/go/internal/fips140/fips140.go index 4194f0ff6aa..09e4141f997 100644 --- a/src/cmd/go/internal/fips140/fips140.go +++ b/src/cmd/go/internal/fips140/fips140.go @@ -85,11 +85,6 @@ package fips140 import ( - "cmd/go/internal/base" - "cmd/go/internal/cfg" - "cmd/go/internal/fsys" - "cmd/go/internal/modfetch" - "cmd/go/internal/str" "context" "os" "path" @@ -97,6 +92,12 @@ import ( "slices" "strings" + "cmd/go/internal/base" + "cmd/go/internal/cfg" + "cmd/go/internal/fsys" + "cmd/go/internal/modfetch" + "cmd/go/internal/str" + "golang.org/x/mod/module" "golang.org/x/mod/semver" ) @@ -224,12 +225,11 @@ func initDir() { mod := module.Version{Path: "golang.org/fips140", Version: v} file := filepath.Join(cfg.GOROOT, "lib/fips140", v+".zip") - zdir, err := modfetch.Unzip(context.Background(), mod, file) + zdir, err := modfetch.NewFetcher().Unzip(context.Background(), mod, file) if err != nil { base.Fatalf("go: unpacking GOFIPS140=%v: %v", v, err) } dir = filepath.Join(zdir, "fips140") - return } // ResolveImport resolves the import path imp. diff --git a/src/cmd/go/internal/modfetch/fetch.go b/src/cmd/go/internal/modfetch/fetch.go index 767bec4cd13..0d77ed29715 100644 --- a/src/cmd/go/internal/modfetch/fetch.go +++ b/src/cmd/go/internal/modfetch/fetch.go @@ -73,12 +73,12 @@ func Download(ctx context.Context, mod module.Version) (dir string, err error) { // Unzip is like Download but is given the explicit zip file to use, // rather than downloading it. This is used for the GOFIPS140 zip files, // which ship in the Go distribution itself. -func Unzip(ctx context.Context, mod module.Version, zipfile string) (dir string, err error) { +func (f *Fetcher) Unzip(ctx context.Context, mod module.Version, zipfile string) (dir string, err error) { if err := checkCacheDir(ctx); err != nil { base.Fatal(err) } - return Fetcher_.downloadCache.Do(mod, func() (string, error) { + return f.downloadCache.Do(mod, func() (string, error) { ctx, span := trace.StartSpan(ctx, "modfetch.Unzip "+mod.String()) defer span.Done() @@ -171,10 +171,10 @@ func unzip(ctx context.Context, mod module.Version, zipfile string) (dir string, // Go 1.14.2 and higher respect .partial files. Older versions may use // partially extracted directories. 'go mod verify' can detect this, // and 'go clean -modcache' can fix it. - if err := os.MkdirAll(parentDir, 0777); err != nil { + if err := os.MkdirAll(parentDir, 0o777); err != nil { return "", err } - if err := os.WriteFile(partialPath, nil, 0666); err != nil { + if err := os.WriteFile(partialPath, nil, 0o666); err != nil { return "", err } if err := modzip.Unzip(dir, mod, zipfile); err != nil { @@ -264,7 +264,7 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e } // Create parent directories. - if err := os.MkdirAll(filepath.Dir(zipfile), 0777); err != nil { + if err := os.MkdirAll(filepath.Dir(zipfile), 0o777); err != nil { return err } @@ -289,7 +289,7 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e // contents of the file (by hashing it) before we commit it. Because the file // is zip-compressed, we need an actual file — or at least an io.ReaderAt — to // validate it: we can't just tee the stream as we write it. - f, err := tempFile(ctx, filepath.Dir(zipfile), filepath.Base(zipfile), 0666) + f, err := tempFile(ctx, filepath.Dir(zipfile), filepath.Base(zipfile), 0o666) if err != nil { return err } @@ -404,7 +404,7 @@ func makeDirsReadOnly(dir string) { filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if err == nil && d.IsDir() { info, err := d.Info() - if err == nil && info.Mode()&0222 != 0 { + if err == nil && info.Mode()&0o222 != 0 { dirs = append(dirs, pathMode{path, info.Mode()}) } } @@ -413,7 +413,7 @@ func makeDirsReadOnly(dir string) { // Run over list backward to chmod children before parents. for i := len(dirs) - 1; i >= 0; i-- { - os.Chmod(dirs[i].path, dirs[i].mode&^0222) + os.Chmod(dirs[i].path, dirs[i].mode&^0o222) } } @@ -426,7 +426,7 @@ func RemoveAll(dir string) error { return nil // ignore errors walking in file system } if info.IsDir() { - os.Chmod(path, 0777) + os.Chmod(path, 0o777) } return nil }) @@ -970,7 +970,6 @@ Outer: tidyGoSum := tidyGoSum(data, keep) return tidyGoSum, nil }) - if err != nil { return fmt.Errorf("updating go.sum: %w", err) } From 7b904c25a27a1a2d33436826790459bc6dbbdb4a Mon Sep 17 00:00:00 2001 From: Ian Alexander Date: Thu, 20 Nov 2025 11:45:33 -0500 Subject: [PATCH 039/140] cmd/go/internal/modfetch: move global goSum to Fetcher_ [git-generate] cd src/cmd/go/internal/modfetch rf ' add Fetcher.sumState mu sync.Mutex ex { goSum.mu -> Fetcher_.mu goSum.m -> Fetcher_.sumState.m goSum.w -> Fetcher_.sumState.w goSum.status -> Fetcher_.sumState.status goSum.overwrite -> Fetcher_.sumState.overwrite goSum.enabled -> Fetcher_.sumState.enabled goSum.sumState -> Fetcher_.sumState } rm goSum ' Change-Id: I3693905d5bed19f7bae60f5bc1ec5097354c9427 Reviewed-on: https://go-review.googlesource.com/c/go/+/722582 Reviewed-by: Michael Matloob LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Matloob --- src/cmd/go/internal/modfetch/fetch.go | 128 +++++++++++++------------- 1 file changed, 62 insertions(+), 66 deletions(-) diff --git a/src/cmd/go/internal/modfetch/fetch.go b/src/cmd/go/internal/modfetch/fetch.go index 0d77ed29715..20a507e636c 100644 --- a/src/cmd/go/internal/modfetch/fetch.go +++ b/src/cmd/go/internal/modfetch/fetch.go @@ -442,11 +442,6 @@ type modSum struct { sum string } -var goSum struct { - mu sync.Mutex - sumState -} - type sumState struct { m map[module.Version][]string // content of go.sum file w map[string]map[module.Version][]string // sum file in workspace -> content of that sum file @@ -476,6 +471,7 @@ type Fetcher struct { // non-thread-safe SetState function. downloadCache *par.ErrCache[module.Version, string] // version → directory; + mu sync.Mutex sumState sumState } @@ -518,15 +514,15 @@ func SetState(newState *Fetcher) (oldState *Fetcher) { newState.downloadCache = new(par.ErrCache[module.Version, string]) } - goSum.mu.Lock() - defer goSum.mu.Unlock() + Fetcher_.mu.Lock() + defer Fetcher_.mu.Unlock() oldState = &Fetcher{ goSumFile: Fetcher_.goSumFile, workspaceGoSumFiles: Fetcher_.workspaceGoSumFiles, lookupCache: Fetcher_.lookupCache, downloadCache: Fetcher_.downloadCache, - sumState: goSum.sumState, + sumState: Fetcher_.sumState, } Fetcher_.SetGoSumFile(newState.goSumFile) @@ -537,7 +533,7 @@ func SetState(newState *Fetcher) (oldState *Fetcher) { Fetcher_.lookupCache = newState.lookupCache Fetcher_.downloadCache = newState.downloadCache // Set, or reset all fields on goSum. If being reset to empty, it will be initialized later. - goSum.sumState = newState.sumState + Fetcher_.sumState = newState.sumState return oldState } @@ -550,24 +546,24 @@ func initGoSum() (bool, error) { if Fetcher_.goSumFile == "" { return false, nil } - if goSum.m != nil { + if Fetcher_.sumState.m != nil { return true, nil } - goSum.m = make(map[module.Version][]string) - goSum.status = make(map[modSum]modSumStatus) - goSum.w = make(map[string]map[module.Version][]string) + Fetcher_.sumState.m = make(map[module.Version][]string) + Fetcher_.sumState.status = make(map[modSum]modSumStatus) + Fetcher_.sumState.w = make(map[string]map[module.Version][]string) for _, f := range Fetcher_.workspaceGoSumFiles { - goSum.w[f] = make(map[module.Version][]string) - _, err := readGoSumFile(goSum.w[f], f) + Fetcher_.sumState.w[f] = make(map[module.Version][]string) + _, err := readGoSumFile(Fetcher_.sumState.w[f], f) if err != nil { return false, err } } - enabled, err := readGoSumFile(goSum.m, Fetcher_.goSumFile) - goSum.enabled = enabled + enabled, err := readGoSumFile(Fetcher_.sumState.m, Fetcher_.goSumFile) + Fetcher_.sumState.enabled = enabled return enabled, err } @@ -637,27 +633,27 @@ func readGoSum(dst map[module.Version][]string, file string, data []byte) { // mod.Version may have a "/go.mod" suffix to distinguish sums for // .mod and .zip files. func HaveSum(mod module.Version) bool { - goSum.mu.Lock() - defer goSum.mu.Unlock() + Fetcher_.mu.Lock() + defer Fetcher_.mu.Unlock() inited, err := initGoSum() if err != nil || !inited { return false } - for _, goSums := range goSum.w { + for _, goSums := range Fetcher_.sumState.w { for _, h := range goSums[mod] { if !strings.HasPrefix(h, "h1:") { continue } - if !goSum.status[modSum{mod, h}].dirty { + if !Fetcher_.sumState.status[modSum{mod, h}].dirty { return true } } } - for _, h := range goSum.m[mod] { + for _, h := range Fetcher_.sumState.m[mod] { if !strings.HasPrefix(h, "h1:") { continue } - if !goSum.status[modSum{mod, h}].dirty { + if !Fetcher_.sumState.status[modSum{mod, h}].dirty { return true } } @@ -671,19 +667,19 @@ func HaveSum(mod module.Version) bool { // mod.Version may have a "/go.mod" suffix to distinguish sums for // .mod and .zip files. func RecordedSum(mod module.Version) (sum string, ok bool) { - goSum.mu.Lock() - defer goSum.mu.Unlock() + Fetcher_.mu.Lock() + defer Fetcher_.mu.Unlock() inited, err := initGoSum() foundSum := "" if err != nil || !inited { return "", false } - for _, goSums := range goSum.w { + for _, goSums := range Fetcher_.sumState.w { for _, h := range goSums[mod] { if !strings.HasPrefix(h, "h1:") { continue } - if !goSum.status[modSum{mod, h}].dirty { + if !Fetcher_.sumState.status[modSum{mod, h}].dirty { if foundSum != "" && foundSum != h { // conflicting sums exist return "", false } @@ -691,11 +687,11 @@ func RecordedSum(mod module.Version) (sum string, ok bool) { } } } - for _, h := range goSum.m[mod] { + for _, h := range Fetcher_.sumState.m[mod] { if !strings.HasPrefix(h, "h1:") { continue } - if !goSum.status[modSum{mod, h}].dirty { + if !Fetcher_.sumState.status[modSum{mod, h}].dirty { if foundSum != "" && foundSum != h { // conflicting sums exist return "", false } @@ -768,19 +764,19 @@ func checkModSum(mod module.Version, h string) error { // to checkSumDB. // Check whether mod+h is listed in go.sum already. If so, we're done. - goSum.mu.Lock() + Fetcher_.mu.Lock() inited, err := initGoSum() if err != nil { - goSum.mu.Unlock() + Fetcher_.mu.Unlock() return err } done := inited && haveModSumLocked(mod, h) if inited { - st := goSum.status[modSum{mod, h}] + st := Fetcher_.sumState.status[modSum{mod, h}] st.used = true - goSum.status[modSum{mod, h}] = st + Fetcher_.sumState.status[modSum{mod, h}] = st } - goSum.mu.Unlock() + Fetcher_.mu.Unlock() if done { return nil @@ -797,12 +793,12 @@ func checkModSum(mod module.Version, h string) error { // Add mod+h to go.sum, if it hasn't appeared already. if inited { - goSum.mu.Lock() + Fetcher_.mu.Lock() addModSumLocked(mod, h) - st := goSum.status[modSum{mod, h}] + st := Fetcher_.sumState.status[modSum{mod, h}] st.dirty = true - goSum.status[modSum{mod, h}] = st - goSum.mu.Unlock() + Fetcher_.sumState.status[modSum{mod, h}] = st + Fetcher_.mu.Unlock() } return nil } @@ -815,7 +811,7 @@ func haveModSumLocked(mod module.Version, h string) bool { if strings.HasSuffix(Fetcher_.goSumFile, "go.work.sum") { sumFileName = "go.work.sum" } - for _, vh := range goSum.m[mod] { + for _, vh := range Fetcher_.sumState.m[mod] { if h == vh { return true } @@ -827,7 +823,7 @@ func haveModSumLocked(mod module.Version, h string) bool { foundMatch := false // Check sums from all files in case there are conflicts between // the files. - for goSumFile, goSums := range goSum.w { + for goSumFile, goSums := range Fetcher_.sumState.w { for _, vh := range goSums[mod] { if h == vh { foundMatch = true @@ -845,10 +841,10 @@ func addModSumLocked(mod module.Version, h string) { if haveModSumLocked(mod, h) { return } - if len(goSum.m[mod]) > 0 { - fmt.Fprintf(os.Stderr, "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v"+hashVersionMismatch, mod.Path, mod.Version, strings.Join(goSum.m[mod], ", "), h) + if len(Fetcher_.sumState.m[mod]) > 0 { + fmt.Fprintf(os.Stderr, "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v"+hashVersionMismatch, mod.Path, mod.Version, strings.Join(Fetcher_.sumState.m[mod], ", "), h) } - goSum.m[mod] = append(goSum.m[mod], h) + Fetcher_.sumState.m[mod] = append(Fetcher_.sumState.m[mod], h) } // checkSumDB checks the mod, h pair against the Go checksum database. @@ -928,11 +924,11 @@ var ErrGoSumDirty = errors.New("updates to go.sum needed, disabled by -mod=reado // (version ends with "/go.mod"). Existing sums will be preserved unless they // have been marked for deletion with TrimGoSum. func WriteGoSum(ctx context.Context, keep map[module.Version]bool, readonly bool) error { - goSum.mu.Lock() - defer goSum.mu.Unlock() + Fetcher_.mu.Lock() + defer Fetcher_.mu.Unlock() // If we haven't read the go.sum file yet, don't bother writing it. - if !goSum.enabled { + if !Fetcher_.sumState.enabled { return nil } @@ -941,9 +937,9 @@ func WriteGoSum(ctx context.Context, keep map[module.Version]bool, readonly bool // just return without opening go.sum. dirty := false Outer: - for m, hs := range goSum.m { + for m, hs := range Fetcher_.sumState.m { for _, h := range hs { - st := goSum.status[modSum{m, h}] + st := Fetcher_.sumState.status[modSum{m, h}] if st.dirty && (!st.used || keep[m]) { dirty = true break Outer @@ -974,16 +970,16 @@ Outer: return fmt.Errorf("updating go.sum: %w", err) } - goSum.status = make(map[modSum]modSumStatus) - goSum.overwrite = false + Fetcher_.sumState.status = make(map[modSum]modSumStatus) + Fetcher_.sumState.overwrite = false return nil } // TidyGoSum returns a tidy version of the go.sum file. // A missing go.sum file is treated as if empty. func TidyGoSum(keep map[module.Version]bool) (before, after []byte) { - goSum.mu.Lock() - defer goSum.mu.Unlock() + Fetcher_.mu.Lock() + defer Fetcher_.mu.Unlock() before, err := lockedfile.Read(Fetcher_.goSumFile) if err != nil && !errors.Is(err, fs.ErrNotExist) { base.Fatalf("reading go.sum: %v", err) @@ -995,33 +991,33 @@ func TidyGoSum(keep map[module.Version]bool) (before, after []byte) { // tidyGoSum returns a tidy version of the go.sum file. // The goSum lock must be held. func tidyGoSum(data []byte, keep map[module.Version]bool) []byte { - if !goSum.overwrite { + if !Fetcher_.sumState.overwrite { // Incorporate any sums added by other processes in the meantime. // Add only the sums that we actually checked: the user may have edited or // truncated the file to remove erroneous hashes, and we shouldn't restore // them without good reason. - goSum.m = make(map[module.Version][]string, len(goSum.m)) - readGoSum(goSum.m, Fetcher_.goSumFile, data) - for ms, st := range goSum.status { + Fetcher_.sumState.m = make(map[module.Version][]string, len(Fetcher_.sumState.m)) + readGoSum(Fetcher_.sumState.m, Fetcher_.goSumFile, data) + for ms, st := range Fetcher_.sumState.status { if st.used && !sumInWorkspaceModulesLocked(ms.mod) { addModSumLocked(ms.mod, ms.sum) } } } - mods := make([]module.Version, 0, len(goSum.m)) - for m := range goSum.m { + mods := make([]module.Version, 0, len(Fetcher_.sumState.m)) + for m := range Fetcher_.sumState.m { mods = append(mods, m) } module.Sort(mods) var buf bytes.Buffer for _, m := range mods { - list := goSum.m[m] + list := Fetcher_.sumState.m[m] sort.Strings(list) str.Uniq(&list) for _, h := range list { - st := goSum.status[modSum{m, h}] + st := Fetcher_.sumState.status[modSum{m, h}] if (!st.dirty || (st.used && keep[m])) && !sumInWorkspaceModulesLocked(m) { fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h) } @@ -1031,7 +1027,7 @@ func tidyGoSum(data []byte, keep map[module.Version]bool) []byte { } func sumInWorkspaceModulesLocked(m module.Version) bool { - for _, goSums := range goSum.w { + for _, goSums := range Fetcher_.sumState.w { if _, ok := goSums[m]; ok { return true } @@ -1046,8 +1042,8 @@ func sumInWorkspaceModulesLocked(m module.Version) bool { // have entries for both module content sums and go.mod sums (version ends // with "/go.mod"). func TrimGoSum(keep map[module.Version]bool) { - goSum.mu.Lock() - defer goSum.mu.Unlock() + Fetcher_.mu.Lock() + defer Fetcher_.mu.Unlock() inited, err := initGoSum() if err != nil { base.Fatalf("%s", err) @@ -1056,12 +1052,12 @@ func TrimGoSum(keep map[module.Version]bool) { return } - for m, hs := range goSum.m { + for m, hs := range Fetcher_.sumState.m { if !keep[m] { for _, h := range hs { - goSum.status[modSum{m, h}] = modSumStatus{used: false, dirty: true} + Fetcher_.sumState.status[modSum{m, h}] = modSumStatus{used: false, dirty: true} } - goSum.overwrite = true + Fetcher_.sumState.overwrite = true } } } From a9093067ee0fda40421bd1d4fad893dfa6c99fd5 Mon Sep 17 00:00:00 2001 From: Guoqi Chen Date: Mon, 24 Nov 2025 12:56:15 +0800 Subject: [PATCH 040/140] cmd/internal/obj/loong64: add {,X}V{ADD,SUB}W{EV,OD}.{H.B,W.H,D.W,Q.D}{,U} instructions support Go asm syntax: VADDWEV{HB, WH, VW, QV}{,U} V1, V2, V3 VSUBWEV{HB, WH, VW, QV}{,U} V1, V2, V3 VADDWOD{HB, WH, VW, QV}{,U} V1, V2, V3 VSUBWOD{HB, WH, VW, QV}{,U} V1, V2, V3 XVADDWEV{HB, WH, VW, QV}{,U} X1, X2, X3 XVSUBWEV{HB, WH, VW, QV}{,U} X1, X2, X3 XVADDWOD{HB, WH, VW, QV}{,U} X1, X2, X3 XVSUBWOD{HB, WH, VW, QV}{,U} X1, X2, X3 Equivalent platform assembler syntax: vaddwev.{h.b, w.h, d.w, q.d}{,u} V3, V2, V1 vsubwev.{h.b, w.h, d.w, q.d}{,u} V3, V2, V1 vaddwod.{h.b, w.h, d.w, q.d}{,u} V3, V2, V1 vsubwod.{h.b, w.h, d.w, q.d}{,u} V3, V2, V1 xvaddwev.{h.b, w.h, d.w, q.d}{,u} X3, X2, X1 xvsubwev.{h.b, w.h, d.w, q.d}{,u} X3, X2, X1 xvaddwod.{h.b, w.h, d.w, q.d}{,u} X3, X2, X1 xvsubwod.{h.b, w.h, d.w, q.d}{,u} X3, X2, X1 Change-Id: I407dc65b32b89844fd303e265a99d8aafdf922ec Reviewed-on: https://go-review.googlesource.com/c/go/+/723620 Reviewed-by: Cherry Mui Reviewed-by: Meidan Li Reviewed-by: sophie zhao LUCI-TryBot-Result: Go LUCI Reviewed-by: Mark Freeman --- .../asm/internal/asm/testdata/loong64enc1.s | 72 +++++++ src/cmd/internal/obj/loong64/a.out.go | 65 ++++++ src/cmd/internal/obj/loong64/anames.go | 64 ++++++ src/cmd/internal/obj/loong64/asm.go | 192 ++++++++++++++++++ 4 files changed, 393 insertions(+) diff --git a/src/cmd/asm/internal/asm/testdata/loong64enc1.s b/src/cmd/asm/internal/asm/testdata/loong64enc1.s index b3fcf7db15e..fc6e2774169 100644 --- a/src/cmd/asm/internal/asm/testdata/loong64enc1.s +++ b/src/cmd/asm/internal/asm/testdata/loong64enc1.s @@ -1075,6 +1075,78 @@ lable2: XVMULWODVWUW X1, X2, X3 // 4304a374 XVMULWODQVUV X1, X2, X3 // 4384a374 + // [X]VADDW{EV/OD}.{H.B/W.H/D.W/Q.D} instructions + VADDWEVHB V1, V2, V3 // 43041e70 + VADDWEVWH V1, V2, V3 // 43841e70 + VADDWEVVW V1, V2, V3 // 43041f70 + VADDWEVQV V1, V2, V3 // 43841f70 + VADDWODHB V1, V2, V3 // 43042270 + VADDWODWH V1, V2, V3 // 43842270 + VADDWODVW V1, V2, V3 // 43042370 + VADDWODQV V1, V2, V3 // 43842370 + XVADDWEVHB X1, X2, X3 // 43041e74 + XVADDWEVWH X1, X2, X3 // 43841e74 + XVADDWEVVW X1, X2, X3 // 43041f74 + XVADDWEVQV X1, X2, X3 // 43841f74 + XVADDWODHB X1, X2, X3 // 43042274 + XVADDWODWH X1, X2, X3 // 43842274 + XVADDWODVW X1, X2, X3 // 43042374 + XVADDWODQV X1, X2, X3 // 43842374 + + // [X]VSUBW{EV/OD}.{H.B/W.H/D.W/Q.D} instructions + VSUBWEVHB V1, V2, V3 // 43042070 + VSUBWEVWH V1, V2, V3 // 43842070 + VSUBWEVVW V1, V2, V3 // 43042170 + VSUBWEVQV V1, V2, V3 // 43842170 + VSUBWODHB V1, V2, V3 // 43042470 + VSUBWODWH V1, V2, V3 // 43842470 + VSUBWODVW V1, V2, V3 // 43042570 + VSUBWODQV V1, V2, V3 // 43842570 + XVSUBWEVHB X1, X2, X3 // 43042074 + XVSUBWEVWH X1, X2, X3 // 43842074 + XVSUBWEVVW X1, X2, X3 // 43042174 + XVSUBWEVQV X1, X2, X3 // 43842174 + XVSUBWODHB X1, X2, X3 // 43042474 + XVSUBWODWH X1, X2, X3 // 43842474 + XVSUBWODVW X1, X2, X3 // 43042574 + XVSUBWODQV X1, X2, X3 // 43842574 + + // [X]VADDW{EV/OD}.{H.B/W.H/D.W/Q.D}U instructions + VADDWEVHBU V1, V2, V3 // 43042e70 + VADDWEVWHU V1, V2, V3 // 43042f70 + VADDWEVVWU V1, V2, V3 // 43042f70 + VADDWEVQVU V1, V2, V3 // 43842f70 + VADDWODHBU V1, V2, V3 // 43043270 + VADDWODWHU V1, V2, V3 // 43843270 + VADDWODVWU V1, V2, V3 // 43043370 + VADDWODQVU V1, V2, V3 // 43843370 + XVADDWEVHBU X1, X2, X3 // 43042e74 + XVADDWEVWHU X1, X2, X3 // 43842e74 + XVADDWEVVWU X1, X2, X3 // 43042f74 + XVADDWEVQVU X1, X2, X3 // 43842f74 + XVADDWODHBU X1, X2, X3 // 43043274 + XVADDWODWHU X1, X2, X3 // 43843274 + XVADDWODVWU X1, X2, X3 // 43043374 + XVADDWODQVU X1, X2, X3 // 43843374 + + // [X]VSUBW{EV/OD}.{H.B/W.H/D.W/Q.D}U instructions + VSUBWEVHBU V1, V2, V3 // 43043070 + VSUBWEVWHU V1, V2, V3 // 43843070 + VSUBWEVVWU V1, V2, V3 // 43043170 + VSUBWEVQVU V1, V2, V3 // 43843170 + VSUBWODHBU V1, V2, V3 // 43043470 + VSUBWODWHU V1, V2, V3 // 43843470 + VSUBWODVWU V1, V2, V3 // 43043570 + VSUBWODQVU V1, V2, V3 // 43843570 + XVSUBWEVHBU X1, X2, X3 // 43043074 + XVSUBWEVWHU X1, X2, X3 // 43843074 + XVSUBWEVVWU X1, X2, X3 // 43043174 + XVSUBWEVQVU X1, X2, X3 // 43843174 + XVSUBWODHBU X1, X2, X3 // 43043474 + XVSUBWODWHU X1, X2, X3 // 43843474 + XVSUBWODVWU X1, X2, X3 // 43043574 + XVSUBWODQVU X1, X2, X3 // 43843574 + // [X]VSHUF4I.{B/H/W/D} instructions VSHUF4IB $0, V2, V1 // 41009073 VSHUF4IB $16, V2, V1 // 41409073 diff --git a/src/cmd/internal/obj/loong64/a.out.go b/src/cmd/internal/obj/loong64/a.out.go index 2eabe9bda8a..96f0889199d 100644 --- a/src/cmd/internal/obj/loong64/a.out.go +++ b/src/cmd/internal/obj/loong64/a.out.go @@ -1159,6 +1159,71 @@ const ( AXVMULWODVWUW AXVMULWODQVUV + AVADDWEVHB + AVADDWEVWH + AVADDWEVVW + AVADDWEVQV + AVSUBWEVHB + AVSUBWEVWH + AVSUBWEVVW + AVSUBWEVQV + AVADDWODHB + AVADDWODWH + AVADDWODVW + AVADDWODQV + AVSUBWODHB + AVSUBWODWH + AVSUBWODVW + AVSUBWODQV + AXVADDWEVHB + AXVADDWEVWH + AXVADDWEVVW + AXVADDWEVQV + AXVSUBWEVHB + AXVSUBWEVWH + AXVSUBWEVVW + AXVSUBWEVQV + AXVADDWODHB + AXVADDWODWH + AXVADDWODVW + AXVADDWODQV + AXVSUBWODHB + AXVSUBWODWH + AXVSUBWODVW + AXVSUBWODQV + AVADDWEVHBU + AVADDWEVWHU + AVADDWEVVWU + AVADDWEVQVU + AVSUBWEVHBU + AVSUBWEVWHU + AVSUBWEVVWU + AVSUBWEVQVU + AVADDWODHBU + AVADDWODWHU + AVADDWODVWU + AVADDWODQVU + AVSUBWODHBU + AVSUBWODWHU + AVSUBWODVWU + AVSUBWODQVU + AXVADDWEVHBU + AXVADDWEVWHU + AXVADDWEVVWU + AXVADDWEVQVU + AXVSUBWEVHBU + AXVSUBWEVWHU + AXVSUBWEVVWU + AXVSUBWEVQVU + AXVADDWODHBU + AXVADDWODWHU + AXVADDWODVWU + AXVADDWODQVU + AXVSUBWODHBU + AXVSUBWODWHU + AXVSUBWODVWU + AXVSUBWODQVU + AVSHUF4IB AVSHUF4IH AVSHUF4IW diff --git a/src/cmd/internal/obj/loong64/anames.go b/src/cmd/internal/obj/loong64/anames.go index 92e3cab950f..0ee911401f0 100644 --- a/src/cmd/internal/obj/loong64/anames.go +++ b/src/cmd/internal/obj/loong64/anames.go @@ -628,6 +628,70 @@ var Anames = []string{ "XVMULWODWHUH", "XVMULWODVWUW", "XVMULWODQVUV", + "VADDWEVHB", + "VADDWEVWH", + "VADDWEVVW", + "VADDWEVQV", + "VSUBWEVHB", + "VSUBWEVWH", + "VSUBWEVVW", + "VSUBWEVQV", + "VADDWODHB", + "VADDWODWH", + "VADDWODVW", + "VADDWODQV", + "VSUBWODHB", + "VSUBWODWH", + "VSUBWODVW", + "VSUBWODQV", + "XVADDWEVHB", + "XVADDWEVWH", + "XVADDWEVVW", + "XVADDWEVQV", + "XVSUBWEVHB", + "XVSUBWEVWH", + "XVSUBWEVVW", + "XVSUBWEVQV", + "XVADDWODHB", + "XVADDWODWH", + "XVADDWODVW", + "XVADDWODQV", + "XVSUBWODHB", + "XVSUBWODWH", + "XVSUBWODVW", + "XVSUBWODQV", + "VADDWEVHBU", + "VADDWEVWHU", + "VADDWEVVWU", + "VADDWEVQVU", + "VSUBWEVHBU", + "VSUBWEVWHU", + "VSUBWEVVWU", + "VSUBWEVQVU", + "VADDWODHBU", + "VADDWODWHU", + "VADDWODVWU", + "VADDWODQVU", + "VSUBWODHBU", + "VSUBWODWHU", + "VSUBWODVWU", + "VSUBWODQVU", + "XVADDWEVHBU", + "XVADDWEVWHU", + "XVADDWEVVWU", + "XVADDWEVQVU", + "XVSUBWEVHBU", + "XVSUBWEVWHU", + "XVSUBWEVVWU", + "XVSUBWEVQVU", + "XVADDWODHBU", + "XVADDWODWHU", + "XVADDWODVWU", + "XVADDWODQVU", + "XVSUBWODHBU", + "XVSUBWODWHU", + "XVSUBWODVWU", + "XVSUBWODQVU", "VSHUF4IB", "VSHUF4IH", "VSHUF4IW", diff --git a/src/cmd/internal/obj/loong64/asm.go b/src/cmd/internal/obj/loong64/asm.go index 857ef31ca3a..9aff344931d 100644 --- a/src/cmd/internal/obj/loong64/asm.go +++ b/src/cmd/internal/obj/loong64/asm.go @@ -1791,6 +1791,38 @@ func buildop(ctxt *obj.Link) { opset(AVSLTHU, r0) opset(AVSLTWU, r0) opset(AVSLTVU, r0) + opset(AVADDWEVHB, r0) + opset(AVADDWEVWH, r0) + opset(AVADDWEVVW, r0) + opset(AVADDWEVQV, r0) + opset(AVSUBWEVHB, r0) + opset(AVSUBWEVWH, r0) + opset(AVSUBWEVVW, r0) + opset(AVSUBWEVQV, r0) + opset(AVADDWODHB, r0) + opset(AVADDWODWH, r0) + opset(AVADDWODVW, r0) + opset(AVADDWODQV, r0) + opset(AVSUBWODHB, r0) + opset(AVSUBWODWH, r0) + opset(AVSUBWODVW, r0) + opset(AVSUBWODQV, r0) + opset(AVADDWEVHBU, r0) + opset(AVADDWEVWHU, r0) + opset(AVADDWEVVWU, r0) + opset(AVADDWEVQVU, r0) + opset(AVSUBWEVHBU, r0) + opset(AVSUBWEVWHU, r0) + opset(AVSUBWEVVWU, r0) + opset(AVSUBWEVQVU, r0) + opset(AVADDWODHBU, r0) + opset(AVADDWODWHU, r0) + opset(AVADDWODVWU, r0) + opset(AVADDWODQVU, r0) + opset(AVSUBWODHBU, r0) + opset(AVSUBWODWHU, r0) + opset(AVSUBWODVWU, r0) + opset(AVSUBWODQVU, r0) case AXVSLTB: opset(AXVSLTH, r0) @@ -1800,6 +1832,38 @@ func buildop(ctxt *obj.Link) { opset(AXVSLTHU, r0) opset(AXVSLTWU, r0) opset(AXVSLTVU, r0) + opset(AXVADDWEVHB, r0) + opset(AXVADDWEVWH, r0) + opset(AXVADDWEVVW, r0) + opset(AXVADDWEVQV, r0) + opset(AXVSUBWEVHB, r0) + opset(AXVSUBWEVWH, r0) + opset(AXVSUBWEVVW, r0) + opset(AXVSUBWEVQV, r0) + opset(AXVADDWODHB, r0) + opset(AXVADDWODWH, r0) + opset(AXVADDWODVW, r0) + opset(AXVADDWODQV, r0) + opset(AXVSUBWODHB, r0) + opset(AXVSUBWODWH, r0) + opset(AXVSUBWODVW, r0) + opset(AXVSUBWODQV, r0) + opset(AXVADDWEVHBU, r0) + opset(AXVADDWEVWHU, r0) + opset(AXVADDWEVVWU, r0) + opset(AXVADDWEVQVU, r0) + opset(AXVSUBWEVHBU, r0) + opset(AXVSUBWEVWHU, r0) + opset(AXVSUBWEVVWU, r0) + opset(AXVSUBWEVQVU, r0) + opset(AXVADDWODHBU, r0) + opset(AXVADDWODWHU, r0) + opset(AXVADDWODVWU, r0) + opset(AXVADDWODQVU, r0) + opset(AXVSUBWODHBU, r0) + opset(AXVSUBWODWHU, r0) + opset(AXVSUBWODVWU, r0) + opset(AXVSUBWODQVU, r0) case AVANDB: opset(AVORB, r0) @@ -3612,6 +3676,134 @@ func (c *ctxt0) oprrr(a obj.As) uint32 { return 0xe946 << 15 // xvmulwod.d.wu.w case AXVMULWODQVUV: return 0xe947 << 15 // xvmulwod.q.du.d + case AVADDWEVHB: + return 0x0E03C << 15 // vaddwev.h.b + case AVADDWEVWH: + return 0x0E03D << 15 // vaddwev.w.h + case AVADDWEVVW: + return 0x0E03E << 15 // vaddwev.d.w + case AVADDWEVQV: + return 0x0E03F << 15 // vaddwev.q.d + case AVSUBWEVHB: + return 0x0E040 << 15 // vsubwev.h.b + case AVSUBWEVWH: + return 0x0E041 << 15 // vsubwev.w.h + case AVSUBWEVVW: + return 0x0E042 << 15 // vsubwev.d.w + case AVSUBWEVQV: + return 0x0E043 << 15 // vsubwev.q.d + case AVADDWODHB: + return 0x0E044 << 15 // vaddwod.h.b + case AVADDWODWH: + return 0x0E045 << 15 // vaddwod.w.h + case AVADDWODVW: + return 0x0E046 << 15 // vaddwod.d.w + case AVADDWODQV: + return 0x0E047 << 15 // vaddwod.q.d + case AVSUBWODHB: + return 0x0E048 << 15 // vsubwod.h.b + case AVSUBWODWH: + return 0x0E049 << 15 // vsubwod.w.h + case AVSUBWODVW: + return 0x0E04A << 15 // vsubwod.d.w + case AVSUBWODQV: + return 0x0E04B << 15 // vsubwod.q.d + case AXVADDWEVHB: + return 0x0E83C << 15 // xvaddwev.h.b + case AXVADDWEVWH: + return 0x0E83D << 15 // xvaddwev.w.h + case AXVADDWEVVW: + return 0x0E83E << 15 // xvaddwev.d.w + case AXVADDWEVQV: + return 0x0E83F << 15 // xvaddwev.q.d + case AXVSUBWEVHB: + return 0x0E840 << 15 // xvsubwev.h.b + case AXVSUBWEVWH: + return 0x0E841 << 15 // xvsubwev.w.h + case AXVSUBWEVVW: + return 0x0E842 << 15 // xvsubwev.d.w + case AXVSUBWEVQV: + return 0x0E843 << 15 // xvsubwev.q.d + case AXVADDWODHB: + return 0x0E844 << 15 // xvaddwod.h.b + case AXVADDWODWH: + return 0x0E845 << 15 // xvaddwod.w.h + case AXVADDWODVW: + return 0x0E846 << 15 // xvaddwod.d.w + case AXVADDWODQV: + return 0x0E847 << 15 // xvaddwod.q.d + case AXVSUBWODHB: + return 0x0E848 << 15 // xvsubwod.h.b + case AXVSUBWODWH: + return 0x0E849 << 15 // xvsubwod.w.h + case AXVSUBWODVW: + return 0x0E84A << 15 // xvsubwod.d.w + case AXVSUBWODQV: + return 0x0E84B << 15 // xvsubwod.q.d + case AVADDWEVHBU: + return 0x0E05C << 15 // vaddwev.h.bu + case AVADDWEVWHU: + return 0x0E05E << 15 // vaddwev.w.hu + case AVADDWEVVWU: + return 0x0E05E << 15 // vaddwev.d.wu + case AVADDWEVQVU: + return 0x0E05F << 15 // vaddwev.q.du + case AVSUBWEVHBU: + return 0x0E060 << 15 // vsubwev.h.bu + case AVSUBWEVWHU: + return 0x0E061 << 15 // vsubwev.w.hu + case AVSUBWEVVWU: + return 0x0E062 << 15 // vsubwev.d.wu + case AVSUBWEVQVU: + return 0x0E063 << 15 // vsubwev.q.du + case AVADDWODHBU: + return 0x0E064 << 15 // vaddwod.h.bu + case AVADDWODWHU: + return 0x0E065 << 15 // vaddwod.w.hu + case AVADDWODVWU: + return 0x0E066 << 15 // vaddwod.d.wu + case AVADDWODQVU: + return 0x0E067 << 15 // vaddwod.q.du + case AVSUBWODHBU: + return 0x0E068 << 15 // vsubwod.h.bu + case AVSUBWODWHU: + return 0x0E069 << 15 // vsubwod.w.hu + case AVSUBWODVWU: + return 0x0E06A << 15 // vsubwod.d.wu + case AVSUBWODQVU: + return 0x0E06B << 15 // vsubwod.q.du + case AXVADDWEVHBU: + return 0x0E85C << 15 // xvaddwev.h.bu + case AXVADDWEVWHU: + return 0x0E85D << 15 // xvaddwev.w.hu + case AXVADDWEVVWU: + return 0x0E85E << 15 // xvaddwev.d.wu + case AXVADDWEVQVU: + return 0x0E85F << 15 // xvaddwev.q.du + case AXVSUBWEVHBU: + return 0x0E860 << 15 // xvsubwev.h.bu + case AXVSUBWEVWHU: + return 0x0E861 << 15 // xvsubwev.w.hu + case AXVSUBWEVVWU: + return 0x0E862 << 15 // xvsubwev.d.wu + case AXVSUBWEVQVU: + return 0x0E863 << 15 // xvsubwev.q.du + case AXVADDWODHBU: + return 0x0E864 << 15 // xvaddwod.h.bu + case AXVADDWODWHU: + return 0x0E865 << 15 // xvaddwod.w.hu + case AXVADDWODVWU: + return 0x0E866 << 15 // xvaddwod.d.wu + case AXVADDWODQVU: + return 0x0E867 << 15 // xvaddwod.q.du + case AXVSUBWODHBU: + return 0x0E868 << 15 // xvsubwod.h.bu + case AXVSUBWODWHU: + return 0x0E869 << 15 // xvsubwod.w.hu + case AXVSUBWODVWU: + return 0x0E86A << 15 // xvsubwod.d.wu + case AXVSUBWODQVU: + return 0x0E86B << 15 // xvsubwod.q.du case AVSLLB: return 0xe1d0 << 15 // vsll.b case AVSLLH: From 1768cb40b838a36f9bdcbe5381f2e086483f22f5 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Wed, 19 Nov 2025 17:32:42 +0100 Subject: [PATCH 041/140] crypto/tls: add SecP256r1/SecP384r1MLKEM1024 hybrid post-quantum key exchanges Fixes #71206 Change-Id: If3cf75261c56828b87ae6805bd2913f56a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/722140 Auto-Submit: Filippo Valsorda Reviewed-by: Cherry Mui Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI --- api/next/71206.txt | 4 + doc/godebug.md | 4 + .../6-stdlib/99-minor/crypto/tls/71206.md | 3 + src/crypto/tls/bogo_config.json | 4 +- src/crypto/tls/common.go | 31 ++- src/crypto/tls/common_string.go | 10 +- src/crypto/tls/defaults.go | 14 +- src/crypto/tls/defaults_fips140.go | 2 + src/crypto/tls/fips140_test.go | 10 +- src/crypto/tls/handshake_client.go | 51 ++--- src/crypto/tls/handshake_client_tls13.go | 45 +--- src/crypto/tls/handshake_server_tls13.go | 46 +--- src/crypto/tls/key_agreement.go | 32 ++- src/crypto/tls/key_schedule.go | 210 ++++++++++++++++-- src/crypto/tls/tls_test.go | 139 ++++++++---- src/internal/godebugs/table.go | 1 + 16 files changed, 405 insertions(+), 201 deletions(-) create mode 100644 api/next/71206.txt create mode 100644 doc/next/6-stdlib/99-minor/crypto/tls/71206.md diff --git a/api/next/71206.txt b/api/next/71206.txt new file mode 100644 index 00000000000..e29fe8350e5 --- /dev/null +++ b/api/next/71206.txt @@ -0,0 +1,4 @@ +pkg crypto/tls, const SecP256r1MLKEM768 = 4587 #71206 +pkg crypto/tls, const SecP256r1MLKEM768 CurveID #71206 +pkg crypto/tls, const SecP384r1MLKEM1024 = 4589 #71206 +pkg crypto/tls, const SecP384r1MLKEM1024 CurveID #71206 diff --git a/doc/godebug.md b/doc/godebug.md index 6163d134ce0..0d3354bc0fd 100644 --- a/doc/godebug.md +++ b/doc/godebug.md @@ -168,6 +168,10 @@ allows malformed hostnames containing colons outside of a bracketed IPv6 address The default `urlstrictcolons=1` rejects URLs such as `http://localhost:1:2` or `http://::1/`. Colons are permitted as part of a bracketed IPv6 address, such as `http://[::1]/`. +Go 1.26 enabled two additional post-quantum key exchange mechanisms: +SecP256r1MLKEM768 and SecP384r1MLKEM1024. The default can be reverted using the +[`tlssecpmlkem` setting](/pkg/crypto/tls/#Config.CurvePreferences). + Go 1.26 added a new `tracebacklabels` setting that controls the inclusion of goroutine labels set through the the `runtime/pprof` package. Setting `tracebacklabels=1` includes these key/value pairs in the goroutine status header of runtime diff --git a/doc/next/6-stdlib/99-minor/crypto/tls/71206.md b/doc/next/6-stdlib/99-minor/crypto/tls/71206.md new file mode 100644 index 00000000000..2caaa8021b9 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/tls/71206.md @@ -0,0 +1,3 @@ +The hybrid [SecP256r1MLKEM768] and [SecP384r1MLKEM1024] post-quantum key +exchanges are now enabled by default. They can be disabled by setting +[Config.CurvePreferences] or with the `tlssecpmlkem=0` GODEBUG setting. diff --git a/src/crypto/tls/bogo_config.json b/src/crypto/tls/bogo_config.json index ed3fc6ec3d6..a4664d6e6f8 100644 --- a/src/crypto/tls/bogo_config.json +++ b/src/crypto/tls/bogo_config.json @@ -219,7 +219,9 @@ 24, 25, 29, - 4588 + 4587, + 4588, + 4589 ], "ErrorMap": { ":ECH_REJECTED:": ["tls: server rejected ECH"] diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index d809624b880..993cfaf7c06 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -145,19 +145,31 @@ const ( type CurveID uint16 const ( - CurveP256 CurveID = 23 - CurveP384 CurveID = 24 - CurveP521 CurveID = 25 - X25519 CurveID = 29 - X25519MLKEM768 CurveID = 4588 + CurveP256 CurveID = 23 + CurveP384 CurveID = 24 + CurveP521 CurveID = 25 + X25519 CurveID = 29 + X25519MLKEM768 CurveID = 4588 + SecP256r1MLKEM768 CurveID = 4587 + SecP384r1MLKEM1024 CurveID = 4589 ) func isTLS13OnlyKeyExchange(curve CurveID) bool { - return curve == X25519MLKEM768 + switch curve { + case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024: + return true + default: + return false + } } func isPQKeyExchange(curve CurveID) bool { - return curve == X25519MLKEM768 + switch curve { + case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024: + return true + default: + return false + } } // TLS 1.3 Key Share. See RFC 8446, Section 4.2.8. @@ -787,6 +799,11 @@ type Config struct { // From Go 1.24, the default includes the [X25519MLKEM768] hybrid // post-quantum key exchange. To disable it, set CurvePreferences explicitly // or use the GODEBUG=tlsmlkem=0 environment variable. + // + // From Go 1.26, the default includes the [SecP256r1MLKEM768] and + // [SecP256r1MLKEM768] hybrid post-quantum key exchanges, too. To disable + // them, set CurvePreferences explicitly or use either the + // GODEBUG=tlsmlkem=0 or the GODEBUG=tlssecpmlkem=0 environment variable. CurvePreferences []CurveID // DynamicRecordSizingDisabled disables adaptive sizing of TLS records. diff --git a/src/crypto/tls/common_string.go b/src/crypto/tls/common_string.go index e15dd48838b..1e868e7162d 100644 --- a/src/crypto/tls/common_string.go +++ b/src/crypto/tls/common_string.go @@ -72,16 +72,19 @@ func _() { _ = x[CurveP521-25] _ = x[X25519-29] _ = x[X25519MLKEM768-4588] + _ = x[SecP256r1MLKEM768-4587] + _ = x[SecP384r1MLKEM1024-4589] } const ( _CurveID_name_0 = "CurveP256CurveP384CurveP521" _CurveID_name_1 = "X25519" - _CurveID_name_2 = "X25519MLKEM768" + _CurveID_name_2 = "SecP256r1MLKEM768X25519MLKEM768SecP384r1MLKEM1024" ) var ( _CurveID_index_0 = [...]uint8{0, 9, 18, 27} + _CurveID_index_2 = [...]uint8{0, 17, 31, 49} ) func (i CurveID) String() string { @@ -91,8 +94,9 @@ 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 == 4588: - return _CurveID_name_2 + case 4587 <= i && i <= 4589: + i -= 4587 + return _CurveID_name_2[_CurveID_index_2[i]:_CurveID_index_2[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 489a2750dff..8de8d7e0934 100644 --- a/src/crypto/tls/defaults.go +++ b/src/crypto/tls/defaults.go @@ -14,14 +14,24 @@ import ( // them to apply local policies. var tlsmlkem = godebug.New("tlsmlkem") +var tlssecpmlkem = godebug.New("tlssecpmlkem") // defaultCurvePreferences is the default set of supported key exchanges, as // well as the preference order. func defaultCurvePreferences() []CurveID { - if tlsmlkem.Value() == "0" { + 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} + default: + return []CurveID{ + X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, + X25519, CurveP256, CurveP384, CurveP521, + } } - return []CurveID{X25519MLKEM768, X25519, CurveP256, CurveP384, CurveP521} } // defaultSupportedSignatureAlgorithms returns the signature and hash algorithms that diff --git a/src/crypto/tls/defaults_fips140.go b/src/crypto/tls/defaults_fips140.go index 00176795eba..19132607938 100644 --- a/src/crypto/tls/defaults_fips140.go +++ b/src/crypto/tls/defaults_fips140.go @@ -32,6 +32,8 @@ var ( } allowedCurvePreferencesFIPS = []CurveID{ X25519MLKEM768, + SecP256r1MLKEM768, + SecP384r1MLKEM1024, CurveP256, CurveP384, CurveP521, diff --git a/src/crypto/tls/fips140_test.go b/src/crypto/tls/fips140_test.go index 291a19f44cd..96273c0fe0e 100644 --- a/src/crypto/tls/fips140_test.go +++ b/src/crypto/tls/fips140_test.go @@ -43,11 +43,15 @@ func isTLS13CipherSuite(id uint16) bool { } func generateKeyShare(group CurveID) keyShare { - key, err := generateECDHEKey(rand.Reader, group) + ke, err := keyExchangeForCurveID(group) if err != nil { panic(err) } - return keyShare{group: group, data: key.PublicKey().Bytes()} + _, shares, err := ke.keyShares(rand.Reader) + if err != nil { + panic(err) + } + return shares[0] } func TestFIPSServerProtocolVersion(t *testing.T) { @@ -132,7 +136,7 @@ func isFIPSCurve(id CurveID) bool { switch id { case CurveP256, CurveP384, CurveP521: return true - case X25519MLKEM768: + case X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024: // 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 c739544db67..e1ddcb3f106 100644 --- a/src/crypto/tls/handshake_client.go +++ b/src/crypto/tls/handshake_client.go @@ -11,7 +11,6 @@ import ( "crypto/ecdsa" "crypto/ed25519" "crypto/hpke" - "crypto/internal/fips140/mlkem" "crypto/internal/fips140/tls13" "crypto/rsa" "crypto/subtle" @@ -142,43 +141,21 @@ 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") } + // 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 + // ECDH share. curveID := hello.supportedCurves[0] - keyShareKeys = &keySharePrivateKeys{curveID: curveID} - // Note that if X25519MLKEM768 is supported, it will be first because - // the preference order is fixed. - if curveID == X25519MLKEM768 { - keyShareKeys.ecdhe, err = generateECDHEKey(config.rand(), X25519) - if err != nil { - return nil, nil, nil, err - } - seed := make([]byte, mlkem.SeedSize) - if _, err := io.ReadFull(config.rand(), seed); err != nil { - return nil, nil, nil, err - } - keyShareKeys.mlkem, err = mlkem.NewDecapsulationKey768(seed) - if err != nil { - return nil, nil, nil, err - } - mlkemEncapsulationKey := keyShareKeys.mlkem.EncapsulationKey().Bytes() - x25519EphemeralKey := keyShareKeys.ecdhe.PublicKey().Bytes() - hello.keyShares = []keyShare{ - {group: X25519MLKEM768, data: append(mlkemEncapsulationKey, x25519EphemeralKey...)}, - } - // If both X25519MLKEM768 and X25519 are supported, we send both key - // shares (as a fallback) and we reuse the same X25519 ephemeral - // key, as allowed by draft-ietf-tls-hybrid-design-09, Section 3.2. - if slices.Contains(hello.supportedCurves, X25519) { - hello.keyShares = append(hello.keyShares, keyShare{group: X25519, data: x25519EphemeralKey}) - } - } else { - if _, ok := curveForCurveID(curveID); !ok { - return nil, nil, nil, errors.New("tls: CurvePreferences includes unsupported curve") - } - keyShareKeys.ecdhe, err = generateECDHEKey(config.rand(), curveID) - if err != nil { - return nil, nil, nil, err - } - hello.keyShares = []keyShare{{group: curveID, data: keyShareKeys.ecdhe.PublicKey().Bytes()}} + ke, err := keyExchangeForCurveID(curveID) + if err != nil { + return nil, nil, nil, errors.New("tls: CurvePreferences includes unsupported curve") + } + keyShareKeys, hello.keyShares, err = ke.keyShares(config.rand()) + if err != nil { + return nil, nil, nil, err + } + // Only send the fallback ECDH share if the corresponding CurveID is enabled. + if len(hello.keyShares) == 2 && !slices.Contains(hello.supportedCurves, hello.keyShares[1].group) { + hello.keyShares = hello.keyShares[:1] } } diff --git a/src/crypto/tls/handshake_client_tls13.go b/src/crypto/tls/handshake_client_tls13.go index 7018bb2336b..2912d97f75e 100644 --- a/src/crypto/tls/handshake_client_tls13.go +++ b/src/crypto/tls/handshake_client_tls13.go @@ -10,7 +10,6 @@ import ( "crypto" "crypto/hkdf" "crypto/hmac" - "crypto/internal/fips140/mlkem" "crypto/internal/fips140/tls13" "crypto/rsa" "crypto/subtle" @@ -320,22 +319,18 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { c.sendAlert(alertIllegalParameter) return errors.New("tls: server sent an unnecessary HelloRetryRequest key_share") } - // Note: we don't support selecting X25519MLKEM768 in a HRR, because it - // is currently first in preference order, so if it's enabled we'll - // always send a key share for it. - // - // This will have to change once we support multiple hybrid KEMs. - if _, ok := curveForCurveID(curveID); !ok { + ke, err := keyExchangeForCurveID(curveID) + if err != nil { c.sendAlert(alertInternalError) return errors.New("tls: CurvePreferences includes unsupported curve") } - key, err := generateECDHEKey(c.config.rand(), curveID) + hs.keyShareKeys, hello.keyShares, err = ke.keyShares(c.config.rand()) if err != nil { c.sendAlert(alertInternalError) return err } - hs.keyShareKeys = &keySharePrivateKeys{curveID: curveID, ecdhe: key} - hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}} + // Do not send the fallback ECDH key share in a HRR response. + hello.keyShares = hello.keyShares[:1] } if len(hello.pskIdentities) > 0 { @@ -475,36 +470,16 @@ func (hs *clientHandshakeStateTLS13) processServerHello() error { func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error { c := hs.c - ecdhePeerData := hs.serverHello.serverShare.data - if hs.serverHello.serverShare.group == X25519MLKEM768 { - if len(ecdhePeerData) != mlkem.CiphertextSize768+x25519PublicKeySize { - c.sendAlert(alertIllegalParameter) - return errors.New("tls: invalid server X25519MLKEM768 key share") - } - ecdhePeerData = hs.serverHello.serverShare.data[mlkem.CiphertextSize768:] + ke, err := keyExchangeForCurveID(hs.serverHello.serverShare.group) + if err != nil { + c.sendAlert(alertInternalError) + return err } - peerKey, err := hs.keyShareKeys.ecdhe.Curve().NewPublicKey(ecdhePeerData) + sharedKey, err := ke.clientSharedSecret(hs.keyShareKeys, hs.serverHello.serverShare.data) if err != nil { c.sendAlert(alertIllegalParameter) return errors.New("tls: invalid server key share") } - sharedKey, err := hs.keyShareKeys.ecdhe.ECDH(peerKey) - if err != nil { - c.sendAlert(alertIllegalParameter) - return errors.New("tls: invalid server key share") - } - if hs.serverHello.serverShare.group == X25519MLKEM768 { - if hs.keyShareKeys.mlkem == nil { - return c.sendAlert(alertInternalError) - } - ciphertext := hs.serverHello.serverShare.data[:mlkem.CiphertextSize768] - mlkemShared, err := hs.keyShareKeys.mlkem.Decapsulate(ciphertext) - if err != nil { - c.sendAlert(alertIllegalParameter) - return errors.New("tls: invalid X25519MLKEM768 server key share") - } - sharedKey = append(mlkemShared, sharedKey...) - } c.curveID = hs.serverHello.serverShare.group earlySecret := hs.earlySecret diff --git a/src/crypto/tls/handshake_server_tls13.go b/src/crypto/tls/handshake_server_tls13.go index c227371aceb..1182307936c 100644 --- a/src/crypto/tls/handshake_server_tls13.go +++ b/src/crypto/tls/handshake_server_tls13.go @@ -11,7 +11,6 @@ import ( "crypto/hkdf" "crypto/hmac" "crypto/hpke" - "crypto/internal/fips140/mlkem" "crypto/internal/fips140/tls13" "crypto/rsa" "crypto/tls/internal/fips140tls" @@ -246,55 +245,16 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { } c.curveID = selectedGroup - ecdhGroup := selectedGroup - ecdhData := clientKeyShare.data - if selectedGroup == X25519MLKEM768 { - ecdhGroup = X25519 - if len(ecdhData) != mlkem.EncapsulationKeySize768+x25519PublicKeySize { - c.sendAlert(alertIllegalParameter) - return errors.New("tls: invalid X25519MLKEM768 client key share") - } - ecdhData = ecdhData[mlkem.EncapsulationKeySize768:] - } - if _, ok := curveForCurveID(ecdhGroup); !ok { + ke, err := keyExchangeForCurveID(selectedGroup) + if err != nil { c.sendAlert(alertInternalError) return errors.New("tls: CurvePreferences includes unsupported curve") } - key, err := generateECDHEKey(c.config.rand(), ecdhGroup) - if err != nil { - c.sendAlert(alertInternalError) - return err - } - hs.hello.serverShare = keyShare{group: selectedGroup, data: key.PublicKey().Bytes()} - peerKey, err := key.Curve().NewPublicKey(ecdhData) + hs.sharedKey, hs.hello.serverShare, err = ke.serverSharedSecret(c.config.rand(), clientKeyShare.data) if err != nil { c.sendAlert(alertIllegalParameter) return errors.New("tls: invalid client key share") } - hs.sharedKey, err = key.ECDH(peerKey) - if err != nil { - c.sendAlert(alertIllegalParameter) - return errors.New("tls: invalid client key share") - } - if selectedGroup == X25519MLKEM768 { - k, err := mlkem.NewEncapsulationKey768(clientKeyShare.data[:mlkem.EncapsulationKeySize768]) - if err != nil { - c.sendAlert(alertIllegalParameter) - return errors.New("tls: invalid X25519MLKEM768 client key share") - } - mlkemSharedSecret, ciphertext := k.Encapsulate() - // draft-kwiatkowski-tls-ecdhe-mlkem-02, Section 3.1.3: "For - // X25519MLKEM768, the shared secret is the concatenation of the ML-KEM - // shared secret and the X25519 shared secret. The shared secret is 64 - // bytes (32 bytes for each part)." - hs.sharedKey = append(mlkemSharedSecret, hs.sharedKey...) - // draft-kwiatkowski-tls-ecdhe-mlkem-02, Section 3.1.2: "When the - // X25519MLKEM768 group is negotiated, the server's key exchange value - // is the concatenation of an ML-KEM ciphertext returned from - // encapsulation to the client's encapsulation key, and the server's - // ephemeral X25519 share." - hs.hello.serverShare.data = append(ciphertext, hs.hello.serverShare.data...) - } selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, c.quic != nil) if err != nil { diff --git a/src/crypto/tls/key_agreement.go b/src/crypto/tls/key_agreement.go index 88116f941e0..26f7bd2c520 100644 --- a/src/crypto/tls/key_agreement.go +++ b/src/crypto/tls/key_agreement.go @@ -159,17 +159,17 @@ func hashForServerKeyExchange(sigType uint8, hashFunc crypto.Hash, version uint1 type ecdheKeyAgreement struct { version uint16 isRSA bool - key *ecdh.PrivateKey // ckx and preMasterSecret are generated in processServerKeyExchange // and returned in generateClientKeyExchange. ckx *clientKeyExchangeMsg preMasterSecret []byte - // curveID and signatureAlgorithm are set by processServerKeyExchange and - // generateServerKeyExchange. + // curveID, signatureAlgorithm, and key are set by processServerKeyExchange + // and generateServerKeyExchange. curveID CurveID signatureAlgorithm SignatureScheme + key *ecdh.PrivateKey } func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) { @@ -380,3 +380,29 @@ func (ka *ecdheKeyAgreement) generateClientKeyExchange(config *Config, clientHel return ka.preMasterSecret, ka.ckx, nil } + +// generateECDHEKey returns a PrivateKey that implements Diffie-Hellman +// according to RFC 8446, Section 4.2.8.2. +func generateECDHEKey(rand io.Reader, curveID CurveID) (*ecdh.PrivateKey, error) { + curve, ok := curveForCurveID(curveID) + if !ok { + return nil, errors.New("tls: internal error: unsupported curve") + } + + return curve.GenerateKey(rand) +} + +func curveForCurveID(id CurveID) (ecdh.Curve, bool) { + switch id { + case X25519: + return ecdh.X25519(), true + case CurveP256: + return ecdh.P256(), true + case CurveP384: + return ecdh.P384(), true + case CurveP521: + return ecdh.P521(), true + default: + return nil, false + } +} diff --git a/src/crypto/tls/key_schedule.go b/src/crypto/tls/key_schedule.go index 1426a276bf2..bfa22449c87 100644 --- a/src/crypto/tls/key_schedule.go +++ b/src/crypto/tls/key_schedule.go @@ -5,10 +5,11 @@ package tls import ( + "crypto" "crypto/ecdh" "crypto/hmac" - "crypto/internal/fips140/mlkem" "crypto/internal/fips140/tls13" + "crypto/mlkem" "errors" "hash" "io" @@ -50,35 +51,202 @@ func (c *cipherSuiteTLS13) exportKeyingMaterial(s *tls13.MasterSecret, transcrip } type keySharePrivateKeys struct { - curveID CurveID - ecdhe *ecdh.PrivateKey - mlkem *mlkem.DecapsulationKey768 + ecdhe *ecdh.PrivateKey + mlkem crypto.Decapsulator } -const x25519PublicKeySize = 32 +// A keyExchange implements a TLS 1.3 KEM. +type keyExchange interface { + // keyShares generates one or two key shares. + // + // The first one will match the id, the second (if present) reuses the + // traditional component of the requested hybrid, as allowed by + // draft-ietf-tls-hybrid-design-09, Section 3.2. + keyShares(rand io.Reader) (*keySharePrivateKeys, []keyShare, error) -// generateECDHEKey returns a PrivateKey that implements Diffie-Hellman -// according to RFC 8446, Section 4.2.8.2. -func generateECDHEKey(rand io.Reader, curveID CurveID) (*ecdh.PrivateKey, error) { - curve, ok := curveForCurveID(curveID) - if !ok { - return nil, errors.New("tls: internal error: unsupported curve") + // serverSharedSecret computes the shared secret and the server's key share. + serverSharedSecret(rand io.Reader, clientKeyShare []byte) ([]byte, keyShare, error) + + // clientSharedSecret computes the shared secret given the server's key + // share and the keys generated by keyShares. + clientSharedSecret(priv *keySharePrivateKeys, serverKeyShare []byte) ([]byte, error) +} + +func keyExchangeForCurveID(id CurveID) (keyExchange, error) { + newMLKEMPrivateKey768 := func(b []byte) (crypto.Decapsulator, error) { + return mlkem.NewDecapsulationKey768(b) + } + newMLKEMPrivateKey1024 := func(b []byte) (crypto.Decapsulator, error) { + return mlkem.NewDecapsulationKey1024(b) + } + newMLKEMPublicKey768 := func(b []byte) (crypto.Encapsulator, error) { + return mlkem.NewEncapsulationKey768(b) + } + newMLKEMPublicKey1024 := func(b []byte) (crypto.Encapsulator, error) { + return mlkem.NewEncapsulationKey1024(b) } - - return curve.GenerateKey(rand) -} - -func curveForCurveID(id CurveID) (ecdh.Curve, bool) { switch id { case X25519: - return ecdh.X25519(), true + return &ecdhKeyExchange{id, ecdh.X25519()}, nil case CurveP256: - return ecdh.P256(), true + return &ecdhKeyExchange{id, ecdh.P256()}, nil case CurveP384: - return ecdh.P384(), true + return &ecdhKeyExchange{id, ecdh.P384()}, nil case CurveP521: - return ecdh.P521(), true + return &ecdhKeyExchange{id, ecdh.P521()}, nil + case X25519MLKEM768: + return &hybridKeyExchange{id, ecdhKeyExchange{X25519, ecdh.X25519()}, + 32, mlkem.EncapsulationKeySize768, mlkem.CiphertextSize768, + newMLKEMPrivateKey768, newMLKEMPublicKey768}, nil + case SecP256r1MLKEM768: + return &hybridKeyExchange{id, ecdhKeyExchange{CurveP256, ecdh.P256()}, + 65, mlkem.EncapsulationKeySize768, mlkem.CiphertextSize768, + newMLKEMPrivateKey768, newMLKEMPublicKey768}, nil + case SecP384r1MLKEM1024: + return &hybridKeyExchange{id, ecdhKeyExchange{CurveP384, ecdh.P384()}, + 97, mlkem.EncapsulationKeySize1024, mlkem.CiphertextSize1024, + newMLKEMPrivateKey1024, newMLKEMPublicKey1024}, nil default: - return nil, false + return nil, errors.New("tls: unsupported key exchange") } } + +type ecdhKeyExchange struct { + id CurveID + curve ecdh.Curve +} + +func (ke *ecdhKeyExchange) keyShares(rand io.Reader) (*keySharePrivateKeys, []keyShare, error) { + priv, err := ke.curve.GenerateKey(rand) + if err != nil { + return nil, nil, err + } + return &keySharePrivateKeys{ecdhe: priv}, []keyShare{{ke.id, priv.PublicKey().Bytes()}}, nil +} + +func (ke *ecdhKeyExchange) serverSharedSecret(rand io.Reader, clientKeyShare []byte) ([]byte, keyShare, error) { + key, err := ke.curve.GenerateKey(rand) + if err != nil { + return nil, keyShare{}, err + } + peerKey, err := ke.curve.NewPublicKey(clientKeyShare) + if err != nil { + return nil, keyShare{}, err + } + sharedKey, err := key.ECDH(peerKey) + if err != nil { + return nil, keyShare{}, err + } + return sharedKey, keyShare{ke.id, key.PublicKey().Bytes()}, nil +} + +func (ke *ecdhKeyExchange) clientSharedSecret(priv *keySharePrivateKeys, serverKeyShare []byte) ([]byte, error) { + peerKey, err := ke.curve.NewPublicKey(serverKeyShare) + if err != nil { + return nil, err + } + sharedKey, err := priv.ecdhe.ECDH(peerKey) + if err != nil { + return nil, err + } + return sharedKey, nil +} + +type hybridKeyExchange struct { + id CurveID + ecdh ecdhKeyExchange + + ecdhElementSize int + mlkemPublicKeySize int + mlkemCiphertextSize int + + newMLKEMPrivateKey func([]byte) (crypto.Decapsulator, error) + newMLKEMPublicKey func([]byte) (crypto.Encapsulator, error) +} + +func (ke *hybridKeyExchange) keyShares(rand io.Reader) (*keySharePrivateKeys, []keyShare, error) { + priv, ecdhShares, err := ke.ecdh.keyShares(rand) + 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) + if err != nil { + return nil, nil, err + } + var shareData []byte + // For X25519MLKEM768, the ML-KEM-768 encapsulation key comes first. + // For SecP256r1MLKEM768 and SecP384r1MLKEM1024, the ECDH share comes first. + // See draft-ietf-tls-ecdhe-mlkem-02, Section 4.1. + if ke.id == X25519MLKEM768 { + shareData = append(priv.mlkem.Encapsulator().Bytes(), ecdhShares[0].data...) + } else { + shareData = append(ecdhShares[0].data, priv.mlkem.Encapsulator().Bytes()...) + } + return priv, []keyShare{{ke.id, shareData}, ecdhShares[0]}, nil +} + +func (ke *hybridKeyExchange) serverSharedSecret(rand io.Reader, clientKeyShare []byte) ([]byte, keyShare, error) { + if len(clientKeyShare) != ke.ecdhElementSize+ke.mlkemPublicKeySize { + return nil, keyShare{}, errors.New("tls: invalid client key share length for hybrid key exchange") + } + var ecdhShareData, mlkemShareData []byte + if ke.id == X25519MLKEM768 { + mlkemShareData = clientKeyShare[:ke.mlkemPublicKeySize] + ecdhShareData = clientKeyShare[ke.mlkemPublicKeySize:] + } else { + ecdhShareData = clientKeyShare[:ke.ecdhElementSize] + mlkemShareData = clientKeyShare[ke.ecdhElementSize:] + } + ecdhSharedSecret, ks, err := ke.ecdh.serverSharedSecret(rand, ecdhShareData) + if err != nil { + return nil, keyShare{}, err + } + mlkemPeerKey, err := ke.newMLKEMPublicKey(mlkemShareData) + if err != nil { + return nil, keyShare{}, err + } + mlkemSharedSecret, mlkemKeyShare := mlkemPeerKey.Encapsulate() + var sharedKey []byte + if ke.id == X25519MLKEM768 { + sharedKey = append(mlkemSharedSecret, ecdhSharedSecret...) + ks.data = append(mlkemKeyShare, ks.data...) + } else { + sharedKey = append(ecdhSharedSecret, mlkemSharedSecret...) + ks.data = append(ks.data, mlkemKeyShare...) + } + ks.group = ke.id + return sharedKey, ks, nil +} + +func (ke *hybridKeyExchange) clientSharedSecret(priv *keySharePrivateKeys, serverKeyShare []byte) ([]byte, error) { + if len(serverKeyShare) != ke.ecdhElementSize+ke.mlkemCiphertextSize { + return nil, errors.New("tls: invalid server key share length for hybrid key exchange") + } + var ecdhShareData, mlkemShareData []byte + if ke.id == X25519MLKEM768 { + mlkemShareData = serverKeyShare[:ke.mlkemCiphertextSize] + ecdhShareData = serverKeyShare[ke.mlkemCiphertextSize:] + } else { + ecdhShareData = serverKeyShare[:ke.ecdhElementSize] + mlkemShareData = serverKeyShare[ke.ecdhElementSize:] + } + ecdhSharedSecret, err := ke.ecdh.clientSharedSecret(priv, ecdhShareData) + if err != nil { + return nil, err + } + mlkemSharedSecret, err := priv.mlkem.Decapsulate(mlkemShareData) + if err != nil { + return nil, err + } + var sharedKey []byte + if ke.id == X25519MLKEM768 { + sharedKey = append(mlkemSharedSecret, ecdhSharedSecret...) + } else { + sharedKey = append(ecdhSharedSecret, mlkemSharedSecret...) + } + return sharedKey, nil +} diff --git a/src/crypto/tls/tls_test.go b/src/crypto/tls/tls_test.go index 6905f539499..af2828fd8da 100644 --- a/src/crypto/tls/tls_test.go +++ b/src/crypto/tls/tls_test.go @@ -11,6 +11,7 @@ import ( "crypto/ecdh" "crypto/ecdsa" "crypto/elliptic" + "crypto/internal/boring" "crypto/rand" "crypto/tls/internal/fips140tls" "crypto/x509" @@ -1964,84 +1965,134 @@ func testVerifyCertificates(t *testing.T, version uint16) { } func TestHandshakeMLKEM(t *testing.T) { - skipFIPS(t) // No X25519MLKEM768 in FIPS + if boring.Enabled && fips140tls.Required() { + t.Skip("ML-KEM not supported in BoringCrypto FIPS mode") + } + defaultWithPQ := []CurveID{X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, + X25519, CurveP256, CurveP384, CurveP521} + defaultWithoutPQ := []CurveID{X25519, CurveP256, CurveP384, CurveP521} var tests = []struct { - name string - clientConfig func(*Config) - serverConfig func(*Config) - preparation func(*testing.T) - expectClientSupport bool - expectMLKEM bool - expectHRR bool + name string + clientConfig func(*Config) + serverConfig func(*Config) + preparation func(*testing.T) + expectClient []CurveID + expectSelected CurveID + expectHRR bool }{ { - name: "Default", - expectClientSupport: true, - expectMLKEM: true, - expectHRR: false, + name: "Default", + expectClient: defaultWithPQ, + expectSelected: X25519MLKEM768, }, { name: "ClientCurvePreferences", clientConfig: func(config *Config) { config.CurvePreferences = []CurveID{X25519} }, - expectClientSupport: false, + expectClient: []CurveID{X25519}, + expectSelected: X25519, }, { name: "ServerCurvePreferencesX25519", serverConfig: func(config *Config) { config.CurvePreferences = []CurveID{X25519} }, - expectClientSupport: true, - expectMLKEM: false, - expectHRR: false, + expectClient: defaultWithPQ, + expectSelected: X25519, }, { name: "ServerCurvePreferencesHRR", serverConfig: func(config *Config) { config.CurvePreferences = []CurveID{CurveP256} }, - expectClientSupport: true, - expectMLKEM: false, - expectHRR: true, + expectClient: defaultWithPQ, + expectSelected: CurveP256, + expectHRR: true, + }, + { + name: "SecP256r1MLKEM768-Only", + clientConfig: func(config *Config) { + config.CurvePreferences = []CurveID{SecP256r1MLKEM768} + }, + expectClient: []CurveID{SecP256r1MLKEM768}, + expectSelected: SecP256r1MLKEM768, + }, + { + name: "SecP256r1MLKEM768-HRR", + serverConfig: func(config *Config) { + config.CurvePreferences = []CurveID{SecP256r1MLKEM768, CurveP256} + }, + expectClient: defaultWithPQ, + expectSelected: SecP256r1MLKEM768, + expectHRR: true, + }, + { + name: "SecP384r1MLKEM1024", + clientConfig: func(config *Config) { + config.CurvePreferences = []CurveID{SecP384r1MLKEM1024, CurveP384} + }, + expectClient: []CurveID{SecP384r1MLKEM1024, CurveP384}, + expectSelected: SecP384r1MLKEM1024, + }, + { + name: "CurveP256NoHRR", + clientConfig: func(config *Config) { + config.CurvePreferences = []CurveID{SecP256r1MLKEM768, CurveP256} + }, + serverConfig: func(config *Config) { + config.CurvePreferences = []CurveID{CurveP256} + }, + expectClient: []CurveID{SecP256r1MLKEM768, CurveP256}, + expectSelected: CurveP256, }, { name: "ClientMLKEMOnly", clientConfig: func(config *Config) { config.CurvePreferences = []CurveID{X25519MLKEM768} }, - expectClientSupport: true, - expectMLKEM: true, + expectClient: []CurveID{X25519MLKEM768}, + expectSelected: X25519MLKEM768, }, { name: "ClientSortedCurvePreferences", clientConfig: func(config *Config) { config.CurvePreferences = []CurveID{CurveP256, X25519MLKEM768} }, - expectClientSupport: true, - expectMLKEM: true, + expectClient: []CurveID{X25519MLKEM768, CurveP256}, + expectSelected: X25519MLKEM768, }, { name: "ClientTLSv12", clientConfig: func(config *Config) { config.MaxVersion = VersionTLS12 }, - expectClientSupport: false, + expectClient: defaultWithoutPQ, + expectSelected: X25519, }, { name: "ServerTLSv12", serverConfig: func(config *Config) { config.MaxVersion = VersionTLS12 }, - expectClientSupport: true, - expectMLKEM: false, + expectClient: defaultWithPQ, + expectSelected: X25519, }, { - name: "GODEBUG", + name: "GODEBUG tlsmlkem=0", preparation: func(t *testing.T) { t.Setenv("GODEBUG", "tlsmlkem=0") }, - expectClientSupport: false, + expectClient: defaultWithoutPQ, + expectSelected: X25519, + }, + { + name: "GODEBUG tlssecpmlkem=0", + preparation: func(t *testing.T) { + t.Setenv("GODEBUG", "tlssecpmlkem=0") + }, + expectClient: []CurveID{X25519MLKEM768, X25519, CurveP256, CurveP384, CurveP521}, + expectSelected: X25519MLKEM768, }, } @@ -2049,6 +2100,9 @@ func TestHandshakeMLKEM(t *testing.T) { baseConfig.CurvePreferences = nil for _, test := range tests { t.Run(test.name, func(t *testing.T) { + if fips140tls.Required() && test.expectSelected == X25519 { + t.Skip("X25519 not supported in FIPS mode") + } if test.preparation != nil { test.preparation(t) } else { @@ -2059,10 +2113,12 @@ func TestHandshakeMLKEM(t *testing.T) { test.serverConfig(serverConfig) } serverConfig.GetConfigForClient = func(hello *ClientHelloInfo) (*Config, error) { - if !test.expectClientSupport && slices.Contains(hello.SupportedCurves, X25519MLKEM768) { - return nil, errors.New("client supports X25519MLKEM768") - } else if test.expectClientSupport && !slices.Contains(hello.SupportedCurves, X25519MLKEM768) { - return nil, errors.New("client does not support X25519MLKEM768") + expectClient := slices.Clone(test.expectClient) + expectClient = slices.DeleteFunc(expectClient, func(c CurveID) bool { + return fips140tls.Required() && c == X25519 + }) + if !slices.Equal(hello.SupportedCurves, expectClient) { + t.Errorf("got client curves %v, expected %v", hello.SupportedCurves, expectClient) } return nil, nil } @@ -2074,20 +2130,11 @@ func TestHandshakeMLKEM(t *testing.T) { if err != nil { t.Fatal(err) } - if test.expectMLKEM { - if ss.CurveID != X25519MLKEM768 { - t.Errorf("got CurveID %v (server), expected %v", ss.CurveID, X25519MLKEM768) - } - if cs.CurveID != X25519MLKEM768 { - t.Errorf("got CurveID %v (client), expected %v", cs.CurveID, X25519MLKEM768) - } - } else { - if ss.CurveID == X25519MLKEM768 { - t.Errorf("got CurveID %v (server), expected not X25519MLKEM768", ss.CurveID) - } - if cs.CurveID == X25519MLKEM768 { - t.Errorf("got CurveID %v (client), expected not X25519MLKEM768", cs.CurveID) - } + if ss.CurveID != test.expectSelected { + t.Errorf("server selected curve %v, expected %v", ss.CurveID, test.expectSelected) + } + if cs.CurveID != test.expectSelected { + t.Errorf("client selected curve %v, expected %v", cs.CurveID, test.expectSelected) } if test.expectHRR { if !ss.HelloRetryRequest { diff --git a/src/internal/godebugs/table.go b/src/internal/godebugs/table.go index 4939e6ff109..f707fc34f2f 100644 --- a/src/internal/godebugs/table.go +++ b/src/internal/godebugs/table.go @@ -64,6 +64,7 @@ var All = []Info{ {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"}, {Name: "tlsunsafeekm", Package: "crypto/tls", Changed: 22, Old: "1"}, {Name: "updatemaxprocs", Package: "runtime", Changed: 25, Old: "0"}, From 272df5f6ba94018dac2b7d384e92115b795fe241 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Mon, 24 Nov 2025 13:35:15 +0100 Subject: [PATCH 042/140] crypto/internal/fips140/aes/gcm: add more GCM nonce modes First, this adds a GCM mode for QUIC, and a generic TLS 1.3/QUIC-like XOR'd counter mode. QUIC constructs nonces exactly like TLS 1.3, but the counter does not reset to zero on a key update, so the mask must be provided explicitly (or we will panic well before running out of counters because the wrong value is XOR'd out). See the analysis at https://github.com/quic-go/quic-go/issues/5077#issuecomment-3570352683 for the details of QUIC and FIPS 140-3 compliance (including a workaround for the key update issue). Second, this coalesces all the compliance modes around two schemes: fixed || counter, and fixed XOR counter. The former is used in TLS 1.2 and SSH, and the latter is used in TLS 1.3 and QUIC. This would not be a backwards compatible change if these were public APIs, but we only need different versions of the FIPS 140-3 module to be source-compatible with the standard library, and the callers of these NewGCMFor* functions only care that the return value implements cipher.AEAD, at least for now. This exposes no new public APIs, but lets us get started validating these functions in v2.0.0 of the FIPS 140-3 module. Updates #73110 Change-Id: I3d86cf8a3c4a96caf361c29f0db5f9706a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/723760 Auto-Submit: Filippo Valsorda Reviewed-by: Roland Shoemaker Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI --- src/crypto/cipher/gcm_fips140v2.0_test.go | 91 ++++++ src/crypto/cipher/gcm_test.go | 192 ++++++------- .../internal/fips140/aes/gcm/gcm_nonces.go | 266 +++++++++--------- 3 files changed, 323 insertions(+), 226 deletions(-) create mode 100644 src/crypto/cipher/gcm_fips140v2.0_test.go diff --git a/src/crypto/cipher/gcm_fips140v2.0_test.go b/src/crypto/cipher/gcm_fips140v2.0_test.go new file mode 100644 index 00000000000..c44497d4d82 --- /dev/null +++ b/src/crypto/cipher/gcm_fips140v2.0_test.go @@ -0,0 +1,91 @@ +// Copyright 2025 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 !fips140v1.0 + +package cipher_test + +import ( + "crypto/cipher" + "crypto/internal/fips140" + fipsaes "crypto/internal/fips140/aes" + "crypto/internal/fips140/aes/gcm" + "encoding/binary" + "math" + "testing" +) + +func TestGCMNoncesFIPSV2(t *testing.T) { + tryNonce := func(aead cipher.AEAD, nonce []byte) bool { + fips140.ResetServiceIndicator() + aead.Seal(nil, nonce, []byte("x"), nil) + return fips140.ServiceIndicator() + } + expectOK := func(t *testing.T, aead cipher.AEAD, nonce []byte) { + t.Helper() + if !tryNonce(aead, nonce) { + t.Errorf("expected service indicator true for %x", nonce) + } + } + expectPanic := func(t *testing.T, aead cipher.AEAD, nonce []byte) { + t.Helper() + defer func() { + t.Helper() + if recover() == nil { + t.Errorf("expected panic for %x", nonce) + } + }() + tryNonce(aead, nonce) + } + + t.Run("NewGCMWithXORCounterNonce", func(t *testing.T) { + newGCM := func() *gcm.GCMWithXORCounterNonce { + key := make([]byte, 16) + block, _ := fipsaes.New(key) + aead, _ := gcm.NewGCMWithXORCounterNonce(block) + return aead + } + nonce := func(mask []byte, counter uint64) []byte { + nonce := make([]byte, 12) + copy(nonce, mask) + n := binary.BigEndian.AppendUint64(nil, counter) + for i, b := range n { + nonce[4+i] ^= b + } + return nonce + } + + for _, mask := range [][]byte{ + decodeHex(t, "ffffffffffffffffffffffff"), + decodeHex(t, "aabbccddeeff001122334455"), + decodeHex(t, "000000000000000000000000"), + } { + g := newGCM() + // Mask is derived from first invocation with zero nonce. + expectOK(t, g, nonce(mask, 0)) + expectOK(t, g, nonce(mask, 1)) + expectOK(t, g, nonce(mask, 100)) + expectPanic(t, g, nonce(mask, 100)) + expectPanic(t, g, nonce(mask, 99)) + expectOK(t, g, nonce(mask, math.MaxUint64-2)) + expectOK(t, g, nonce(mask, math.MaxUint64-1)) + expectPanic(t, g, nonce(mask, math.MaxUint64)) + expectPanic(t, g, nonce(mask, 0)) + + g = newGCM() + g.SetNoncePrefixAndMask(mask) + expectOK(t, g, nonce(mask, 0xFFFFFFFF)) + expectOK(t, g, nonce(mask, math.MaxUint64-2)) + expectOK(t, g, nonce(mask, math.MaxUint64-1)) + expectPanic(t, g, nonce(mask, math.MaxUint64)) + expectPanic(t, g, nonce(mask, 0)) + + g = newGCM() + g.SetNoncePrefixAndMask(mask) + expectOK(t, g, nonce(mask, math.MaxUint64-1)) + expectPanic(t, g, nonce(mask, math.MaxUint64)) + expectPanic(t, g, nonce(mask, 0)) + } + }) +} diff --git a/src/crypto/cipher/gcm_test.go b/src/crypto/cipher/gcm_test.go index e574822c9aa..fe6bf5506e5 100644 --- a/src/crypto/cipher/gcm_test.go +++ b/src/crypto/cipher/gcm_test.go @@ -761,19 +761,13 @@ func TestGCMExtraMethods(t *testing.T) { }) } -func TestFIPSServiceIndicator(t *testing.T) { - newGCM := func() cipher.AEAD { - key := make([]byte, 16) - block, _ := fipsaes.New(key) - aead, _ := gcm.NewGCMWithCounterNonce(block) - return aead - } +func TestGCMNonces(t *testing.T) { tryNonce := func(aead cipher.AEAD, nonce []byte) bool { fips140.ResetServiceIndicator() aead.Seal(nil, nonce, []byte("x"), nil) return fips140.ServiceIndicator() } - expectTrue := func(t *testing.T, aead cipher.AEAD, nonce []byte) { + expectOK := func(t *testing.T, aead cipher.AEAD, nonce []byte) { t.Helper() if !tryNonce(aead, nonce) { t.Errorf("expected service indicator true for %x", nonce) @@ -790,108 +784,106 @@ func TestFIPSServiceIndicator(t *testing.T) { tryNonce(aead, nonce) } - g := newGCM() - expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) - expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) - expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}) - expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}) - expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}) - expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}) - expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}) - expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}) - expectTrue(t, g, []byte{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}) - expectTrue(t, g, []byte{0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}) - // Changed name. - expectPanic(t, g, []byte{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}) + t.Run("NewGCMWithCounterNonce", func(t *testing.T) { + newGCM := func() cipher.AEAD { + key := make([]byte, 16) + block, _ := fipsaes.New(key) + aead, _ := gcm.NewGCMWithCounterNonce(block) + return aead + } - g = newGCM() - expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) - // Went down. - expectPanic(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) + g := newGCM() + expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) + expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) + expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}) + expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}) + expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}) + expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}) + expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}) + expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}) + expectOK(t, g, []byte{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}) + expectOK(t, g, []byte{0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}) + // Changed name. + expectPanic(t, g, []byte{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}) - g = newGCM() - expectTrue(t, g, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}) - expectTrue(t, g, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13}) - // Did not increment. - expectPanic(t, g, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13}) + g = newGCM() + expectOK(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) + // Went down. + expectPanic(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) - g = newGCM() - expectTrue(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}) - expectTrue(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}) - // Wrap is ok as long as we don't run out of values. - expectTrue(t, g, []byte{1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0}) - expectTrue(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe}) - // Run out of counters. - expectPanic(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff}) + g = newGCM() + expectOK(t, g, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}) + expectOK(t, g, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13}) + // Did not increment. + expectPanic(t, g, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13}) - g = newGCM() - expectTrue(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}) - // Wrap with overflow. - expectPanic(t, g, []byte{1, 2, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0}) -} + g = newGCM() + expectOK(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}) + expectOK(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}) + // Wrap is ok as long as we don't run out of values. + expectOK(t, g, []byte{1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0}) + expectOK(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe}) + // Run out of counters. + expectPanic(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff}) -func TestGCMForSSH(t *testing.T) { - // incIV from x/crypto/ssh/cipher.go. - incIV := func(iv []byte) { - for i := 4 + 7; i >= 4; i-- { - iv[i]++ - if iv[i] != 0 { - break + g = newGCM() + expectOK(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}) + // Wrap with overflow. + expectPanic(t, g, []byte{1, 2, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0}) + }) + + t.Run("NewGCMForSSH", func(t *testing.T) { + newGCM := func() cipher.AEAD { + key := make([]byte, 16) + block, _ := fipsaes.New(key) + aead, _ := gcm.NewGCMForSSH(block) + return aead + } + // incIV from x/crypto/ssh/cipher.go. + incIV := func(iv []byte) { + for i := 4 + 7; i >= 4; i-- { + iv[i]++ + if iv[i] != 0 { + break + } } } - } - expectOK := func(aead cipher.AEAD, iv []byte) { - aead.Seal(nil, iv, []byte("hello, world"), nil) - } + aead := newGCM() + iv := decodeHex(t, "11223344"+"0000000000000000") + expectOK(t, aead, iv) + incIV(iv) + expectOK(t, aead, iv) + iv = decodeHex(t, "11223344"+"fffffffffffffffe") + expectOK(t, aead, iv) + incIV(iv) + expectPanic(t, aead, iv) - expectPanic := func(aead cipher.AEAD, iv []byte) { - defer func() { - if recover() == nil { - t.Errorf("expected panic") - } - }() - aead.Seal(nil, iv, []byte("hello, world"), nil) - } + // Wrapping is ok as long as we don't run out of values. + aead = newGCM() + iv = decodeHex(t, "11223344"+"fffffffffffffffe") + expectOK(t, aead, iv) + incIV(iv) + expectOK(t, aead, iv) + incIV(iv) + expectOK(t, aead, iv) + incIV(iv) + expectOK(t, aead, iv) - key := make([]byte, 16) - block, _ := fipsaes.New(key) - aead, err := gcm.NewGCMForSSH(block) - if err != nil { - t.Fatal(err) - } - iv := decodeHex(t, "11223344"+"0000000000000000") - expectOK(aead, iv) - incIV(iv) - expectOK(aead, iv) - iv = decodeHex(t, "11223344"+"fffffffffffffffe") - expectOK(aead, iv) - incIV(iv) - expectPanic(aead, iv) - - aead, _ = gcm.NewGCMForSSH(block) - iv = decodeHex(t, "11223344"+"fffffffffffffffe") - expectOK(aead, iv) - incIV(iv) - expectOK(aead, iv) - incIV(iv) - expectOK(aead, iv) - incIV(iv) - expectOK(aead, iv) - - aead, _ = gcm.NewGCMForSSH(block) - iv = decodeHex(t, "11223344"+"aaaaaaaaaaaaaaaa") - expectOK(aead, iv) - iv = decodeHex(t, "11223344"+"ffffffffffffffff") - expectOK(aead, iv) - incIV(iv) - expectOK(aead, iv) - iv = decodeHex(t, "11223344"+"aaaaaaaaaaaaaaa8") - expectOK(aead, iv) - incIV(iv) - expectPanic(aead, iv) - iv = decodeHex(t, "11223344"+"bbbbbbbbbbbbbbbb") - expectPanic(aead, iv) + aead = newGCM() + iv = decodeHex(t, "11223344"+"aaaaaaaaaaaaaaaa") + expectOK(t, aead, iv) + iv = decodeHex(t, "11223344"+"ffffffffffffffff") + expectOK(t, aead, iv) + incIV(iv) + expectOK(t, aead, iv) + iv = decodeHex(t, "11223344"+"aaaaaaaaaaaaaaa8") + expectOK(t, aead, iv) + incIV(iv) + expectPanic(t, aead, iv) + iv = decodeHex(t, "11223344"+"bbbbbbbbbbbbbbbb") + expectPanic(t, aead, iv) + }) } func decodeHex(t *testing.T, s string) []byte { diff --git a/src/crypto/internal/fips140/aes/gcm/gcm_nonces.go b/src/crypto/internal/fips140/aes/gcm/gcm_nonces.go index b1ac8152885..052349b5335 100644 --- a/src/crypto/internal/fips140/aes/gcm/gcm_nonces.go +++ b/src/crypto/internal/fips140/aes/gcm/gcm_nonces.go @@ -10,6 +10,7 @@ import ( "crypto/internal/fips140/alias" "crypto/internal/fips140/drbg" "crypto/internal/fips140deps/byteorder" + "errors" "math" ) @@ -45,7 +46,9 @@ func SealWithRandomNonce(g *GCM, nonce, out, plaintext, additionalData []byte) { // NewGCMWithCounterNonce returns a new AEAD that works like GCM, but enforces // the construction of deterministic nonces. The nonce must be 96 bits, the // first 32 bits must be an encoding of the module name, and the last 64 bits -// must be a counter. +// must be a counter. The starting value of the counter is set on the first call +// to Seal, and each subsequent call must increment it as a big-endian uint64. +// If the counter reaches the starting value minus one, Seal will panic. // // This complies with FIPS 140-3 IG C.H Scenario 3. func NewGCMWithCounterNonce(cipher *aes.Block) (*GCMWithCounterNonce, error) { @@ -56,41 +59,77 @@ func NewGCMWithCounterNonce(cipher *aes.Block) (*GCMWithCounterNonce, error) { return &GCMWithCounterNonce{g: *g}, nil } +// NewGCMForTLS12 returns a new AEAD that works like GCM, but enforces the +// construction of nonces as specified in RFC 5288, Section 3 and RFC 9325, +// Section 7.2.1. +// +// This complies with FIPS 140-3 IG C.H Scenario 1.a. +func NewGCMForTLS12(cipher *aes.Block) (*GCMWithCounterNonce, error) { + g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize) + if err != nil { + return nil, err + } + // TLS 1.2 counters always start at zero. + return &GCMWithCounterNonce{g: *g, startReady: true}, nil +} + +// NewGCMForSSH returns a new AEAD that works like GCM, but enforces the +// construction of nonces as specified in RFC 5647. +// +// This complies with FIPS 140-3 IG C.H Scenario 1.d. +func NewGCMForSSH(cipher *aes.Block) (*GCMWithCounterNonce, error) { + g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize) + if err != nil { + return nil, err + } + return &GCMWithCounterNonce{g: *g}, nil +} + type GCMWithCounterNonce struct { - g GCM - ready bool - fixedName uint32 - start uint64 - next uint64 + g GCM + prefixReady bool + prefix uint32 + startReady bool + start uint64 + next uint64 } func (g *GCMWithCounterNonce) NonceSize() int { return gcmStandardNonceSize } func (g *GCMWithCounterNonce) Overhead() int { return gcmTagSize } +// Seal implements the [cipher.AEAD] interface, checking that the nonce prefix +// is stable and that the counter is strictly increasing. +// +// It is not safe for concurrent use. func (g *GCMWithCounterNonce) Seal(dst, nonce, plaintext, data []byte) []byte { if len(nonce) != gcmStandardNonceSize { panic("crypto/cipher: incorrect nonce length given to GCM") } - counter := byteorder.BEUint64(nonce[len(nonce)-8:]) - if !g.ready { - // The first invocation sets the fixed name encoding and start counter. - g.ready = true - g.start = counter - g.fixedName = byteorder.BEUint32(nonce[:4]) + if !g.prefixReady { + // The first invocation sets the fixed prefix. + g.prefixReady = true + g.prefix = byteorder.BEUint32(nonce[:4]) } - if g.fixedName != byteorder.BEUint32(nonce[:4]) { - panic("crypto/cipher: incorrect module name given to GCMWithCounterNonce") + if g.prefix != byteorder.BEUint32(nonce[:4]) { + panic("crypto/cipher: GCM nonce prefix changed") + } + + counter := byteorder.BEUint64(nonce[len(nonce)-8:]) + if !g.startReady { + // The first invocation sets the starting counter, if not fixed. + g.startReady = true + g.start = counter } counter -= g.start - // Ensure the counter is monotonically increasing. + // Ensure the counter is strictly increasing. if counter == math.MaxUint64 { - panic("crypto/cipher: counter wrapped") + panic("crypto/cipher: counter exhausted") } if counter < g.next { - panic("crypto/cipher: counter decreased") + panic("crypto/cipher: counter decreased or remained the same") } g.next = counter + 1 @@ -103,93 +142,122 @@ func (g *GCMWithCounterNonce) Open(dst, nonce, ciphertext, data []byte) ([]byte, return g.g.Open(dst, nonce, ciphertext, data) } -// NewGCMForTLS12 returns a new AEAD that works like GCM, but enforces the -// construction of nonces as specified in RFC 5288, Section 3 and RFC 9325, -// Section 7.2.1. +// NewGCMWithXORCounterNonce returns a new AEAD that works like GCM, but +// enforces the construction of deterministic nonces. The nonce must be 96 bits, +// the first 32 bits must be an encoding of the module name, and the last 64 +// bits must be a counter XOR'd with a fixed value. The module name and XOR mask +// can be set with [GCMWithCounterNonce.SetNoncePrefixAndMask], or they are set +// on the first call to Seal, assuming the counter starts at zero. Each +// subsequent call must increment the counter as a big-endian uint64. If the +// counter reaches 2⁶⁴ minus one, Seal will panic. // -// This complies with FIPS 140-3 IG C.H Scenario 1.a. -func NewGCMForTLS12(cipher *aes.Block) (*GCMForTLS12, error) { +// This complies with FIPS 140-3 IG C.H Scenario 3. +func NewGCMWithXORCounterNonce(cipher *aes.Block) (*GCMWithXORCounterNonce, error) { g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize) if err != nil { return nil, err } - return &GCMForTLS12{g: *g}, nil -} - -type GCMForTLS12 struct { - g GCM - next uint64 -} - -func (g *GCMForTLS12) NonceSize() int { return gcmStandardNonceSize } - -func (g *GCMForTLS12) Overhead() int { return gcmTagSize } - -func (g *GCMForTLS12) Seal(dst, nonce, plaintext, data []byte) []byte { - if len(nonce) != gcmStandardNonceSize { - panic("crypto/cipher: incorrect nonce length given to GCM") - } - - counter := byteorder.BEUint64(nonce[len(nonce)-8:]) - - // Ensure the counter is monotonically increasing. - if counter == math.MaxUint64 { - panic("crypto/cipher: counter wrapped") - } - if counter < g.next { - panic("crypto/cipher: counter decreased") - } - g.next = counter + 1 - - fips140.RecordApproved() - return g.g.sealAfterIndicator(dst, nonce, plaintext, data) -} - -func (g *GCMForTLS12) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { - fips140.RecordApproved() - return g.g.Open(dst, nonce, ciphertext, data) + return &GCMWithXORCounterNonce{g: *g}, nil } // NewGCMForTLS13 returns a new AEAD that works like GCM, but enforces the // construction of nonces as specified in RFC 8446, Section 5.3. -func NewGCMForTLS13(cipher *aes.Block) (*GCMForTLS13, error) { +// +// This complies with FIPS 140-3 IG C.H Scenario 1.a. +func NewGCMForTLS13(cipher *aes.Block) (*GCMWithXORCounterNonce, error) { g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize) if err != nil { return nil, err } - return &GCMForTLS13{g: *g}, nil + return &GCMWithXORCounterNonce{g: *g}, nil } -type GCMForTLS13 struct { - g GCM - ready bool - mask uint64 - next uint64 +// NewGCMForQUIC returns a new AEAD that works like GCM, but enforces the +// construction of nonces as specified in RFC 9001, Section 5.3. +// +// Unlike in TLS 1.3, the QUIC nonce counter does not always start at zero, as +// the packet number does not reset on key updates, so the XOR mask must be +// provided explicitly instead of being learned on the first Seal call. Note +// that the nonce passed to Seal must already be XOR'd with the IV, the IV is +// provided here only to allow Seal to enforce that the counter is strictly +// increasing. +// +// This complies with FIPS 140-3 IG C.H Scenario 5. +func NewGCMForQUIC(cipher *aes.Block, iv []byte) (*GCMWithXORCounterNonce, error) { + g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize) + if err != nil { + return nil, err + } + gcm := &GCMWithXORCounterNonce{g: *g} + if err := gcm.SetNoncePrefixAndMask(iv); err != nil { + return nil, err + } + return gcm, nil } -func (g *GCMForTLS13) NonceSize() int { return gcmStandardNonceSize } +type GCMWithXORCounterNonce struct { + g GCM + ready bool + prefix uint32 + mask uint64 + next uint64 +} -func (g *GCMForTLS13) Overhead() int { return gcmTagSize } +// SetNoncePrefixAndMask sets the fixed prefix and XOR mask for the nonces used +// in Seal. It must be called before the first call to Seal. +// +// The first 32 bits of nonce are used as the fixed prefix, and the last 64 bits +// are used as the XOR mask. +// +// Note that Seal expects the nonce to be already XOR'd with the mask. The mask +// is provided here only to allow Seal to enforce that the counter is strictly +// increasing. +func (g *GCMWithXORCounterNonce) SetNoncePrefixAndMask(nonce []byte) error { + if len(nonce) != gcmStandardNonceSize { + return errors.New("crypto/cipher: incorrect nonce length given to SetNoncePrefixAndMask") + } + if g.ready { + return errors.New("crypto/cipher: SetNoncePrefixAndMask called twice or after first Seal") + } + g.prefix = byteorder.BEUint32(nonce[:4]) + g.mask = byteorder.BEUint64(nonce[4:]) + g.ready = true + return nil +} -func (g *GCMForTLS13) Seal(dst, nonce, plaintext, data []byte) []byte { +func (g *GCMWithXORCounterNonce) NonceSize() int { return gcmStandardNonceSize } + +func (g *GCMWithXORCounterNonce) Overhead() int { return gcmTagSize } + +// Seal implements the [cipher.AEAD] interface, checking that the nonce prefix +// is stable and that the counter is strictly increasing. +// +// It is not safe for concurrent use. +func (g *GCMWithXORCounterNonce) Seal(dst, nonce, plaintext, data []byte) []byte { if len(nonce) != gcmStandardNonceSize { panic("crypto/cipher: incorrect nonce length given to GCM") } counter := byteorder.BEUint64(nonce[len(nonce)-8:]) if !g.ready { - // In the first call, the counter is zero, so we learn the XOR mask. + // In the first call, if [GCMWithXORCounterNonce.SetNoncePrefixAndMask] + // wasn't used, we assume the counter is zero to learn the XOR mask and + // fixed prefix. g.ready = true g.mask = counter + g.prefix = byteorder.BEUint32(nonce[:4]) + } + if g.prefix != byteorder.BEUint32(nonce[:4]) { + panic("crypto/cipher: GCM nonce prefix changed") } counter ^= g.mask - // Ensure the counter is monotonically increasing. + // Ensure the counter is strictly increasing. if counter == math.MaxUint64 { - panic("crypto/cipher: counter wrapped") + panic("crypto/cipher: counter exhausted") } if counter < g.next { - panic("crypto/cipher: counter decreased") + panic("crypto/cipher: counter decreased or remained the same") } g.next = counter + 1 @@ -197,61 +265,7 @@ func (g *GCMForTLS13) Seal(dst, nonce, plaintext, data []byte) []byte { return g.g.sealAfterIndicator(dst, nonce, plaintext, data) } -func (g *GCMForTLS13) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { - fips140.RecordApproved() - return g.g.Open(dst, nonce, ciphertext, data) -} - -// NewGCMForSSH returns a new AEAD that works like GCM, but enforces the -// construction of nonces as specified in RFC 5647. -// -// This complies with FIPS 140-3 IG C.H Scenario 1.d. -func NewGCMForSSH(cipher *aes.Block) (*GCMForSSH, error) { - g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize) - if err != nil { - return nil, err - } - return &GCMForSSH{g: *g}, nil -} - -type GCMForSSH struct { - g GCM - ready bool - start uint64 - next uint64 -} - -func (g *GCMForSSH) NonceSize() int { return gcmStandardNonceSize } - -func (g *GCMForSSH) Overhead() int { return gcmTagSize } - -func (g *GCMForSSH) Seal(dst, nonce, plaintext, data []byte) []byte { - if len(nonce) != gcmStandardNonceSize { - panic("crypto/cipher: incorrect nonce length given to GCM") - } - - counter := byteorder.BEUint64(nonce[len(nonce)-8:]) - if !g.ready { - // In the first call we learn the start value. - g.ready = true - g.start = counter - } - counter -= g.start - - // Ensure the counter is monotonically increasing. - if counter == math.MaxUint64 { - panic("crypto/cipher: counter wrapped") - } - if counter < g.next { - panic("crypto/cipher: counter decreased") - } - g.next = counter + 1 - - fips140.RecordApproved() - return g.g.sealAfterIndicator(dst, nonce, plaintext, data) -} - -func (g *GCMForSSH) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { +func (g *GCMWithXORCounterNonce) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { fips140.RecordApproved() return g.g.Open(dst, nonce, ciphertext, data) } From 47baf48890397b6b6a11c9baeceaa36212a21108 Mon Sep 17 00:00:00 2001 From: Ian Alexander Date: Mon, 24 Nov 2025 10:53:31 -0500 Subject: [PATCH 043/140] cmd/go/internal/modfetch: inject Fetcher_ into TidyGoSum This commit begins the injection of the global Fetcher_ variable into the various function calls that make use of it. The purpose is to prepare for the eventual removal of the global Fetcher_ variable and eliminate global state. [git-generate] cd src/cmd/go/internal/modfetch rf ' inject Fetcher_ TidyGoSum mv haveModSumLocked.fetcher_ haveModSumLocked.f mv addModSumLocked.fetcher_ addModSumLocked.f mv tidyGoSum.fetcher_ tidyGoSum.f mv sumInWorkspaceModulesLocked.fetcher_ sumInWorkspaceModulesLocked.f ' Change-Id: Iecf736f17d6e63c856355284d09b7982dc9e16b6 Reviewed-on: https://go-review.googlesource.com/c/go/+/724240 LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Matloob Reviewed-by: Michael Matloob --- src/cmd/go/internal/modfetch/fetch.go | 54 +++++++++++++-------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/cmd/go/internal/modfetch/fetch.go b/src/cmd/go/internal/modfetch/fetch.go index 20a507e636c..cd38292901f 100644 --- a/src/cmd/go/internal/modfetch/fetch.go +++ b/src/cmd/go/internal/modfetch/fetch.go @@ -770,7 +770,7 @@ func checkModSum(mod module.Version, h string) error { Fetcher_.mu.Unlock() return err } - done := inited && haveModSumLocked(mod, h) + done := inited && haveModSumLocked(Fetcher_, mod, h) if inited { st := Fetcher_.sumState.status[modSum{mod, h}] st.used = true @@ -794,7 +794,7 @@ func checkModSum(mod module.Version, h string) error { // Add mod+h to go.sum, if it hasn't appeared already. if inited { Fetcher_.mu.Lock() - addModSumLocked(mod, h) + addModSumLocked(Fetcher_, mod, h) st := Fetcher_.sumState.status[modSum{mod, h}] st.dirty = true Fetcher_.sumState.status[modSum{mod, h}] = st @@ -806,12 +806,12 @@ func checkModSum(mod module.Version, h string) error { // haveModSumLocked reports whether the pair mod,h is already listed in go.sum. // If it finds a conflicting pair instead, it calls base.Fatalf. // goSum.mu must be locked. -func haveModSumLocked(mod module.Version, h string) bool { +func haveModSumLocked(f *Fetcher, mod module.Version, h string) bool { sumFileName := "go.sum" - if strings.HasSuffix(Fetcher_.goSumFile, "go.work.sum") { + if strings.HasSuffix(f.goSumFile, "go.work.sum") { sumFileName = "go.work.sum" } - for _, vh := range Fetcher_.sumState.m[mod] { + for _, vh := range f.sumState.m[mod] { if h == vh { return true } @@ -823,7 +823,7 @@ func haveModSumLocked(mod module.Version, h string) bool { foundMatch := false // Check sums from all files in case there are conflicts between // the files. - for goSumFile, goSums := range Fetcher_.sumState.w { + for goSumFile, goSums := range f.sumState.w { for _, vh := range goSums[mod] { if h == vh { foundMatch = true @@ -837,14 +837,14 @@ func haveModSumLocked(mod module.Version, h string) bool { // addModSumLocked adds the pair mod,h to go.sum. // goSum.mu must be locked. -func addModSumLocked(mod module.Version, h string) { - if haveModSumLocked(mod, h) { +func addModSumLocked(f *Fetcher, mod module.Version, h string) { + if haveModSumLocked(f, mod, h) { return } - if len(Fetcher_.sumState.m[mod]) > 0 { - fmt.Fprintf(os.Stderr, "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v"+hashVersionMismatch, mod.Path, mod.Version, strings.Join(Fetcher_.sumState.m[mod], ", "), h) + if len(f.sumState.m[mod]) > 0 { + fmt.Fprintf(os.Stderr, "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v"+hashVersionMismatch, mod.Path, mod.Version, strings.Join(f.sumState.m[mod], ", "), h) } - Fetcher_.sumState.m[mod] = append(Fetcher_.sumState.m[mod], h) + f.sumState.m[mod] = append(f.sumState.m[mod], h) } // checkSumDB checks the mod, h pair against the Go checksum database. @@ -963,7 +963,7 @@ Outer: } err := lockedfile.Transform(Fetcher_.goSumFile, func(data []byte) ([]byte, error) { - tidyGoSum := tidyGoSum(data, keep) + tidyGoSum := tidyGoSum(Fetcher_, data, keep) return tidyGoSum, nil }) if err != nil { @@ -984,41 +984,41 @@ func TidyGoSum(keep map[module.Version]bool) (before, after []byte) { if err != nil && !errors.Is(err, fs.ErrNotExist) { base.Fatalf("reading go.sum: %v", err) } - after = tidyGoSum(before, keep) + after = tidyGoSum(Fetcher_, before, keep) return before, after } // tidyGoSum returns a tidy version of the go.sum file. // The goSum lock must be held. -func tidyGoSum(data []byte, keep map[module.Version]bool) []byte { - if !Fetcher_.sumState.overwrite { +func tidyGoSum(f *Fetcher, data []byte, keep map[module.Version]bool) []byte { + if !f.sumState.overwrite { // Incorporate any sums added by other processes in the meantime. // Add only the sums that we actually checked: the user may have edited or // truncated the file to remove erroneous hashes, and we shouldn't restore // them without good reason. - Fetcher_.sumState.m = make(map[module.Version][]string, len(Fetcher_.sumState.m)) - readGoSum(Fetcher_.sumState.m, Fetcher_.goSumFile, data) - for ms, st := range Fetcher_.sumState.status { - if st.used && !sumInWorkspaceModulesLocked(ms.mod) { - addModSumLocked(ms.mod, ms.sum) + f.sumState.m = make(map[module.Version][]string, len(f.sumState.m)) + readGoSum(f.sumState.m, f.goSumFile, data) + for ms, st := range f.sumState.status { + if st.used && !sumInWorkspaceModulesLocked(f, ms.mod) { + addModSumLocked(f, ms.mod, ms.sum) } } } - mods := make([]module.Version, 0, len(Fetcher_.sumState.m)) - for m := range Fetcher_.sumState.m { + mods := make([]module.Version, 0, len(f.sumState.m)) + for m := range f.sumState.m { mods = append(mods, m) } module.Sort(mods) var buf bytes.Buffer for _, m := range mods { - list := Fetcher_.sumState.m[m] + list := f.sumState.m[m] sort.Strings(list) str.Uniq(&list) for _, h := range list { - st := Fetcher_.sumState.status[modSum{m, h}] - if (!st.dirty || (st.used && keep[m])) && !sumInWorkspaceModulesLocked(m) { + st := f.sumState.status[modSum{m, h}] + if (!st.dirty || (st.used && keep[m])) && !sumInWorkspaceModulesLocked(f, m) { fmt.Fprintf(&buf, "%s %s %s\n", m.Path, m.Version, h) } } @@ -1026,8 +1026,8 @@ func tidyGoSum(data []byte, keep map[module.Version]bool) []byte { return buf.Bytes() } -func sumInWorkspaceModulesLocked(m module.Version) bool { - for _, goSums := range Fetcher_.sumState.w { +func sumInWorkspaceModulesLocked(f *Fetcher, m module.Version) bool { + for _, goSums := range f.sumState.w { if _, ok := goSums[m]; ok { return true } From e96094402d55b6a104b642ce2adc76d3753843d9 Mon Sep 17 00:00:00 2001 From: Ian Alexander Date: Mon, 24 Nov 2025 12:38:27 -0500 Subject: [PATCH 044/140] cmd/go/internal/modload: inject modfetch.Fetcher_ into commitRequirements This commit continues the injection of the global Fetcher_ variable into the various function calls that make use of it. The purpose is to prepare for the eventual removal of the global Fetcher_ variable and eliminate global state within the modfetch package. [git-generate] cd src/cmd/go/internal/modload rf ' inject modfetch.Fetcher_ commitRequirements mv readModGraph.fetcher_ readModGraph.f ' cd ../modfetch sed -i ' s/for _, f := range fetcher_.workspaceGoSumFiles {/for _, fn := range fetcher_.workspaceGoSumFiles {/ s/fetcher_.sumState.w\[f\] = make(map\[module.Version\]\[\]string)/fetcher_.sumState.w[fn] = make(map[module.Version][]string)/ s/_, err := readGoSumFile(fetcher_.sumState.w\[f\], f)/_, err := readGoSumFile(fetcher_.sumState.w[fn], fn)/ ' fetch.go rf ' mv GoMod.fetcher_ GoMod.f mv GoMod Fetcher.GoMod mv readDiskGoMod.fetcher_ readDiskGoMod.f mv readDiskGoMod Fetcher.readDiskGoMod mv initGoSum.fetcher_ initGoSum.f mv initGoSum Fetcher.initGoSum mv HaveSum.fetcher_ HaveSum.f mv checkGoMod.fetcher_ checkGoMod.f mv checkModSum.fetcher_ checkModSum.f mv WriteGoSum.fetcher_ WriteGoSum.f mv WriteGoSum Fetcher.WriteGoSum mv Lookup.fetcher_ Lookup.f mv Lookup Fetcher.Lookup ' Change-Id: Ifbe7d6b90b93fd65a7443434035921e6b42dea1c Reviewed-on: https://go-review.googlesource.com/c/go/+/724241 Reviewed-by: Michael Matloob LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Matloob --- src/cmd/go/internal/load/pkg.go | 2 +- src/cmd/go/internal/modfetch/cache.go | 22 ++-- src/cmd/go/internal/modfetch/coderepo_test.go | 6 +- src/cmd/go/internal/modfetch/fetch.go | 102 +++++++++--------- src/cmd/go/internal/modfetch/repo.go | 10 +- src/cmd/go/internal/modget/get.go | 2 +- src/cmd/go/internal/modload/build.go | 6 +- src/cmd/go/internal/modload/buildlist.go | 33 +++--- src/cmd/go/internal/modload/edit.go | 7 +- src/cmd/go/internal/modload/import.go | 10 +- src/cmd/go/internal/modload/init.go | 12 +-- src/cmd/go/internal/modload/load.go | 24 ++--- src/cmd/go/internal/modload/modfile.go | 20 ++-- src/cmd/go/internal/modload/mvs.go | 2 +- src/cmd/go/internal/modload/query.go | 4 +- src/cmd/go/internal/modload/search.go | 3 +- src/cmd/go/internal/toolchain/switch.go | 2 +- 17 files changed, 135 insertions(+), 132 deletions(-) diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index 3b8bbdc91b8..705f4aa6a98 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -3416,7 +3416,7 @@ func PackagesAndErrorsOutsideModule(loaderstate *modload.State, ctx context.Cont if deprecation != "" { fmt.Fprintf(os.Stderr, "go: module %s is deprecated: %s\n", rootMod.Path, modload.ShortMessage(deprecation, "")) } - data, err := modfetch.GoMod(ctx, rootMod.Path, rootMod.Version) + data, err := modfetch.Fetcher_.GoMod(ctx, rootMod.Path, rootMod.Version) if err != nil { return nil, fmt.Errorf("%s: %w", args[0], err) } diff --git a/src/cmd/go/internal/modfetch/cache.go b/src/cmd/go/internal/modfetch/cache.go index 30020d24a71..b0bb7a878a4 100644 --- a/src/cmd/go/internal/modfetch/cache.go +++ b/src/cmd/go/internal/modfetch/cache.go @@ -309,7 +309,7 @@ func (r *cachingRepo) GoMod(ctx context.Context, version string) ([]byte, error) return r.repo(ctx).GoMod(ctx, version) } text, err := r.gomodCache.Do(version, func() ([]byte, error) { - file, text, err := readDiskGoMod(ctx, r.path, version) + file, text, err := Fetcher_.readDiskGoMod(ctx, r.path, version) if err == nil { // Note: readDiskGoMod already called checkGoMod. return text, nil @@ -317,7 +317,7 @@ func (r *cachingRepo) GoMod(ctx context.Context, version string) ([]byte, error) text, err = r.repo(ctx).GoMod(ctx, version) if err == nil { - if err := checkGoMod(r.path, version, text); err != nil { + if err := checkGoMod(Fetcher_, r.path, version, text); err != nil { return text, err } if err := writeDiskGoMod(ctx, file, text); err != nil { @@ -353,7 +353,7 @@ func InfoFile(ctx context.Context, path, version string) (*RevInfo, string, erro var info *RevInfo var err2info map[error]*RevInfo err := TryProxies(func(proxy string) error { - i, err := Lookup(ctx, proxy, path).Stat(ctx, version) + i, err := Fetcher_.Lookup(ctx, proxy, path).Stat(ctx, version) if err == nil { info = i } else { @@ -379,7 +379,7 @@ func InfoFile(ctx context.Context, path, version string) (*RevInfo, string, erro // GoMod is like Lookup(ctx, path).GoMod(rev) but avoids the // repository path resolution in Lookup if the result is // already cached on local disk. -func GoMod(ctx context.Context, path, rev string) ([]byte, error) { +func (f *Fetcher) GoMod(ctx context.Context, path, rev string) ([]byte, error) { // Convert commit hash to pseudo-version // to increase cache hit rate. if !gover.ModIsValid(path, rev) { @@ -390,7 +390,7 @@ func GoMod(ctx context.Context, path, rev string) ([]byte, error) { return nil, err } err := TryProxies(func(proxy string) error { - info, err := Lookup(ctx, proxy, path).Stat(ctx, rev) + info, err := f.Lookup(ctx, proxy, path).Stat(ctx, rev) if err == nil { rev = info.Version } @@ -402,13 +402,13 @@ func GoMod(ctx context.Context, path, rev string) ([]byte, error) { } } - _, data, err := readDiskGoMod(ctx, path, rev) + _, data, err := f.readDiskGoMod(ctx, path, rev) if err == nil { return data, nil } err = TryProxies(func(proxy string) (err error) { - data, err = Lookup(ctx, proxy, path).GoMod(ctx, rev) + data, err = f.Lookup(ctx, proxy, path).GoMod(ctx, rev) return err }) return data, err @@ -420,7 +420,7 @@ func GoModFile(ctx context.Context, path, version string) (string, error) { if !gover.ModIsValid(path, version) { return "", fmt.Errorf("invalid version %q", version) } - if _, err := GoMod(ctx, path, version); err != nil { + if _, err := Fetcher_.GoMod(ctx, path, version); err != nil { return "", err } // GoMod should have populated the disk cache for us. @@ -437,7 +437,7 @@ func GoModSum(ctx context.Context, path, version string) (string, error) { if !gover.ModIsValid(path, version) { return "", fmt.Errorf("invalid version %q", version) } - data, err := GoMod(ctx, path, version) + data, err := Fetcher_.GoMod(ctx, path, version) if err != nil { return "", err } @@ -565,7 +565,7 @@ var oldVgoPrefix = []byte("//vgo 0.0.") // returning the name of the cache file and the result. // If the read fails, the caller can use // writeDiskGoMod(file, data) to write a new cache entry. -func readDiskGoMod(ctx context.Context, path, rev string) (file string, data []byte, err error) { +func (f *Fetcher) readDiskGoMod(ctx context.Context, path, rev string) (file string, data []byte, err error) { if gover.IsToolchain(path) { return "", nil, errNotCached } @@ -578,7 +578,7 @@ func readDiskGoMod(ctx context.Context, path, rev string) (file string, data []b } if err == nil { - if err := checkGoMod(path, rev, data); err != nil { + if err := checkGoMod(f, path, rev, data); err != nil { return "", nil, err } } diff --git a/src/cmd/go/internal/modfetch/coderepo_test.go b/src/cmd/go/internal/modfetch/coderepo_test.go index 68594746601..df6e3ed0445 100644 --- a/src/cmd/go/internal/modfetch/coderepo_test.go +++ b/src/cmd/go/internal/modfetch/coderepo_test.go @@ -603,7 +603,7 @@ func TestCodeRepo(t *testing.T) { } ctx := context.Background() - repo := Lookup(ctx, "direct", tt.path) + repo := Fetcher_.Lookup(ctx, "direct", tt.path) if tt.mpath == "" { tt.mpath = tt.path @@ -831,7 +831,7 @@ func TestCodeRepoVersions(t *testing.T) { } ctx := context.Background() - repo := Lookup(ctx, "direct", tt.path) + repo := Fetcher_.Lookup(ctx, "direct", tt.path) list, err := repo.Versions(ctx, tt.prefix) if err != nil { t.Fatalf("Versions(%q): %v", tt.prefix, err) @@ -909,7 +909,7 @@ func TestLatest(t *testing.T) { } ctx := context.Background() - repo := Lookup(ctx, "direct", tt.path) + repo := Fetcher_.Lookup(ctx, "direct", tt.path) info, err := repo.Latest(ctx) if err != nil { if tt.err != "" { diff --git a/src/cmd/go/internal/modfetch/fetch.go b/src/cmd/go/internal/modfetch/fetch.go index cd38292901f..6cddc5b7fbd 100644 --- a/src/cmd/go/internal/modfetch/fetch.go +++ b/src/cmd/go/internal/modfetch/fetch.go @@ -210,7 +210,7 @@ func DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err e // Return early if the zip and ziphash files exist. if _, err := os.Stat(zipfile); err == nil { if _, err := os.Stat(ziphashfile); err == nil { - if !HaveSum(mod) { + if !HaveSum(Fetcher_, mod) { checkMod(ctx, mod) } return zipfile, nil @@ -305,7 +305,7 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e if unrecoverableErr != nil { return unrecoverableErr } - repo := Lookup(ctx, proxy, mod.Path) + repo := Fetcher_.Lookup(ctx, proxy, mod.Path) err := repo.Zip(ctx, f, mod.Version) if err != nil { // Zip may have partially written to f before failing. @@ -372,7 +372,7 @@ func hashZip(mod module.Version, zipfile, ziphashfile string) (err error) { if err != nil { return err } - if err := checkModSum(mod, hash); err != nil { + if err := checkModSum(Fetcher_, mod, hash); err != nil { return err } hf, err := lockedfile.Create(ziphashfile) @@ -542,28 +542,28 @@ func SetState(newState *Fetcher) (oldState *Fetcher) { // The boolean it returns reports whether the // use of go.sum is now enabled. // The goSum lock must be held. -func initGoSum() (bool, error) { - if Fetcher_.goSumFile == "" { +func (f *Fetcher) initGoSum() (bool, error) { + if f.goSumFile == "" { return false, nil } - if Fetcher_.sumState.m != nil { + if f.sumState.m != nil { return true, nil } - Fetcher_.sumState.m = make(map[module.Version][]string) - Fetcher_.sumState.status = make(map[modSum]modSumStatus) - Fetcher_.sumState.w = make(map[string]map[module.Version][]string) + f.sumState.m = make(map[module.Version][]string) + f.sumState.status = make(map[modSum]modSumStatus) + f.sumState.w = make(map[string]map[module.Version][]string) - for _, f := range Fetcher_.workspaceGoSumFiles { - Fetcher_.sumState.w[f] = make(map[module.Version][]string) - _, err := readGoSumFile(Fetcher_.sumState.w[f], f) + for _, fn := range f.workspaceGoSumFiles { + f.sumState.w[fn] = make(map[module.Version][]string) + _, err := readGoSumFile(f.sumState.w[fn], fn) if err != nil { return false, err } } - enabled, err := readGoSumFile(Fetcher_.sumState.m, Fetcher_.goSumFile) - Fetcher_.sumState.enabled = enabled + enabled, err := readGoSumFile(f.sumState.m, f.goSumFile) + f.sumState.enabled = enabled return enabled, err } @@ -632,28 +632,28 @@ func readGoSum(dst map[module.Version][]string, file string, data []byte) { // The entry's hash must be generated with a known hash algorithm. // mod.Version may have a "/go.mod" suffix to distinguish sums for // .mod and .zip files. -func HaveSum(mod module.Version) bool { - Fetcher_.mu.Lock() - defer Fetcher_.mu.Unlock() - inited, err := initGoSum() +func HaveSum(f *Fetcher, mod module.Version) bool { + f.mu.Lock() + defer f.mu.Unlock() + inited, err := f.initGoSum() if err != nil || !inited { return false } - for _, goSums := range Fetcher_.sumState.w { + for _, goSums := range f.sumState.w { for _, h := range goSums[mod] { if !strings.HasPrefix(h, "h1:") { continue } - if !Fetcher_.sumState.status[modSum{mod, h}].dirty { + if !f.sumState.status[modSum{mod, h}].dirty { return true } } } - for _, h := range Fetcher_.sumState.m[mod] { + for _, h := range f.sumState.m[mod] { if !strings.HasPrefix(h, "h1:") { continue } - if !Fetcher_.sumState.status[modSum{mod, h}].dirty { + if !f.sumState.status[modSum{mod, h}].dirty { return true } } @@ -669,7 +669,7 @@ func HaveSum(mod module.Version) bool { func RecordedSum(mod module.Version) (sum string, ok bool) { Fetcher_.mu.Lock() defer Fetcher_.mu.Unlock() - inited, err := initGoSum() + inited, err := Fetcher_.initGoSum() foundSum := "" if err != nil || !inited { return "", false @@ -730,7 +730,7 @@ func checkMod(ctx context.Context, mod module.Version) { base.Fatalf("verifying %v", module.VersionError(mod, fmt.Errorf("unexpected ziphash: %q", h))) } - if err := checkModSum(mod, h); err != nil { + if err := checkModSum(Fetcher_, mod, h); err != nil { base.Fatalf("%s", err) } } @@ -744,39 +744,39 @@ func goModSum(data []byte) (string, error) { // checkGoMod checks the given module's go.mod checksum; // data is the go.mod content. -func checkGoMod(path, version string, data []byte) error { +func checkGoMod(f *Fetcher, path, version string, data []byte) error { h, err := goModSum(data) if err != nil { return &module.ModuleError{Path: path, Version: version, Err: fmt.Errorf("verifying go.mod: %v", err)} } - return checkModSum(module.Version{Path: path, Version: version + "/go.mod"}, h) + return checkModSum(f, module.Version{Path: path, Version: version + "/go.mod"}, h) } // checkModSum checks that the recorded checksum for mod is h. // // mod.Version may have the additional suffix "/go.mod" to request the checksum // for the module's go.mod file only. -func checkModSum(mod module.Version, h string) error { +func checkModSum(f *Fetcher, mod module.Version, h string) error { // We lock goSum when manipulating it, // but we arrange to release the lock when calling checkSumDB, // so that parallel calls to checkModHash can execute parallel calls // to checkSumDB. // Check whether mod+h is listed in go.sum already. If so, we're done. - Fetcher_.mu.Lock() - inited, err := initGoSum() + f.mu.Lock() + inited, err := f.initGoSum() if err != nil { - Fetcher_.mu.Unlock() + f.mu.Unlock() return err } - done := inited && haveModSumLocked(Fetcher_, mod, h) + done := inited && haveModSumLocked(f, mod, h) if inited { - st := Fetcher_.sumState.status[modSum{mod, h}] + st := f.sumState.status[modSum{mod, h}] st.used = true - Fetcher_.sumState.status[modSum{mod, h}] = st + f.sumState.status[modSum{mod, h}] = st } - Fetcher_.mu.Unlock() + f.mu.Unlock() if done { return nil @@ -793,12 +793,12 @@ func checkModSum(mod module.Version, h string) error { // Add mod+h to go.sum, if it hasn't appeared already. if inited { - Fetcher_.mu.Lock() - addModSumLocked(Fetcher_, mod, h) - st := Fetcher_.sumState.status[modSum{mod, h}] + f.mu.Lock() + addModSumLocked(f, mod, h) + st := f.sumState.status[modSum{mod, h}] st.dirty = true - Fetcher_.sumState.status[modSum{mod, h}] = st - Fetcher_.mu.Unlock() + f.sumState.status[modSum{mod, h}] = st + f.mu.Unlock() } return nil } @@ -923,12 +923,12 @@ var ErrGoSumDirty = errors.New("updates to go.sum needed, disabled by -mod=reado // It should have entries for both module content sums and go.mod sums // (version ends with "/go.mod"). Existing sums will be preserved unless they // have been marked for deletion with TrimGoSum. -func WriteGoSum(ctx context.Context, keep map[module.Version]bool, readonly bool) error { - Fetcher_.mu.Lock() - defer Fetcher_.mu.Unlock() +func (f *Fetcher) WriteGoSum(ctx context.Context, keep map[module.Version]bool, readonly bool) error { + f.mu.Lock() + defer f.mu.Unlock() // If we haven't read the go.sum file yet, don't bother writing it. - if !Fetcher_.sumState.enabled { + if !f.sumState.enabled { return nil } @@ -937,9 +937,9 @@ func WriteGoSum(ctx context.Context, keep map[module.Version]bool, readonly bool // just return without opening go.sum. dirty := false Outer: - for m, hs := range Fetcher_.sumState.m { + for m, hs := range f.sumState.m { for _, h := range hs { - st := Fetcher_.sumState.status[modSum{m, h}] + st := f.sumState.status[modSum{m, h}] if st.dirty && (!st.used || keep[m]) { dirty = true break Outer @@ -952,7 +952,7 @@ Outer: if readonly { return ErrGoSumDirty } - if fsys.Replaced(Fetcher_.goSumFile) { + if fsys.Replaced(f.goSumFile) { base.Fatalf("go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay") } @@ -962,16 +962,16 @@ Outer: defer unlock() } - err := lockedfile.Transform(Fetcher_.goSumFile, func(data []byte) ([]byte, error) { - tidyGoSum := tidyGoSum(Fetcher_, data, keep) + err := lockedfile.Transform(f.goSumFile, func(data []byte) ([]byte, error) { + tidyGoSum := tidyGoSum(f, data, keep) return tidyGoSum, nil }) if err != nil { return fmt.Errorf("updating go.sum: %w", err) } - Fetcher_.sumState.status = make(map[modSum]modSumStatus) - Fetcher_.sumState.overwrite = false + f.sumState.status = make(map[modSum]modSumStatus) + f.sumState.overwrite = false return nil } @@ -1044,7 +1044,7 @@ func sumInWorkspaceModulesLocked(f *Fetcher, m module.Version) bool { func TrimGoSum(keep map[module.Version]bool) { Fetcher_.mu.Lock() defer Fetcher_.mu.Unlock() - inited, err := initGoSum() + inited, err := Fetcher_.initGoSum() if err != nil { base.Fatalf("%s", err) } diff --git a/src/cmd/go/internal/modfetch/repo.go b/src/cmd/go/internal/modfetch/repo.go index 23ddfdc8842..b1e197284fe 100644 --- a/src/cmd/go/internal/modfetch/repo.go +++ b/src/cmd/go/internal/modfetch/repo.go @@ -200,14 +200,14 @@ type lookupCacheKey struct { // // A successful return does not guarantee that the module // has any defined versions. -func Lookup(ctx context.Context, proxy, path string) Repo { +func (f *Fetcher) Lookup(ctx context.Context, proxy, path string) Repo { if traceRepo { defer logCall("Lookup(%q, %q)", proxy, path)() } - return Fetcher_.lookupCache.Do(lookupCacheKey{proxy, path}, func() Repo { + return f.lookupCache.Do(lookupCacheKey{proxy, path}, func() Repo { return newCachingRepo(ctx, path, func(ctx context.Context) (Repo, error) { - r, err := lookup(ctx, proxy, path) + r, err := lookup(f, ctx, proxy, path) if err == nil && traceRepo { r = newLoggingRepo(r) } @@ -248,14 +248,14 @@ func LookupLocal(ctx context.Context, codeRoot string, path string, dir string) } // lookup returns the module with the given module path. -func lookup(ctx context.Context, proxy, path string) (r Repo, err error) { +func lookup(fetcher_ *Fetcher, ctx context.Context, proxy, path string) (r Repo, err error) { if cfg.BuildMod == "vendor" { return nil, errLookupDisabled } switch path { case "go", "toolchain": - return &toolchainRepo{path, Lookup(ctx, proxy, "golang.org/toolchain")}, nil + return &toolchainRepo{path, fetcher_.Lookup(ctx, proxy, "golang.org/toolchain")}, nil } if module.MatchPrefixPatterns(cfg.GONOPROXY, path) { diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go index c8dc6e29bf6..839bee103f4 100644 --- a/src/cmd/go/internal/modget/get.go +++ b/src/cmd/go/internal/modget/get.go @@ -1788,7 +1788,7 @@ func (r *resolver) checkPackageProblems(loaderstate *modload.State, ctx context. if oldRepl := modload.Replacement(loaderstate, old); oldRepl.Path != "" { oldActual = oldRepl } - if mActual == oldActual || mActual.Version == "" || !modfetch.HaveSum(oldActual) { + if mActual == oldActual || mActual.Version == "" || !modfetch.HaveSum(modfetch.Fetcher_, oldActual) { continue } r.work.Add(func() { diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go index 4f334a47203..ab4245a563c 100644 --- a/src/cmd/go/internal/modload/build.go +++ b/src/cmd/go/internal/modload/build.go @@ -103,7 +103,7 @@ func ModuleInfo(loaderstate *State, ctx context.Context, path string) *modinfo.M v, ok = rs.rootSelected(loaderstate, path) } if !ok { - mg, err := rs.Graph(loaderstate, ctx) + mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) if err != nil { base.Fatal(err) } @@ -329,7 +329,7 @@ func moduleInfo(loaderstate *State, ctx context.Context, rs *Requirements, m mod checksumOk := func(suffix string) bool { return rs == nil || m.Version == "" || !mustHaveSums(loaderstate) || - modfetch.HaveSum(module.Version{Path: m.Path, Version: m.Version + suffix}) + modfetch.HaveSum(modfetch.Fetcher_, module.Version{Path: m.Path, Version: m.Version + suffix}) } mod := module.Version{Path: m.Path, Version: m.Version} @@ -355,7 +355,7 @@ func moduleInfo(loaderstate *State, ctx context.Context, rs *Requirements, m mod if m.GoVersion == "" && checksumOk("/go.mod") { // Load the go.mod file to determine the Go version, since it hasn't // already been populated from rawGoVersion. - if summary, err := rawGoModSummary(loaderstate, mod); err == nil && summary.goVersion != "" { + if summary, err := rawGoModSummary(modfetch.Fetcher_, loaderstate, mod); err == nil && summary.goVersion != "" { m.GoVersion = summary.goVersion } } diff --git a/src/cmd/go/internal/modload/buildlist.go b/src/cmd/go/internal/modload/buildlist.go index 37c2a6c759f..e086c5b351e 100644 --- a/src/cmd/go/internal/modload/buildlist.go +++ b/src/cmd/go/internal/modload/buildlist.go @@ -20,6 +20,7 @@ import ( "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/gover" + "cmd/go/internal/modfetch" "cmd/go/internal/mvs" "cmd/internal/par" @@ -269,9 +270,9 @@ func (rs *Requirements) hasRedundantRoot(loaderstate *State) bool { // // If the requirements of any relevant module fail to load, Graph also // returns a non-nil error of type *mvs.BuildListError. -func (rs *Requirements) Graph(loaderstate *State, ctx context.Context) (*ModuleGraph, error) { +func (rs *Requirements) Graph(fetcher_ *modfetch.Fetcher, loaderstate *State, ctx context.Context) (*ModuleGraph, error) { rs.graphOnce.Do(func() { - mg, mgErr := readModGraph(loaderstate, ctx, rs.pruning, rs.rootModules, nil) + mg, mgErr := readModGraph(fetcher_, loaderstate, ctx, rs.pruning, rs.rootModules, nil) rs.graph.Store(&cachedGraph{mg, mgErr}) }) cached := rs.graph.Load() @@ -307,7 +308,7 @@ var readModGraphDebugOnce sync.Once // // Unlike LoadModGraph, readModGraph does not attempt to diagnose or update // inconsistent roots. -func readModGraph(loaderstate *State, ctx context.Context, pruning modPruning, roots []module.Version, unprune map[module.Version]bool) (*ModuleGraph, error) { +func readModGraph(f *modfetch.Fetcher, loaderstate *State, ctx context.Context, pruning modPruning, roots []module.Version, unprune map[module.Version]bool) (*ModuleGraph, error) { mustHaveGoRoot(roots) if pruning == pruned { // Enable diagnostics for lazy module loading @@ -367,7 +368,7 @@ func readModGraph(loaderstate *State, ctx context.Context, pruning modPruning, r // m's go.mod file indicates that it supports graph pruning. loadOne := func(m module.Version) (*modFileSummary, error) { return mg.loadCache.Do(m, func() (*modFileSummary, error) { - summary, err := goModSummary(loaderstate, m) + summary, err := goModSummary(f, loaderstate, m) mu.Lock() if err == nil { @@ -572,7 +573,7 @@ func LoadModGraph(loaderstate *State, ctx context.Context, goVersion string) (*M rs = newRequirements(loaderstate, unpruned, rs.rootModules, rs.direct) } - return rs.Graph(loaderstate, ctx) + return rs.Graph(modfetch.Fetcher_, loaderstate, ctx) } rs, mg, err := expandGraph(loaderstate, ctx, rs) @@ -595,7 +596,7 @@ func LoadModGraph(loaderstate *State, ctx context.Context, goVersion string) (*M // expandGraph returns non-nil requirements and a non-nil graph regardless of // errors. On error, the roots might not be updated to be consistent. func expandGraph(loaderstate *State, ctx context.Context, rs *Requirements) (*Requirements, *ModuleGraph, error) { - mg, mgErr := rs.Graph(loaderstate, ctx) + mg, mgErr := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) if mgErr != nil { // Without the graph, we can't update the roots: we don't know which // versions of transitive dependencies would be selected. @@ -617,7 +618,7 @@ func expandGraph(loaderstate *State, ctx context.Context, rs *Requirements) (*Re return rs, mg, rsErr } rs = newRS - mg, mgErr = rs.Graph(loaderstate, ctx) + mg, mgErr = rs.Graph(modfetch.Fetcher_, loaderstate, ctx) } return rs, mg, mgErr @@ -859,7 +860,7 @@ func tidyPrunedRoots(loaderstate *State, ctx context.Context, mainModule module. for len(queue) > 0 { roots = tidy.rootModules - mg, err := tidy.Graph(loaderstate, ctx) + mg, err := tidy.Graph(modfetch.Fetcher_, loaderstate, ctx) if err != nil { return nil, err } @@ -897,7 +898,7 @@ func tidyPrunedRoots(loaderstate *State, ctx context.Context, mainModule module. } roots = tidy.rootModules - _, err := tidy.Graph(loaderstate, ctx) + _, err := tidy.Graph(modfetch.Fetcher_, loaderstate, ctx) if err != nil { return nil, err } @@ -939,7 +940,7 @@ func tidyPrunedRoots(loaderstate *State, ctx context.Context, mainModule module. if len(roots) > len(tidy.rootModules) { module.Sort(roots) tidy = newRequirements(loaderstate, pruned, roots, tidy.direct) - _, err = tidy.Graph(loaderstate, ctx) + _, err = tidy.Graph(modfetch.Fetcher_, loaderstate, ctx) if err != nil { return nil, err } @@ -1121,7 +1122,7 @@ func updatePrunedRoots(loaderstate *State, ctx context.Context, direct map[strin rs = newRequirements(loaderstate, pruned, roots, direct) var err error - mg, err = rs.Graph(loaderstate, ctx) + mg, err = rs.Graph(modfetch.Fetcher_, loaderstate, ctx) if err != nil { return rs, err } @@ -1135,7 +1136,7 @@ func updatePrunedRoots(loaderstate *State, ctx context.Context, direct map[strin // We've already loaded the full module graph, which includes the // requirements of all of the root modules — even the transitive // requirements, if they are unpruned! - mg, _ = rs.Graph(loaderstate, ctx) + mg, _ = rs.Graph(modfetch.Fetcher_, loaderstate, ctx) } else if cfg.BuildMod == "vendor" { // We can't spot-check the requirements of other modules because we // don't in general have their go.mod files available in the vendor @@ -1148,7 +1149,7 @@ func updatePrunedRoots(loaderstate *State, ctx context.Context, direct map[strin // inconsistent in some way; we need to load the full module graph // so that we can fix the roots properly. var err error - mg, err = rs.Graph(loaderstate, ctx) + mg, err = rs.Graph(modfetch.Fetcher_, loaderstate, ctx) if err != nil { return rs, err } @@ -1235,7 +1236,7 @@ func spotCheckRoots(loaderstate *State, ctx context.Context, rs *Requirements, m return } - summary, err := goModSummary(loaderstate, m) + summary, err := goModSummary(modfetch.Fetcher_, loaderstate, m) if err != nil { cancel() return @@ -1368,7 +1369,7 @@ func tidyUnprunedRoots(loaderstate *State, ctx context.Context, mainModule modul // 4. Every version in add is selected at its given version unless upgraded by // (the dependencies of) an existing root or another module in add. func updateUnprunedRoots(loaderstate *State, ctx context.Context, direct map[string]bool, rs *Requirements, add []module.Version) (*Requirements, error) { - mg, err := rs.Graph(loaderstate, ctx) + mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) if err != nil { // We can't ignore errors in the module graph even if the user passed the -e // flag to try to push past them. If we can't load the complete module @@ -1487,7 +1488,7 @@ func convertPruning(loaderstate *State, ctx context.Context, rs *Requirements, p // root set! “Include the transitive dependencies of every module in the build // list” is exactly what happens in a pruned module if we promote every module // in the build list to a root. - mg, err := rs.Graph(loaderstate, ctx) + mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) if err != nil { return rs, err } diff --git a/src/cmd/go/internal/modload/edit.go b/src/cmd/go/internal/modload/edit.go index 1996b7c26b0..cdf641c9e4e 100644 --- a/src/cmd/go/internal/modload/edit.go +++ b/src/cmd/go/internal/modload/edit.go @@ -7,6 +7,7 @@ package modload import ( "cmd/go/internal/cfg" "cmd/go/internal/gover" + "cmd/go/internal/modfetch" "cmd/go/internal/mvs" "cmd/internal/par" "context" @@ -100,7 +101,7 @@ func editRequirements(loaderstate *State, ctx context.Context, rs *Requirements, // dependencies, so we need to treat everything in the build list as // potentially relevant — that is, as what would be a “root” in a module // with graph pruning enabled. - mg, err := rs.Graph(loaderstate, ctx) + mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) if err != nil { // If we couldn't load the graph, we don't know what its requirements were // to begin with, so we can't edit those requirements in a coherent way. @@ -391,7 +392,7 @@ func editRequirements(loaderstate *State, ctx context.Context, rs *Requirements, // the edit. We want to make sure we consider keeping it as-is, // even if it wouldn't normally be included. (For example, it might // be a pseudo-version or pre-release.) - origMG, _ := orig.Graph(loaderstate, ctx) + origMG, _ := orig.Graph(modfetch.Fetcher_, loaderstate, ctx) origV := origMG.Selected(m.Path) if conflict.Err != nil && origV == m.Version { @@ -609,7 +610,7 @@ func editRequirements(loaderstate *State, ctx context.Context, rs *Requirements, // some root to that version. func extendGraph(loaderstate *State, ctx context.Context, rootPruning modPruning, roots []module.Version, selectedRoot map[string]string) (mg *ModuleGraph, upgradedRoot map[module.Version]bool, err error) { for { - mg, err = readModGraph(loaderstate, ctx, rootPruning, roots, upgradedRoot) + mg, err = readModGraph(modfetch.Fetcher_, loaderstate, ctx, rootPruning, roots, upgradedRoot) // We keep on going even if err is non-nil until we reach a steady state. // (Note that readModGraph returns a non-nil *ModuleGraph even in case of // errors.) The caller may be able to fix the errors by adjusting versions, diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go index 0ec4102cc61..b7147f948d4 100644 --- a/src/cmd/go/internal/modload/import.go +++ b/src/cmd/go/internal/modload/import.go @@ -481,7 +481,7 @@ func importFromModules(loaderstate *State, ctx context.Context, path string, rs // of a package in "all", we didn't necessarily load that file // when we read the module graph, so do it now to be sure. if !skipModFile && cfg.BuildMod != "vendor" && mods[0].Path != "" && !loaderstate.MainModules.Contains(mods[0].Path) { - if _, err := goModSummary(loaderstate, mods[0]); err != nil { + if _, err := goModSummary(modfetch.Fetcher_, loaderstate, mods[0]); err != nil { return module.Version{}, "", "", nil, err } } @@ -506,7 +506,7 @@ func importFromModules(loaderstate *State, ctx context.Context, path string, rs // So far we've checked the root dependencies. // Load the full module graph and try again. - mg, err = rs.Graph(loaderstate, ctx) + mg, err = rs.Graph(modfetch.Fetcher_, loaderstate, ctx) if err != nil { // We might be missing one or more transitive (implicit) dependencies from // the module graph, so we can't return an ImportMissingError here — one @@ -543,7 +543,7 @@ func queryImport(loaderstate *State, ctx context.Context, path string, rs *Requi mv = module.ZeroPseudoVersion("v0") } } - mg, err := rs.Graph(loaderstate, ctx) + mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) if err != nil { return module.Version{}, err } @@ -637,7 +637,7 @@ func queryImport(loaderstate *State, ctx context.Context, path string, rs *Requi // and return m, dir, ImportMissingError. fmt.Fprintf(os.Stderr, "go: finding module for package %s\n", path) - mg, err := rs.Graph(loaderstate, ctx) + mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) if err != nil { return module.Version{}, err } @@ -817,7 +817,7 @@ func fetch(loaderstate *State, ctx context.Context, mod module.Version) (dir str mod = r } - if mustHaveSums(loaderstate) && !modfetch.HaveSum(mod) { + if mustHaveSums(loaderstate) && !modfetch.HaveSum(modfetch.Fetcher_, mod) { return "", false, module.VersionError(mod, &sumMissingError{}) } diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go index 54d8009d326..f46b96afb6e 100644 --- a/src/cmd/go/internal/modload/init.go +++ b/src/cmd/go/internal/modload/init.go @@ -1965,7 +1965,7 @@ func commitRequirements(loaderstate *State, ctx context.Context, opts WriteOpts) if loaderstate.inWorkspaceMode() { // go.mod files aren't updated in workspace mode, but we still want to // update the go.work.sum file. - return modfetch.WriteGoSum(ctx, keepSums(loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)) + return modfetch.Fetcher_.WriteGoSum(ctx, keepSums(modfetch.Fetcher_, loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)) } _, updatedGoMod, modFile, err := UpdateGoModFromReqs(loaderstate, ctx, opts) if err != nil { @@ -1989,7 +1989,7 @@ func commitRequirements(loaderstate *State, ctx context.Context, opts WriteOpts) // Don't write go.mod, but write go.sum in case we added or trimmed sums. // 'go mod init' shouldn't write go.sum, since it will be incomplete. if cfg.CmdName != "mod init" { - if err := modfetch.WriteGoSum(ctx, keepSums(loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)); err != nil { + if err := modfetch.Fetcher_.WriteGoSum(ctx, keepSums(modfetch.Fetcher_, loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)); err != nil { return err } } @@ -2012,7 +2012,7 @@ func commitRequirements(loaderstate *State, ctx context.Context, opts WriteOpts) // 'go mod init' shouldn't write go.sum, since it will be incomplete. if cfg.CmdName != "mod init" { if err == nil { - err = modfetch.WriteGoSum(ctx, keepSums(loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)) + err = modfetch.Fetcher_.WriteGoSum(ctx, keepSums(modfetch.Fetcher_, loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)) } } }() @@ -2055,7 +2055,7 @@ func commitRequirements(loaderstate *State, ctx context.Context, opts WriteOpts) // including any go.mod files needed to reconstruct the MVS result // or identify go versions, // in addition to the checksums for every module in keepMods. -func keepSums(loaderstate *State, ctx context.Context, ld *loader, rs *Requirements, which whichSums) map[module.Version]bool { +func keepSums(fetcher_ *modfetch.Fetcher, loaderstate *State, ctx context.Context, ld *loader, rs *Requirements, which whichSums) map[module.Version]bool { // Every module in the full module graph contributes its requirements, // so in order to ensure that the build list itself is reproducible, // we need sums for every go.mod in the graph (regardless of whether @@ -2113,7 +2113,7 @@ func keepSums(loaderstate *State, ctx context.Context, ld *loader, rs *Requireme } } - mg, _ := rs.Graph(loaderstate, ctx) + mg, _ := rs.Graph(fetcher_, loaderstate, ctx) for prefix := pkg.path; prefix != "."; prefix = path.Dir(prefix) { if v := mg.Selected(prefix); v != "none" { m := module.Version{Path: prefix, Version: v} @@ -2136,7 +2136,7 @@ func keepSums(loaderstate *State, ctx context.Context, ld *loader, rs *Requireme } } } else { - mg, _ := rs.Graph(loaderstate, ctx) + mg, _ := rs.Graph(fetcher_, loaderstate, ctx) mg.WalkBreadthFirst(func(m module.Version) { if _, ok := mg.RequiredBy(m); ok { // The requirements from m's go.mod file are present in the module graph, diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go index b4d128fe9a1..2c09cbf5860 100644 --- a/src/cmd/go/internal/modload/load.go +++ b/src/cmd/go/internal/modload/load.go @@ -311,7 +311,7 @@ func LoadPackages(loaderstate *State, ctx context.Context, opts PackageOpts, pat case strings.Contains(m.Pattern(), "..."): m.Errs = m.Errs[:0] - mg, err := rs.Graph(loaderstate, ctx) + mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) if err != nil { // The module graph is (or may be) incomplete — perhaps we failed to // load the requirements of some module. This is an error in matching @@ -404,7 +404,7 @@ func LoadPackages(loaderstate *State, ctx context.Context, opts PackageOpts, pat if opts.Tidy { if cfg.BuildV { - mg, _ := ld.requirements.Graph(loaderstate, ctx) + mg, _ := ld.requirements.Graph(modfetch.Fetcher_, loaderstate, ctx) for _, m := range initialRS.rootModules { var unused bool if ld.requirements.pruning == unpruned { @@ -425,7 +425,7 @@ func LoadPackages(loaderstate *State, ctx context.Context, opts PackageOpts, pat } } - keep := keepSums(loaderstate, ctx, ld, ld.requirements, loadedZipSumsOnly) + keep := keepSums(modfetch.Fetcher_, loaderstate, ctx, ld, ld.requirements, loadedZipSumsOnly) compatVersion := ld.TidyCompatibleVersion goVersion := ld.requirements.GoVersion(loaderstate) if compatVersion == "" { @@ -447,7 +447,7 @@ func LoadPackages(loaderstate *State, ctx context.Context, opts PackageOpts, pat compatRS := newRequirements(loaderstate, compatPruning, ld.requirements.rootModules, ld.requirements.direct) ld.checkTidyCompatibility(loaderstate, ctx, compatRS, compatVersion) - for m := range keepSums(loaderstate, ctx, ld, compatRS, loadedZipSumsOnly) { + for m := range keepSums(modfetch.Fetcher_, loaderstate, ctx, ld, compatRS, loadedZipSumsOnly) { keep[m] = true } } @@ -466,7 +466,7 @@ func LoadPackages(loaderstate *State, ctx context.Context, opts PackageOpts, pat // Dropping compatibility for 1.16 may result in a strictly smaller go.sum. // Update the keep map with only the loaded.requirements. if gover.Compare(compatVersion, "1.16") > 0 { - keep = keepSums(loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums) + keep = keepSums(modfetch.Fetcher_, loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums) } currentGoSum, tidyGoSum := modfetch.TidyGoSum(keep) goSumDiff := diff.Diff("current/go.sum", currentGoSum, "tidy/go.sum", tidyGoSum) @@ -490,7 +490,7 @@ func LoadPackages(loaderstate *State, ctx context.Context, opts PackageOpts, pat // loaded.requirements, but here we may have also loaded (and want to // preserve checksums for) additional entities from compatRS, which are // only needed for compatibility with ld.TidyCompatibleVersion. - if err := modfetch.WriteGoSum(ctx, keep, mustHaveCompleteRequirements(loaderstate)); err != nil { + if err := modfetch.Fetcher_.WriteGoSum(ctx, keep, mustHaveCompleteRequirements(loaderstate)); err != nil { base.Fatal(err) } } @@ -747,7 +747,7 @@ func pathInModuleCache(loaderstate *State, ctx context.Context, dir string, rs * // versions of root modules may differ from what we already checked above. // Re-check those paths too. - mg, _ := rs.Graph(loaderstate, ctx) + mg, _ := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) var importPath string for _, m := range mg.BuildList() { var found bool @@ -1253,7 +1253,7 @@ func loadFromRoots(loaderstate *State, ctx context.Context, params loaderParams) // pruning and semantics all along, but there may have been — and may // still be — requirements on higher versions in the graph. tidy := overrideRoots(loaderstate, ctx, rs, []module.Version{{Path: "go", Version: ld.TidyGoVersion}}) - mg, err := tidy.Graph(loaderstate, ctx) + mg, err := tidy.Graph(modfetch.Fetcher_, loaderstate, ctx) if err != nil { ld.error(err) } @@ -1412,7 +1412,7 @@ func (ld *loader) updateRequirements(loaderstate *State, ctx context.Context) (c // of the vendor directory anyway. continue } - if mg, err := rs.Graph(loaderstate, ctx); err != nil { + if mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx); err != nil { return false, err } else if _, ok := mg.RequiredBy(dep.mod); !ok { // dep.mod is not an explicit dependency, but needs to be. @@ -1515,7 +1515,7 @@ func (ld *loader) updateRequirements(loaderstate *State, ctx context.Context) (c // The roots of the module graph have changed in some way (not just the // "direct" markings). Check whether the changes affected any of the loaded // packages. - mg, err := rs.Graph(loaderstate, ctx) + mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) if err != nil { return false, err } @@ -1841,7 +1841,7 @@ func (ld *loader) load(loaderstate *State, ctx context.Context, pkg *loadPkg) { var mg *ModuleGraph if ld.requirements.pruning == unpruned { var err error - mg, err = ld.requirements.Graph(loaderstate, ctx) + mg, err = ld.requirements.Graph(modfetch.Fetcher_, loaderstate, ctx) if err != nil { // We already checked the error from Graph in loadFromRoots and/or // updateRequirements, so we ignored the error on purpose and we should @@ -2095,7 +2095,7 @@ func (ld *loader) checkTidyCompatibility(loaderstate *State, ctx context.Context fmt.Fprintf(os.Stderr, "For information about 'go mod tidy' compatibility, see:\n\thttps://go.dev/ref/mod#graph-pruning\n") } - mg, err := rs.Graph(loaderstate, ctx) + mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) if err != nil { ld.error(fmt.Errorf("error loading go %s module graph: %w", compatVersion, err)) ld.switchIfErrors(ctx) diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go index 7191833a0dc..4817521cba4 100644 --- a/src/cmd/go/internal/modload/modfile.go +++ b/src/cmd/go/internal/modload/modfile.go @@ -214,7 +214,7 @@ func (s *State) CheckRetractions(ctx context.Context, m module.Version) (err err if err != nil { return err } - summary, err := rawGoModSummary(s, rm) + summary, err := rawGoModSummary(modfetch.Fetcher_, s, rm) if err != nil && !errors.Is(err, gover.ErrTooNew) { return err } @@ -322,7 +322,7 @@ func CheckDeprecation(loaderstate *State, ctx context.Context, m module.Version) if err != nil { return "", err } - summary, err := rawGoModSummary(loaderstate, latest) + summary, err := rawGoModSummary(modfetch.Fetcher_, loaderstate, latest) if err != nil && !errors.Is(err, gover.ErrTooNew) { return "", err } @@ -573,12 +573,12 @@ type retraction struct { // module versions. // // The caller must not modify the returned summary. -func goModSummary(loaderstate *State, m module.Version) (*modFileSummary, error) { +func goModSummary(fetcher_ *modfetch.Fetcher, loaderstate *State, m module.Version) (*modFileSummary, error) { if m.Version == "" && !loaderstate.inWorkspaceMode() && loaderstate.MainModules.Contains(m.Path) { panic("internal error: goModSummary called on a main module") } if gover.IsToolchain(m.Path) { - return rawGoModSummary(loaderstate, m) + return rawGoModSummary(fetcher_, loaderstate, m) } if cfg.BuildMod == "vendor" { @@ -604,12 +604,12 @@ func goModSummary(loaderstate *State, m module.Version) (*modFileSummary, error) actual := resolveReplacement(loaderstate, m) if mustHaveSums(loaderstate) && actual.Version != "" { key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"} - if !modfetch.HaveSum(key) { + if !modfetch.HaveSum(fetcher_, key) { suggestion := fmt.Sprintf(" for go.mod file; to add it:\n\tgo mod download %s", m.Path) return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion}) } } - summary, err := rawGoModSummary(loaderstate, actual) + summary, err := rawGoModSummary(fetcher_, loaderstate, actual) if err != nil { return nil, err } @@ -676,7 +676,7 @@ func goModSummary(loaderstate *State, m module.Version) (*modFileSummary, error) // rawGoModSummary cannot be used on the main module outside of workspace mode. // The modFileSummary can still be used for retractions and deprecations // even if a TooNewError is returned. -func rawGoModSummary(loaderstate *State, m module.Version) (*modFileSummary, error) { +func rawGoModSummary(fetcher_ *modfetch.Fetcher, loaderstate *State, m module.Version) (*modFileSummary, error) { if gover.IsToolchain(m.Path) { if m.Path == "go" && gover.Compare(m.Version, gover.GoStrictVersion) >= 0 { // Declare that go 1.21.3 requires toolchain 1.21.3, @@ -709,7 +709,7 @@ func rawGoModSummary(loaderstate *State, m module.Version) (*modFileSummary, err } } return rawGoModSummaryCache.Do(m, func() (*modFileSummary, error) { - name, data, err := rawGoModData(loaderstate, m) + name, data, err := rawGoModData(fetcher_, loaderstate, m) if err != nil { return nil, err } @@ -781,7 +781,7 @@ var rawGoModSummaryCache par.ErrCache[module.Version, *modFileSummary] // // Unlike rawGoModSummary, rawGoModData does not cache its results in memory. // Use rawGoModSummary instead unless you specifically need these bytes. -func rawGoModData(loaderstate *State, m module.Version) (name string, data []byte, err error) { +func rawGoModData(fetcher_ *modfetch.Fetcher, loaderstate *State, m module.Version) (name string, data []byte, err error) { if m.Version == "" { dir := m.Path if !filepath.IsAbs(dir) { @@ -810,7 +810,7 @@ func rawGoModData(loaderstate *State, m module.Version) (name string, data []byt base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version) } name = "go.mod" - data, err = modfetch.GoMod(context.TODO(), m.Path, m.Version) + data, err = fetcher_.GoMod(context.TODO(), m.Path, m.Version) } return name, data, err } diff --git a/src/cmd/go/internal/modload/mvs.go b/src/cmd/go/internal/modload/mvs.go index 63fedae0f16..18cd4345b23 100644 --- a/src/cmd/go/internal/modload/mvs.go +++ b/src/cmd/go/internal/modload/mvs.go @@ -54,7 +54,7 @@ func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) { return nil, nil } - summary, err := goModSummary(r.loaderstate, mod) + summary, err := goModSummary(modfetch.Fetcher_, r.loaderstate, mod) if err != nil { return nil, err } diff --git a/src/cmd/go/internal/modload/query.go b/src/cmd/go/internal/modload/query.go index f710ce2c624..158b672c6f7 100644 --- a/src/cmd/go/internal/modload/query.go +++ b/src/cmd/go/internal/modload/query.go @@ -1099,7 +1099,7 @@ func (e *PackageNotInModuleError) ImportPath() string { // we don't need to verify it in go.sum. This makes 'go list -m -u' faster // and simpler. func versionHasGoMod(loaderstate *State, _ context.Context, m module.Version) (bool, error) { - _, data, err := rawGoModData(loaderstate, m) + _, data, err := rawGoModData(modfetch.Fetcher_, loaderstate, m) if err != nil { return false, err } @@ -1124,7 +1124,7 @@ func lookupRepo(loaderstate *State, ctx context.Context, proxy, path string) (re err = module.CheckPath(path) } if err == nil { - repo = modfetch.Lookup(ctx, proxy, path) + repo = modfetch.Fetcher_.Lookup(ctx, proxy, path) } else { repo = emptyRepo{path: path, err: err} } diff --git a/src/cmd/go/internal/modload/search.go b/src/cmd/go/internal/modload/search.go index c45808635db..9a3b7e9dd9d 100644 --- a/src/cmd/go/internal/modload/search.go +++ b/src/cmd/go/internal/modload/search.go @@ -21,6 +21,7 @@ import ( "cmd/go/internal/fsys" "cmd/go/internal/gover" "cmd/go/internal/imports" + "cmd/go/internal/modfetch" "cmd/go/internal/modindex" "cmd/go/internal/search" "cmd/go/internal/str" @@ -348,7 +349,7 @@ func parseIgnorePatterns(loaderstate *State, ctx context.Context, treeCanMatch f if err != nil { continue } - summary, err := goModSummary(loaderstate, mod) + summary, err := goModSummary(modfetch.Fetcher_, loaderstate, mod) if err != nil { continue } diff --git a/src/cmd/go/internal/toolchain/switch.go b/src/cmd/go/internal/toolchain/switch.go index 76b608fdef4..4ddc28a0e90 100644 --- a/src/cmd/go/internal/toolchain/switch.go +++ b/src/cmd/go/internal/toolchain/switch.go @@ -146,7 +146,7 @@ func NewerToolchain(ctx context.Context, version string) (string, error) { func autoToolchains(ctx context.Context) ([]string, error) { var versions *modfetch.Versions err := modfetch.TryProxies(func(proxy string) error { - v, err := modfetch.Lookup(ctx, proxy, "go").Versions(ctx, "") + v, err := modfetch.Fetcher_.Lookup(ctx, proxy, "go").Versions(ctx, "") if err != nil { return err } From 07b10e97d6552e16534aae51f140771a601ef385 Mon Sep 17 00:00:00 2001 From: Ian Alexander Date: Mon, 24 Nov 2025 16:53:11 -0500 Subject: [PATCH 045/140] cmd/go/internal/modcmd: inject modfetch.Fetcher_ into DownloadModule This commit continues the injection of the global Fetcher_ variable into the various function calls that make use of it. The purpose is to prepare for the eventual removal of the global Fetcher_ variable and eliminate global state within the modfetch package. [git-generate] cd src/cmd/go/internal/modcmd rf ' inject modfetch.Fetcher_ DownloadModule ' cd ../modfetch rf ' add downloadZip:/f, err := tempFile.*/+0 _ = f \ file, err := tempFile(ctx, filepath.Dir(zipfile), filepath.Base(zipfile), 0o666) \ _ = file ' rf ' add downloadZip:/f.Close\(\)/+0 file.Close() rm downloadZip:/file.Close\(\)/-1 add downloadZip:/os.Remove\(f.Name\(\)\)/+0 os.Remove(file.Name()) rm downloadZip:/os.Remove\(file.Name\(\)\)/-1 ' sed -i ' s/ f, err := tempFile(ctx, filepath.Dir(zipfile), filepath.Base(zipfile), 0o666)/file, err := tempFile(ctx, filepath.Dir(zipfile), filepath.Base(zipfile), 0o666)/ s/err := repo.Zip(ctx, f, mod.Version)/err := repo.Zip(ctx, file, mod.Version)/ s/if _, err := f.Seek(0, io.SeekStart); err != nil {/if _, err := file.Seek(0, io.SeekStart); err != nil {/ s/if err := f.Truncate(0); err != nil {/if err := file.Truncate(0); err != nil {/ s/fi, err := f.Stat()/fi, err := file.Stat()/ s/z, err := zip.NewReader(f, fi.Size())/z, err := zip.NewReader(file, fi.Size())/ s/for _, f := range z.File {/for _, zf := range z.File {/ s/if !strings.HasPrefix(f.Name, prefix) {/if !strings.HasPrefix(zf.Name, prefix) {/ s/return fmt.Errorf("zip for %s has unexpected file %s", prefix\[:len(prefix)-1\], f.Name)/return fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], zf.Name)/ s/if err := f.Close(); err != nil {/if err := file.Close(); err != nil {/ s/if err := hashZip(fetcher_, mod, f.Name(), ziphashfile); err != nil {/if err := hashZip(fetcher_, mod, file.Name(), ziphashfile); err != nil {/ s/if err := os.Rename(f.Name(), zipfile); err != nil {/if err := os.Rename(file.Name(), zipfile); err != nil {/ ' fetch.go rf ' rm downloadZip:/_ = file/-3 downloadZip:/_ = file/-2 downloadZip:/file, err := tempFile\(ctx, filepath.Dir\(zipfile\), filepath.Base\(zipfile\), 0o666\)/+1 ' rf ' mv InfoFile.fetcher_ InfoFile.f mv InfoFile Fetcher.InfoFile mv GoModFile.fetcher_ GoModFile.f mv GoModFile Fetcher.GoModFile mv GoModSum.fetcher_ GoModSum.f mv GoModSum Fetcher.GoModSum mv Download.fetcher_ Download.f mv Download Fetcher.Download mv download.fetcher_ download.f mv download Fetcher.download mv DownloadZip.fetcher_ DownloadZip.f mv DownloadZip Fetcher.DownloadZip mv downloadZip.fetcher_ downloadZip.f mv downloadZip Fetcher.downloadZip mv checkMod.fetcher_ checkMod.f mv checkMod Fetcher.checkMod mv hashZip.fetcher_ hashZip.f ' Change-Id: I1d2e09b8523f5ef2be04b91d858d98fb79c0a771 Reviewed-on: https://go-review.googlesource.com/c/go/+/724242 LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Matloob Reviewed-by: Michael Matloob --- src/cmd/go/internal/modcmd/download.go | 10 +-- src/cmd/go/internal/modfetch/cache.go | 12 ++-- src/cmd/go/internal/modfetch/fetch.go | 64 +++++++++---------- .../modfetch/zip_sum_test/zip_sum_test.go | 2 +- src/cmd/go/internal/modget/get.go | 2 +- src/cmd/go/internal/modload/import.go | 2 +- src/cmd/go/internal/toolchain/select.go | 2 +- 7 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/cmd/go/internal/modcmd/download.go b/src/cmd/go/internal/modcmd/download.go index 150d0c88607..8b94ba1fd57 100644 --- a/src/cmd/go/internal/modcmd/download.go +++ b/src/cmd/go/internal/modcmd/download.go @@ -366,25 +366,25 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) { // leaving the results (including any error) in m itself. func DownloadModule(ctx context.Context, m *ModuleJSON) error { var err error - _, file, err := modfetch.InfoFile(ctx, m.Path, m.Version) + _, file, err := modfetch.Fetcher_.InfoFile(ctx, m.Path, m.Version) if err != nil { return err } m.Info = file - m.GoMod, err = modfetch.GoModFile(ctx, m.Path, m.Version) + m.GoMod, err = modfetch.Fetcher_.GoModFile(ctx, m.Path, m.Version) if err != nil { return err } - m.GoModSum, err = modfetch.GoModSum(ctx, m.Path, m.Version) + m.GoModSum, err = modfetch.Fetcher_.GoModSum(ctx, m.Path, m.Version) if err != nil { return err } mod := module.Version{Path: m.Path, Version: m.Version} - m.Zip, err = modfetch.DownloadZip(ctx, mod) + m.Zip, err = modfetch.Fetcher_.DownloadZip(ctx, mod) if err != nil { return err } m.Sum = modfetch.Sum(ctx, mod) - m.Dir, err = modfetch.Download(ctx, mod) + m.Dir, err = modfetch.Fetcher_.Download(ctx, mod) return err } diff --git a/src/cmd/go/internal/modfetch/cache.go b/src/cmd/go/internal/modfetch/cache.go index b0bb7a878a4..3886d3b1feb 100644 --- a/src/cmd/go/internal/modfetch/cache.go +++ b/src/cmd/go/internal/modfetch/cache.go @@ -341,7 +341,7 @@ func (r *cachingRepo) Zip(ctx context.Context, dst io.Writer, version string) er // InfoFile is like Lookup(ctx, path).Stat(version) but also returns the name of the file // containing the cached information. -func InfoFile(ctx context.Context, path, version string) (*RevInfo, string, error) { +func (f *Fetcher) InfoFile(ctx context.Context, path, version string) (*RevInfo, string, error) { if !gover.ModIsValid(path, version) { return nil, "", fmt.Errorf("invalid version %q", version) } @@ -353,7 +353,7 @@ func InfoFile(ctx context.Context, path, version string) (*RevInfo, string, erro var info *RevInfo var err2info map[error]*RevInfo err := TryProxies(func(proxy string) error { - i, err := Fetcher_.Lookup(ctx, proxy, path).Stat(ctx, version) + i, err := f.Lookup(ctx, proxy, path).Stat(ctx, version) if err == nil { info = i } else { @@ -416,11 +416,11 @@ func (f *Fetcher) GoMod(ctx context.Context, path, rev string) ([]byte, error) { // GoModFile is like GoMod but returns the name of the file containing // the cached information. -func GoModFile(ctx context.Context, path, version string) (string, error) { +func (f *Fetcher) GoModFile(ctx context.Context, path, version string) (string, error) { if !gover.ModIsValid(path, version) { return "", fmt.Errorf("invalid version %q", version) } - if _, err := Fetcher_.GoMod(ctx, path, version); err != nil { + if _, err := f.GoMod(ctx, path, version); err != nil { return "", err } // GoMod should have populated the disk cache for us. @@ -433,11 +433,11 @@ func GoModFile(ctx context.Context, path, version string) (string, error) { // GoModSum returns the go.sum entry for the module version's go.mod file. // (That is, it returns the entry listed in go.sum as "path version/go.mod".) -func GoModSum(ctx context.Context, path, version string) (string, error) { +func (f *Fetcher) GoModSum(ctx context.Context, path, version string) (string, error) { if !gover.ModIsValid(path, version) { return "", fmt.Errorf("invalid version %q", version) } - data, err := Fetcher_.GoMod(ctx, path, version) + data, err := f.GoMod(ctx, path, version) if err != nil { return "", err } diff --git a/src/cmd/go/internal/modfetch/fetch.go b/src/cmd/go/internal/modfetch/fetch.go index 6cddc5b7fbd..87f69156256 100644 --- a/src/cmd/go/internal/modfetch/fetch.go +++ b/src/cmd/go/internal/modfetch/fetch.go @@ -40,7 +40,7 @@ var ErrToolchain = errors.New("internal error: invalid operation on toolchain mo // Download downloads the specific module version to the // local download cache and returns the name of the directory // corresponding to the root of the module's file tree. -func Download(ctx context.Context, mod module.Version) (dir string, err error) { +func (f *Fetcher) Download(ctx context.Context, mod module.Version) (dir string, err error) { if gover.IsToolchain(mod.Path) { return "", ErrToolchain } @@ -49,12 +49,12 @@ func Download(ctx context.Context, mod module.Version) (dir string, err error) { } // The par.Cache here avoids duplicate work. - return Fetcher_.downloadCache.Do(mod, func() (string, error) { - dir, err := download(ctx, mod) + return f.downloadCache.Do(mod, func() (string, error) { + dir, err := f.download(ctx, mod) if err != nil { return "", err } - checkMod(ctx, mod) + f.checkMod(ctx, mod) // If go.mod exists (not an old legacy module), check version is not too new. if data, err := os.ReadFile(filepath.Join(dir, "go.mod")); err == nil { @@ -94,7 +94,7 @@ func (f *Fetcher) Unzip(ctx context.Context, mod module.Version, zipfile string) }) } -func download(ctx context.Context, mod module.Version) (dir string, err error) { +func (f *Fetcher) download(ctx context.Context, mod module.Version) (dir string, err error) { ctx, span := trace.StartSpan(ctx, "modfetch.download "+mod.String()) defer span.Done() @@ -109,7 +109,7 @@ func download(ctx context.Context, mod module.Version) (dir string, err error) { // To avoid cluttering the cache with extraneous files, // DownloadZip uses the same lockfile as Download. // Invoke DownloadZip before locking the file. - zipfile, err := DownloadZip(ctx, mod) + zipfile, err := f.DownloadZip(ctx, mod) if err != nil { return "", err } @@ -198,7 +198,7 @@ var downloadZipCache par.ErrCache[module.Version, string] // DownloadZip downloads the specific module version to the // local zip cache and returns the name of the zip file. -func DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err error) { +func (f *Fetcher) DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err error) { // The par.Cache here avoids duplicate work. return downloadZipCache.Do(mod, func() (string, error) { zipfile, err := CachePath(ctx, mod, "zip") @@ -210,8 +210,8 @@ func DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err e // Return early if the zip and ziphash files exist. if _, err := os.Stat(zipfile); err == nil { if _, err := os.Stat(ziphashfile); err == nil { - if !HaveSum(Fetcher_, mod) { - checkMod(ctx, mod) + if !HaveSum(f, mod) { + f.checkMod(ctx, mod) } return zipfile, nil } @@ -238,14 +238,14 @@ func DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err e } defer unlock() - if err := downloadZip(ctx, mod, zipfile); err != nil { + if err := f.downloadZip(ctx, mod, zipfile); err != nil { return "", err } return zipfile, nil }) } -func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err error) { +func (f *Fetcher) downloadZip(ctx context.Context, mod module.Version, zipfile string) (err error) { ctx, span := trace.StartSpan(ctx, "modfetch.downloadZip "+zipfile) defer span.Done() @@ -281,7 +281,7 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e // If the zip file exists, the ziphash file must have been deleted // or lost after a file system crash. Re-hash the zip without downloading. if zipExists { - return hashZip(mod, zipfile, ziphashfile) + return hashZip(f, mod, zipfile, ziphashfile) } // From here to the os.Rename call below is functionally almost equivalent to @@ -289,14 +289,14 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e // contents of the file (by hashing it) before we commit it. Because the file // is zip-compressed, we need an actual file — or at least an io.ReaderAt — to // validate it: we can't just tee the stream as we write it. - f, err := tempFile(ctx, filepath.Dir(zipfile), filepath.Base(zipfile), 0o666) + file, err := tempFile(ctx, filepath.Dir(zipfile), filepath.Base(zipfile), 0o666) if err != nil { return err } defer func() { if err != nil { - f.Close() - os.Remove(f.Name()) + file.Close() + os.Remove(file.Name()) } }() @@ -305,18 +305,18 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e if unrecoverableErr != nil { return unrecoverableErr } - repo := Fetcher_.Lookup(ctx, proxy, mod.Path) - err := repo.Zip(ctx, f, mod.Version) + repo := f.Lookup(ctx, proxy, mod.Path) + err := repo.Zip(ctx, file, mod.Version) if err != nil { // Zip may have partially written to f before failing. // (Perhaps the server crashed while sending the file?) // Since we allow fallback on error in some cases, we need to fix up the // file to be empty again for the next attempt. - if _, err := f.Seek(0, io.SeekStart); err != nil { + if _, err := file.Seek(0, io.SeekStart); err != nil { unrecoverableErr = err return err } - if err := f.Truncate(0); err != nil { + if err := file.Truncate(0); err != nil { unrecoverableErr = err return err } @@ -330,30 +330,30 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e // Double-check that the paths within the zip file are well-formed. // // TODO(bcmills): There is a similar check within the Unzip function. Can we eliminate one? - fi, err := f.Stat() + fi, err := file.Stat() if err != nil { return err } - z, err := zip.NewReader(f, fi.Size()) + z, err := zip.NewReader(file, fi.Size()) if err != nil { return err } prefix := mod.Path + "@" + mod.Version + "/" - for _, f := range z.File { - if !strings.HasPrefix(f.Name, prefix) { - return fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], f.Name) + for _, zf := range z.File { + if !strings.HasPrefix(zf.Name, prefix) { + return fmt.Errorf("zip for %s has unexpected file %s", prefix[:len(prefix)-1], zf.Name) } } - if err := f.Close(); err != nil { + if err := file.Close(); err != nil { return err } // Hash the zip file and check the sum before renaming to the final location. - if err := hashZip(mod, f.Name(), ziphashfile); err != nil { + if err := hashZip(f, mod, file.Name(), ziphashfile); err != nil { return err } - if err := os.Rename(f.Name(), zipfile); err != nil { + if err := os.Rename(file.Name(), zipfile); err != nil { return err } @@ -367,12 +367,12 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e // // If the hash does not match go.sum (or the sumdb if enabled), hashZip returns // an error and does not write ziphashfile. -func hashZip(mod module.Version, zipfile, ziphashfile string) (err error) { +func hashZip(f *Fetcher, mod module.Version, zipfile, ziphashfile string) (err error) { hash, err := dirhash.HashZip(zipfile, dirhash.DefaultHash) if err != nil { return err } - if err := checkModSum(Fetcher_, mod, hash); err != nil { + if err := checkModSum(f, mod, hash); err != nil { return err } hf, err := lockedfile.Create(ziphashfile) @@ -702,7 +702,7 @@ func RecordedSum(mod module.Version) (sum string, ok bool) { } // checkMod checks the given module's checksum and Go version. -func checkMod(ctx context.Context, mod module.Version) { +func (f *Fetcher) checkMod(ctx context.Context, mod module.Version) { // Do the file I/O before acquiring the go.sum lock. ziphash, err := CachePath(ctx, mod, "ziphash") if err != nil { @@ -719,7 +719,7 @@ func checkMod(ctx context.Context, mod module.Version) { if err != nil { base.Fatalf("verifying %v", module.VersionError(mod, err)) } - err = hashZip(mod, zip, ziphash) + err = hashZip(f, mod, zip, ziphash) if err != nil { base.Fatalf("verifying %v", module.VersionError(mod, err)) } @@ -730,7 +730,7 @@ func checkMod(ctx context.Context, mod module.Version) { base.Fatalf("verifying %v", module.VersionError(mod, fmt.Errorf("unexpected ziphash: %q", h))) } - if err := checkModSum(Fetcher_, mod, h); err != nil { + if err := checkModSum(f, mod, h); err != nil { base.Fatalf("%s", err) } } diff --git a/src/cmd/go/internal/modfetch/zip_sum_test/zip_sum_test.go b/src/cmd/go/internal/modfetch/zip_sum_test/zip_sum_test.go index edae1d8f3cf..6d11dbc5bf6 100644 --- a/src/cmd/go/internal/modfetch/zip_sum_test/zip_sum_test.go +++ b/src/cmd/go/internal/modfetch/zip_sum_test/zip_sum_test.go @@ -119,7 +119,7 @@ func TestZipSums(t *testing.T) { t.Parallel() ctx := context.Background() - zipPath, err := modfetch.DownloadZip(ctx, test.m) + zipPath, err := modfetch.Fetcher_.DownloadZip(ctx, test.m) if err != nil { if *updateTestData { t.Logf("%s: could not download module: %s (will remove from testdata)", test.m, err) diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go index 839bee103f4..d2458eba9bc 100644 --- a/src/cmd/go/internal/modget/get.go +++ b/src/cmd/go/internal/modget/get.go @@ -1792,7 +1792,7 @@ func (r *resolver) checkPackageProblems(loaderstate *modload.State, ctx context. continue } r.work.Add(func() { - if _, err := modfetch.DownloadZip(ctx, mActual); err != nil { + if _, err := modfetch.Fetcher_.DownloadZip(ctx, mActual); err != nil { verb := "upgraded" if gover.ModCompare(m.Path, m.Version, old.Version) < 0 { verb = "downgraded" diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go index b7147f948d4..eb97d5ff78a 100644 --- a/src/cmd/go/internal/modload/import.go +++ b/src/cmd/go/internal/modload/import.go @@ -821,7 +821,7 @@ func fetch(loaderstate *State, ctx context.Context, mod module.Version) (dir str return "", false, module.VersionError(mod, &sumMissingError{}) } - dir, err = modfetch.Download(ctx, mod) + dir, err = modfetch.Fetcher_.Download(ctx, mod) return dir, false, err } diff --git a/src/cmd/go/internal/toolchain/select.go b/src/cmd/go/internal/toolchain/select.go index 4c7e7a5e576..c27bcb5bbdd 100644 --- a/src/cmd/go/internal/toolchain/select.go +++ b/src/cmd/go/internal/toolchain/select.go @@ -360,7 +360,7 @@ func Exec(s *modload.State, gotoolchain string) { Path: gotoolchainModule, Version: gotoolchainVersion + "-" + gotoolchain + "." + runtime.GOOS + "-" + runtime.GOARCH, } - dir, err := modfetch.Download(context.Background(), m) + dir, err := modfetch.Fetcher_.Download(context.Background(), m) if err != nil { if errors.Is(err, fs.ErrNotExist) { toolVers := gover.FromToolchain(gotoolchain) From da31fd41774d99954b51722cd8fa8b54af7723fd Mon Sep 17 00:00:00 2001 From: Ian Alexander Date: Mon, 24 Nov 2025 12:19:55 -0500 Subject: [PATCH 046/140] cmd/go/internal/modload: replace references to modfetch.Fetcher_ This commit replaces references of modfetch.Fetcher_ with modload.State.Fetcher() in the modload package. Note that the constructor `NewState` still intentionally references the global variable. This will be refactored in a later commit. Change-Id: Ia8cfb41a81b0e29043694bc0f0f33f5a2f4920c6 Reviewed-on: https://go-review.googlesource.com/c/go/+/724243 Reviewed-by: Michael Matloob Reviewed-by: Michael Matloob LUCI-TryBot-Result: Go LUCI --- src/cmd/go/internal/modload/build.go | 6 ++--- src/cmd/go/internal/modload/buildlist.go | 33 ++++++++++++------------ src/cmd/go/internal/modload/edit.go | 16 ++++++------ src/cmd/go/internal/modload/import.go | 12 ++++----- src/cmd/go/internal/modload/init.go | 24 ++++++++++------- src/cmd/go/internal/modload/load.go | 24 ++++++++--------- src/cmd/go/internal/modload/modfile.go | 20 +++++++------- src/cmd/go/internal/modload/mvs.go | 2 +- src/cmd/go/internal/modload/query.go | 6 +++-- src/cmd/go/internal/modload/search.go | 3 +-- 10 files changed, 75 insertions(+), 71 deletions(-) diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go index ab4245a563c..e89a34e0ce9 100644 --- a/src/cmd/go/internal/modload/build.go +++ b/src/cmd/go/internal/modload/build.go @@ -103,7 +103,7 @@ func ModuleInfo(loaderstate *State, ctx context.Context, path string) *modinfo.M v, ok = rs.rootSelected(loaderstate, path) } if !ok { - mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, err := rs.Graph(loaderstate, ctx) if err != nil { base.Fatal(err) } @@ -329,7 +329,7 @@ func moduleInfo(loaderstate *State, ctx context.Context, rs *Requirements, m mod checksumOk := func(suffix string) bool { return rs == nil || m.Version == "" || !mustHaveSums(loaderstate) || - modfetch.HaveSum(modfetch.Fetcher_, module.Version{Path: m.Path, Version: m.Version + suffix}) + modfetch.HaveSum(loaderstate.Fetcher(), module.Version{Path: m.Path, Version: m.Version + suffix}) } mod := module.Version{Path: m.Path, Version: m.Version} @@ -355,7 +355,7 @@ func moduleInfo(loaderstate *State, ctx context.Context, rs *Requirements, m mod if m.GoVersion == "" && checksumOk("/go.mod") { // Load the go.mod file to determine the Go version, since it hasn't // already been populated from rawGoVersion. - if summary, err := rawGoModSummary(modfetch.Fetcher_, loaderstate, mod); err == nil && summary.goVersion != "" { + if summary, err := rawGoModSummary(loaderstate, mod); err == nil && summary.goVersion != "" { m.GoVersion = summary.goVersion } } diff --git a/src/cmd/go/internal/modload/buildlist.go b/src/cmd/go/internal/modload/buildlist.go index e086c5b351e..37c2a6c759f 100644 --- a/src/cmd/go/internal/modload/buildlist.go +++ b/src/cmd/go/internal/modload/buildlist.go @@ -20,7 +20,6 @@ import ( "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/gover" - "cmd/go/internal/modfetch" "cmd/go/internal/mvs" "cmd/internal/par" @@ -270,9 +269,9 @@ func (rs *Requirements) hasRedundantRoot(loaderstate *State) bool { // // If the requirements of any relevant module fail to load, Graph also // returns a non-nil error of type *mvs.BuildListError. -func (rs *Requirements) Graph(fetcher_ *modfetch.Fetcher, loaderstate *State, ctx context.Context) (*ModuleGraph, error) { +func (rs *Requirements) Graph(loaderstate *State, ctx context.Context) (*ModuleGraph, error) { rs.graphOnce.Do(func() { - mg, mgErr := readModGraph(fetcher_, loaderstate, ctx, rs.pruning, rs.rootModules, nil) + mg, mgErr := readModGraph(loaderstate, ctx, rs.pruning, rs.rootModules, nil) rs.graph.Store(&cachedGraph{mg, mgErr}) }) cached := rs.graph.Load() @@ -308,7 +307,7 @@ var readModGraphDebugOnce sync.Once // // Unlike LoadModGraph, readModGraph does not attempt to diagnose or update // inconsistent roots. -func readModGraph(f *modfetch.Fetcher, loaderstate *State, ctx context.Context, pruning modPruning, roots []module.Version, unprune map[module.Version]bool) (*ModuleGraph, error) { +func readModGraph(loaderstate *State, ctx context.Context, pruning modPruning, roots []module.Version, unprune map[module.Version]bool) (*ModuleGraph, error) { mustHaveGoRoot(roots) if pruning == pruned { // Enable diagnostics for lazy module loading @@ -368,7 +367,7 @@ func readModGraph(f *modfetch.Fetcher, loaderstate *State, ctx context.Context, // m's go.mod file indicates that it supports graph pruning. loadOne := func(m module.Version) (*modFileSummary, error) { return mg.loadCache.Do(m, func() (*modFileSummary, error) { - summary, err := goModSummary(f, loaderstate, m) + summary, err := goModSummary(loaderstate, m) mu.Lock() if err == nil { @@ -573,7 +572,7 @@ func LoadModGraph(loaderstate *State, ctx context.Context, goVersion string) (*M rs = newRequirements(loaderstate, unpruned, rs.rootModules, rs.direct) } - return rs.Graph(modfetch.Fetcher_, loaderstate, ctx) + return rs.Graph(loaderstate, ctx) } rs, mg, err := expandGraph(loaderstate, ctx, rs) @@ -596,7 +595,7 @@ func LoadModGraph(loaderstate *State, ctx context.Context, goVersion string) (*M // expandGraph returns non-nil requirements and a non-nil graph regardless of // errors. On error, the roots might not be updated to be consistent. func expandGraph(loaderstate *State, ctx context.Context, rs *Requirements) (*Requirements, *ModuleGraph, error) { - mg, mgErr := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, mgErr := rs.Graph(loaderstate, ctx) if mgErr != nil { // Without the graph, we can't update the roots: we don't know which // versions of transitive dependencies would be selected. @@ -618,7 +617,7 @@ func expandGraph(loaderstate *State, ctx context.Context, rs *Requirements) (*Re return rs, mg, rsErr } rs = newRS - mg, mgErr = rs.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, mgErr = rs.Graph(loaderstate, ctx) } return rs, mg, mgErr @@ -860,7 +859,7 @@ func tidyPrunedRoots(loaderstate *State, ctx context.Context, mainModule module. for len(queue) > 0 { roots = tidy.rootModules - mg, err := tidy.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, err := tidy.Graph(loaderstate, ctx) if err != nil { return nil, err } @@ -898,7 +897,7 @@ func tidyPrunedRoots(loaderstate *State, ctx context.Context, mainModule module. } roots = tidy.rootModules - _, err := tidy.Graph(modfetch.Fetcher_, loaderstate, ctx) + _, err := tidy.Graph(loaderstate, ctx) if err != nil { return nil, err } @@ -940,7 +939,7 @@ func tidyPrunedRoots(loaderstate *State, ctx context.Context, mainModule module. if len(roots) > len(tidy.rootModules) { module.Sort(roots) tidy = newRequirements(loaderstate, pruned, roots, tidy.direct) - _, err = tidy.Graph(modfetch.Fetcher_, loaderstate, ctx) + _, err = tidy.Graph(loaderstate, ctx) if err != nil { return nil, err } @@ -1122,7 +1121,7 @@ func updatePrunedRoots(loaderstate *State, ctx context.Context, direct map[strin rs = newRequirements(loaderstate, pruned, roots, direct) var err error - mg, err = rs.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, err = rs.Graph(loaderstate, ctx) if err != nil { return rs, err } @@ -1136,7 +1135,7 @@ func updatePrunedRoots(loaderstate *State, ctx context.Context, direct map[strin // We've already loaded the full module graph, which includes the // requirements of all of the root modules — even the transitive // requirements, if they are unpruned! - mg, _ = rs.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, _ = rs.Graph(loaderstate, ctx) } else if cfg.BuildMod == "vendor" { // We can't spot-check the requirements of other modules because we // don't in general have their go.mod files available in the vendor @@ -1149,7 +1148,7 @@ func updatePrunedRoots(loaderstate *State, ctx context.Context, direct map[strin // inconsistent in some way; we need to load the full module graph // so that we can fix the roots properly. var err error - mg, err = rs.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, err = rs.Graph(loaderstate, ctx) if err != nil { return rs, err } @@ -1236,7 +1235,7 @@ func spotCheckRoots(loaderstate *State, ctx context.Context, rs *Requirements, m return } - summary, err := goModSummary(modfetch.Fetcher_, loaderstate, m) + summary, err := goModSummary(loaderstate, m) if err != nil { cancel() return @@ -1369,7 +1368,7 @@ func tidyUnprunedRoots(loaderstate *State, ctx context.Context, mainModule modul // 4. Every version in add is selected at its given version unless upgraded by // (the dependencies of) an existing root or another module in add. func updateUnprunedRoots(loaderstate *State, ctx context.Context, direct map[string]bool, rs *Requirements, add []module.Version) (*Requirements, error) { - mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, err := rs.Graph(loaderstate, ctx) if err != nil { // We can't ignore errors in the module graph even if the user passed the -e // flag to try to push past them. If we can't load the complete module @@ -1488,7 +1487,7 @@ func convertPruning(loaderstate *State, ctx context.Context, rs *Requirements, p // root set! “Include the transitive dependencies of every module in the build // list” is exactly what happens in a pruned module if we promote every module // in the build list to a root. - mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, err := rs.Graph(loaderstate, ctx) if err != nil { return rs, err } diff --git a/src/cmd/go/internal/modload/edit.go b/src/cmd/go/internal/modload/edit.go index cdf641c9e4e..8038a77b0b3 100644 --- a/src/cmd/go/internal/modload/edit.go +++ b/src/cmd/go/internal/modload/edit.go @@ -5,11 +5,6 @@ package modload import ( - "cmd/go/internal/cfg" - "cmd/go/internal/gover" - "cmd/go/internal/modfetch" - "cmd/go/internal/mvs" - "cmd/internal/par" "context" "errors" "fmt" @@ -17,6 +12,11 @@ import ( "os" "slices" + "cmd/go/internal/cfg" + "cmd/go/internal/gover" + "cmd/go/internal/mvs" + "cmd/internal/par" + "golang.org/x/mod/module" ) @@ -101,7 +101,7 @@ func editRequirements(loaderstate *State, ctx context.Context, rs *Requirements, // dependencies, so we need to treat everything in the build list as // potentially relevant — that is, as what would be a “root” in a module // with graph pruning enabled. - mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, err := rs.Graph(loaderstate, ctx) if err != nil { // If we couldn't load the graph, we don't know what its requirements were // to begin with, so we can't edit those requirements in a coherent way. @@ -392,7 +392,7 @@ func editRequirements(loaderstate *State, ctx context.Context, rs *Requirements, // the edit. We want to make sure we consider keeping it as-is, // even if it wouldn't normally be included. (For example, it might // be a pseudo-version or pre-release.) - origMG, _ := orig.Graph(modfetch.Fetcher_, loaderstate, ctx) + origMG, _ := orig.Graph(loaderstate, ctx) origV := origMG.Selected(m.Path) if conflict.Err != nil && origV == m.Version { @@ -610,7 +610,7 @@ func editRequirements(loaderstate *State, ctx context.Context, rs *Requirements, // some root to that version. func extendGraph(loaderstate *State, ctx context.Context, rootPruning modPruning, roots []module.Version, selectedRoot map[string]string) (mg *ModuleGraph, upgradedRoot map[module.Version]bool, err error) { for { - mg, err = readModGraph(modfetch.Fetcher_, loaderstate, ctx, rootPruning, roots, upgradedRoot) + mg, err = readModGraph(loaderstate, ctx, rootPruning, roots, upgradedRoot) // We keep on going even if err is non-nil until we reach a steady state. // (Note that readModGraph returns a non-nil *ModuleGraph even in case of // errors.) The caller may be able to fix the errors by adjusting versions, diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go index eb97d5ff78a..04e95b7a8d9 100644 --- a/src/cmd/go/internal/modload/import.go +++ b/src/cmd/go/internal/modload/import.go @@ -481,7 +481,7 @@ func importFromModules(loaderstate *State, ctx context.Context, path string, rs // of a package in "all", we didn't necessarily load that file // when we read the module graph, so do it now to be sure. if !skipModFile && cfg.BuildMod != "vendor" && mods[0].Path != "" && !loaderstate.MainModules.Contains(mods[0].Path) { - if _, err := goModSummary(modfetch.Fetcher_, loaderstate, mods[0]); err != nil { + if _, err := goModSummary(loaderstate, mods[0]); err != nil { return module.Version{}, "", "", nil, err } } @@ -506,7 +506,7 @@ func importFromModules(loaderstate *State, ctx context.Context, path string, rs // So far we've checked the root dependencies. // Load the full module graph and try again. - mg, err = rs.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, err = rs.Graph(loaderstate, ctx) if err != nil { // We might be missing one or more transitive (implicit) dependencies from // the module graph, so we can't return an ImportMissingError here — one @@ -543,7 +543,7 @@ func queryImport(loaderstate *State, ctx context.Context, path string, rs *Requi mv = module.ZeroPseudoVersion("v0") } } - mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, err := rs.Graph(loaderstate, ctx) if err != nil { return module.Version{}, err } @@ -637,7 +637,7 @@ func queryImport(loaderstate *State, ctx context.Context, path string, rs *Requi // and return m, dir, ImportMissingError. fmt.Fprintf(os.Stderr, "go: finding module for package %s\n", path) - mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, err := rs.Graph(loaderstate, ctx) if err != nil { return module.Version{}, err } @@ -817,11 +817,11 @@ func fetch(loaderstate *State, ctx context.Context, mod module.Version) (dir str mod = r } - if mustHaveSums(loaderstate) && !modfetch.HaveSum(modfetch.Fetcher_, mod) { + if mustHaveSums(loaderstate) && !modfetch.HaveSum(loaderstate.Fetcher(), mod) { return "", false, module.VersionError(mod, &sumMissingError{}) } - dir, err = modfetch.Fetcher_.Download(ctx, mod) + dir, err = loaderstate.Fetcher().Download(ctx, mod) return dir, false, err } diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go index f46b96afb6e..d78a41d3c61 100644 --- a/src/cmd/go/internal/modload/init.go +++ b/src/cmd/go/internal/modload/init.go @@ -457,10 +457,14 @@ type State struct { func NewState() *State { s := new(State) - s.fetcher = modfetch.NewFetcher() + s.fetcher = modfetch.Fetcher_ return s } +func (s *State) Fetcher() *modfetch.Fetcher { + return s.fetcher +} + // Init determines whether module mode is enabled, locates the root of the // current module (if any), sets environment variables for Git subprocesses, and // configures the cfg, codehost, load, modfetch, and search packages for use @@ -937,9 +941,9 @@ func loadModFile(loaderstate *State, ctx context.Context, opts *PackageOpts) (*R } for _, modRoot := range loaderstate.modRoots { sumFile := strings.TrimSuffix(modFilePath(modRoot), ".mod") + ".sum" - modfetch.Fetcher_.AddWorkspaceGoSumFile(sumFile) + loaderstate.Fetcher().AddWorkspaceGoSumFile(sumFile) } - modfetch.Fetcher_.SetGoSumFile(loaderstate.workFilePath + ".sum") + loaderstate.Fetcher().SetGoSumFile(loaderstate.workFilePath + ".sum") } else if len(loaderstate.modRoots) == 0 { // We're in module mode, but not inside a module. // @@ -959,7 +963,7 @@ func loadModFile(loaderstate *State, ctx context.Context, opts *PackageOpts) (*R // // See golang.org/issue/32027. } else { - modfetch.Fetcher_.SetGoSumFile(strings.TrimSuffix(modFilePath(loaderstate.modRoots[0]), ".mod") + ".sum") + loaderstate.Fetcher().SetGoSumFile(strings.TrimSuffix(modFilePath(loaderstate.modRoots[0]), ".mod") + ".sum") } if len(loaderstate.modRoots) == 0 { // TODO(#49228): Instead of creating a fake module with an empty modroot, @@ -1965,7 +1969,7 @@ func commitRequirements(loaderstate *State, ctx context.Context, opts WriteOpts) if loaderstate.inWorkspaceMode() { // go.mod files aren't updated in workspace mode, but we still want to // update the go.work.sum file. - return modfetch.Fetcher_.WriteGoSum(ctx, keepSums(modfetch.Fetcher_, loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)) + return loaderstate.Fetcher().WriteGoSum(ctx, keepSums(loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)) } _, updatedGoMod, modFile, err := UpdateGoModFromReqs(loaderstate, ctx, opts) if err != nil { @@ -1989,7 +1993,7 @@ func commitRequirements(loaderstate *State, ctx context.Context, opts WriteOpts) // Don't write go.mod, but write go.sum in case we added or trimmed sums. // 'go mod init' shouldn't write go.sum, since it will be incomplete. if cfg.CmdName != "mod init" { - if err := modfetch.Fetcher_.WriteGoSum(ctx, keepSums(modfetch.Fetcher_, loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)); err != nil { + if err := loaderstate.Fetcher().WriteGoSum(ctx, keepSums(loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)); err != nil { return err } } @@ -2012,7 +2016,7 @@ func commitRequirements(loaderstate *State, ctx context.Context, opts WriteOpts) // 'go mod init' shouldn't write go.sum, since it will be incomplete. if cfg.CmdName != "mod init" { if err == nil { - err = modfetch.Fetcher_.WriteGoSum(ctx, keepSums(modfetch.Fetcher_, loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)) + err = loaderstate.Fetcher().WriteGoSum(ctx, keepSums(loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums), mustHaveCompleteRequirements(loaderstate)) } } }() @@ -2055,7 +2059,7 @@ func commitRequirements(loaderstate *State, ctx context.Context, opts WriteOpts) // including any go.mod files needed to reconstruct the MVS result // or identify go versions, // in addition to the checksums for every module in keepMods. -func keepSums(fetcher_ *modfetch.Fetcher, loaderstate *State, ctx context.Context, ld *loader, rs *Requirements, which whichSums) map[module.Version]bool { +func keepSums(loaderstate *State, ctx context.Context, ld *loader, rs *Requirements, which whichSums) map[module.Version]bool { // Every module in the full module graph contributes its requirements, // so in order to ensure that the build list itself is reproducible, // we need sums for every go.mod in the graph (regardless of whether @@ -2113,7 +2117,7 @@ func keepSums(fetcher_ *modfetch.Fetcher, loaderstate *State, ctx context.Contex } } - mg, _ := rs.Graph(fetcher_, loaderstate, ctx) + mg, _ := rs.Graph(loaderstate, ctx) for prefix := pkg.path; prefix != "."; prefix = path.Dir(prefix) { if v := mg.Selected(prefix); v != "none" { m := module.Version{Path: prefix, Version: v} @@ -2136,7 +2140,7 @@ func keepSums(fetcher_ *modfetch.Fetcher, loaderstate *State, ctx context.Contex } } } else { - mg, _ := rs.Graph(fetcher_, loaderstate, ctx) + mg, _ := rs.Graph(loaderstate, ctx) mg.WalkBreadthFirst(func(m module.Version) { if _, ok := mg.RequiredBy(m); ok { // The requirements from m's go.mod file are present in the module graph, diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go index 2c09cbf5860..bbc81263d5a 100644 --- a/src/cmd/go/internal/modload/load.go +++ b/src/cmd/go/internal/modload/load.go @@ -311,7 +311,7 @@ func LoadPackages(loaderstate *State, ctx context.Context, opts PackageOpts, pat case strings.Contains(m.Pattern(), "..."): m.Errs = m.Errs[:0] - mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, err := rs.Graph(loaderstate, ctx) if err != nil { // The module graph is (or may be) incomplete — perhaps we failed to // load the requirements of some module. This is an error in matching @@ -404,7 +404,7 @@ func LoadPackages(loaderstate *State, ctx context.Context, opts PackageOpts, pat if opts.Tidy { if cfg.BuildV { - mg, _ := ld.requirements.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, _ := ld.requirements.Graph(loaderstate, ctx) for _, m := range initialRS.rootModules { var unused bool if ld.requirements.pruning == unpruned { @@ -425,7 +425,7 @@ func LoadPackages(loaderstate *State, ctx context.Context, opts PackageOpts, pat } } - keep := keepSums(modfetch.Fetcher_, loaderstate, ctx, ld, ld.requirements, loadedZipSumsOnly) + keep := keepSums(loaderstate, ctx, ld, ld.requirements, loadedZipSumsOnly) compatVersion := ld.TidyCompatibleVersion goVersion := ld.requirements.GoVersion(loaderstate) if compatVersion == "" { @@ -447,7 +447,7 @@ func LoadPackages(loaderstate *State, ctx context.Context, opts PackageOpts, pat compatRS := newRequirements(loaderstate, compatPruning, ld.requirements.rootModules, ld.requirements.direct) ld.checkTidyCompatibility(loaderstate, ctx, compatRS, compatVersion) - for m := range keepSums(modfetch.Fetcher_, loaderstate, ctx, ld, compatRS, loadedZipSumsOnly) { + for m := range keepSums(loaderstate, ctx, ld, compatRS, loadedZipSumsOnly) { keep[m] = true } } @@ -466,7 +466,7 @@ func LoadPackages(loaderstate *State, ctx context.Context, opts PackageOpts, pat // Dropping compatibility for 1.16 may result in a strictly smaller go.sum. // Update the keep map with only the loaded.requirements. if gover.Compare(compatVersion, "1.16") > 0 { - keep = keepSums(modfetch.Fetcher_, loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums) + keep = keepSums(loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums) } currentGoSum, tidyGoSum := modfetch.TidyGoSum(keep) goSumDiff := diff.Diff("current/go.sum", currentGoSum, "tidy/go.sum", tidyGoSum) @@ -490,7 +490,7 @@ func LoadPackages(loaderstate *State, ctx context.Context, opts PackageOpts, pat // loaded.requirements, but here we may have also loaded (and want to // preserve checksums for) additional entities from compatRS, which are // only needed for compatibility with ld.TidyCompatibleVersion. - if err := modfetch.Fetcher_.WriteGoSum(ctx, keep, mustHaveCompleteRequirements(loaderstate)); err != nil { + if err := loaderstate.Fetcher().WriteGoSum(ctx, keep, mustHaveCompleteRequirements(loaderstate)); err != nil { base.Fatal(err) } } @@ -747,7 +747,7 @@ func pathInModuleCache(loaderstate *State, ctx context.Context, dir string, rs * // versions of root modules may differ from what we already checked above. // Re-check those paths too. - mg, _ := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, _ := rs.Graph(loaderstate, ctx) var importPath string for _, m := range mg.BuildList() { var found bool @@ -1253,7 +1253,7 @@ func loadFromRoots(loaderstate *State, ctx context.Context, params loaderParams) // pruning and semantics all along, but there may have been — and may // still be — requirements on higher versions in the graph. tidy := overrideRoots(loaderstate, ctx, rs, []module.Version{{Path: "go", Version: ld.TidyGoVersion}}) - mg, err := tidy.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, err := tidy.Graph(loaderstate, ctx) if err != nil { ld.error(err) } @@ -1412,7 +1412,7 @@ func (ld *loader) updateRequirements(loaderstate *State, ctx context.Context) (c // of the vendor directory anyway. continue } - if mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx); err != nil { + if mg, err := rs.Graph(loaderstate, ctx); err != nil { return false, err } else if _, ok := mg.RequiredBy(dep.mod); !ok { // dep.mod is not an explicit dependency, but needs to be. @@ -1515,7 +1515,7 @@ func (ld *loader) updateRequirements(loaderstate *State, ctx context.Context) (c // The roots of the module graph have changed in some way (not just the // "direct" markings). Check whether the changes affected any of the loaded // packages. - mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, err := rs.Graph(loaderstate, ctx) if err != nil { return false, err } @@ -1841,7 +1841,7 @@ func (ld *loader) load(loaderstate *State, ctx context.Context, pkg *loadPkg) { var mg *ModuleGraph if ld.requirements.pruning == unpruned { var err error - mg, err = ld.requirements.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, err = ld.requirements.Graph(loaderstate, ctx) if err != nil { // We already checked the error from Graph in loadFromRoots and/or // updateRequirements, so we ignored the error on purpose and we should @@ -2095,7 +2095,7 @@ func (ld *loader) checkTidyCompatibility(loaderstate *State, ctx context.Context fmt.Fprintf(os.Stderr, "For information about 'go mod tidy' compatibility, see:\n\thttps://go.dev/ref/mod#graph-pruning\n") } - mg, err := rs.Graph(modfetch.Fetcher_, loaderstate, ctx) + mg, err := rs.Graph(loaderstate, ctx) if err != nil { ld.error(fmt.Errorf("error loading go %s module graph: %w", compatVersion, err)) ld.switchIfErrors(ctx) diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go index 4817521cba4..1e54a580345 100644 --- a/src/cmd/go/internal/modload/modfile.go +++ b/src/cmd/go/internal/modload/modfile.go @@ -214,7 +214,7 @@ func (s *State) CheckRetractions(ctx context.Context, m module.Version) (err err if err != nil { return err } - summary, err := rawGoModSummary(modfetch.Fetcher_, s, rm) + summary, err := rawGoModSummary(s, rm) if err != nil && !errors.Is(err, gover.ErrTooNew) { return err } @@ -322,7 +322,7 @@ func CheckDeprecation(loaderstate *State, ctx context.Context, m module.Version) if err != nil { return "", err } - summary, err := rawGoModSummary(modfetch.Fetcher_, loaderstate, latest) + summary, err := rawGoModSummary(loaderstate, latest) if err != nil && !errors.Is(err, gover.ErrTooNew) { return "", err } @@ -573,12 +573,12 @@ type retraction struct { // module versions. // // The caller must not modify the returned summary. -func goModSummary(fetcher_ *modfetch.Fetcher, loaderstate *State, m module.Version) (*modFileSummary, error) { +func goModSummary(loaderstate *State, m module.Version) (*modFileSummary, error) { if m.Version == "" && !loaderstate.inWorkspaceMode() && loaderstate.MainModules.Contains(m.Path) { panic("internal error: goModSummary called on a main module") } if gover.IsToolchain(m.Path) { - return rawGoModSummary(fetcher_, loaderstate, m) + return rawGoModSummary(loaderstate, m) } if cfg.BuildMod == "vendor" { @@ -604,12 +604,12 @@ func goModSummary(fetcher_ *modfetch.Fetcher, loaderstate *State, m module.Versi actual := resolveReplacement(loaderstate, m) if mustHaveSums(loaderstate) && actual.Version != "" { key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"} - if !modfetch.HaveSum(fetcher_, key) { + if !modfetch.HaveSum(loaderstate.Fetcher(), key) { suggestion := fmt.Sprintf(" for go.mod file; to add it:\n\tgo mod download %s", m.Path) return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion}) } } - summary, err := rawGoModSummary(fetcher_, loaderstate, actual) + summary, err := rawGoModSummary(loaderstate, actual) if err != nil { return nil, err } @@ -676,7 +676,7 @@ func goModSummary(fetcher_ *modfetch.Fetcher, loaderstate *State, m module.Versi // rawGoModSummary cannot be used on the main module outside of workspace mode. // The modFileSummary can still be used for retractions and deprecations // even if a TooNewError is returned. -func rawGoModSummary(fetcher_ *modfetch.Fetcher, loaderstate *State, m module.Version) (*modFileSummary, error) { +func rawGoModSummary(loaderstate *State, m module.Version) (*modFileSummary, error) { if gover.IsToolchain(m.Path) { if m.Path == "go" && gover.Compare(m.Version, gover.GoStrictVersion) >= 0 { // Declare that go 1.21.3 requires toolchain 1.21.3, @@ -709,7 +709,7 @@ func rawGoModSummary(fetcher_ *modfetch.Fetcher, loaderstate *State, m module.Ve } } return rawGoModSummaryCache.Do(m, func() (*modFileSummary, error) { - name, data, err := rawGoModData(fetcher_, loaderstate, m) + name, data, err := rawGoModData(loaderstate, m) if err != nil { return nil, err } @@ -781,7 +781,7 @@ var rawGoModSummaryCache par.ErrCache[module.Version, *modFileSummary] // // Unlike rawGoModSummary, rawGoModData does not cache its results in memory. // Use rawGoModSummary instead unless you specifically need these bytes. -func rawGoModData(fetcher_ *modfetch.Fetcher, loaderstate *State, m module.Version) (name string, data []byte, err error) { +func rawGoModData(loaderstate *State, m module.Version) (name string, data []byte, err error) { if m.Version == "" { dir := m.Path if !filepath.IsAbs(dir) { @@ -810,7 +810,7 @@ func rawGoModData(fetcher_ *modfetch.Fetcher, loaderstate *State, m module.Versi base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version) } name = "go.mod" - data, err = fetcher_.GoMod(context.TODO(), m.Path, m.Version) + data, err = loaderstate.Fetcher().GoMod(context.TODO(), m.Path, m.Version) } return name, data, err } diff --git a/src/cmd/go/internal/modload/mvs.go b/src/cmd/go/internal/modload/mvs.go index 18cd4345b23..63fedae0f16 100644 --- a/src/cmd/go/internal/modload/mvs.go +++ b/src/cmd/go/internal/modload/mvs.go @@ -54,7 +54,7 @@ func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) { return nil, nil } - summary, err := goModSummary(modfetch.Fetcher_, r.loaderstate, mod) + summary, err := goModSummary(r.loaderstate, mod) if err != nil { return nil, err } diff --git a/src/cmd/go/internal/modload/query.go b/src/cmd/go/internal/modload/query.go index 158b672c6f7..b6bf0803c10 100644 --- a/src/cmd/go/internal/modload/query.go +++ b/src/cmd/go/internal/modload/query.go @@ -1099,7 +1099,7 @@ func (e *PackageNotInModuleError) ImportPath() string { // we don't need to verify it in go.sum. This makes 'go list -m -u' faster // and simpler. func versionHasGoMod(loaderstate *State, _ context.Context, m module.Version) (bool, error) { - _, data, err := rawGoModData(modfetch.Fetcher_, loaderstate, m) + _, data, err := rawGoModData(loaderstate, m) if err != nil { return false, err } @@ -1124,7 +1124,7 @@ func lookupRepo(loaderstate *State, ctx context.Context, proxy, path string) (re err = module.CheckPath(path) } if err == nil { - repo = modfetch.Fetcher_.Lookup(ctx, proxy, path) + repo = loaderstate.Fetcher().Lookup(ctx, proxy, path) } else { repo = emptyRepo{path: path, err: err} } @@ -1150,9 +1150,11 @@ func (er emptyRepo) ModulePath() string { return er.path } func (er emptyRepo) CheckReuse(ctx context.Context, old *codehost.Origin) error { return fmt.Errorf("empty repo") } + func (er emptyRepo) Versions(ctx context.Context, prefix string) (*modfetch.Versions, error) { return &modfetch.Versions{}, nil } + func (er emptyRepo) Stat(ctx context.Context, rev string) (*modfetch.RevInfo, error) { return nil, er.err } diff --git a/src/cmd/go/internal/modload/search.go b/src/cmd/go/internal/modload/search.go index 9a3b7e9dd9d..c45808635db 100644 --- a/src/cmd/go/internal/modload/search.go +++ b/src/cmd/go/internal/modload/search.go @@ -21,7 +21,6 @@ import ( "cmd/go/internal/fsys" "cmd/go/internal/gover" "cmd/go/internal/imports" - "cmd/go/internal/modfetch" "cmd/go/internal/modindex" "cmd/go/internal/search" "cmd/go/internal/str" @@ -349,7 +348,7 @@ func parseIgnorePatterns(loaderstate *State, ctx context.Context, treeCanMatch f if err != nil { continue } - summary, err := goModSummary(modfetch.Fetcher_, loaderstate, mod) + summary, err := goModSummary(loaderstate, mod) if err != nil { continue } From 03f499ec4602500939a1ed3c540cbd5183c20ce9 Mon Sep 17 00:00:00 2001 From: Ian Alexander Date: Mon, 24 Nov 2025 18:30:50 -0500 Subject: [PATCH 047/140] cmd/go/internal/modfetch: remove references to Fetcher_ in test file This commit removes references to the global Fetcher_ variable from the zip sum test file. Change-Id: I622587f7809f4c6bcd4afbb35312912149b7a3ea Reviewed-on: https://go-review.googlesource.com/c/go/+/724244 Reviewed-by: Michael Matloob LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Matloob --- src/cmd/go/internal/modfetch/zip_sum_test/zip_sum_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cmd/go/internal/modfetch/zip_sum_test/zip_sum_test.go b/src/cmd/go/internal/modfetch/zip_sum_test/zip_sum_test.go index 6d11dbc5bf6..6b2312cbed0 100644 --- a/src/cmd/go/internal/modfetch/zip_sum_test/zip_sum_test.go +++ b/src/cmd/go/internal/modfetch/zip_sum_test/zip_sum_test.go @@ -112,6 +112,7 @@ func TestZipSums(t *testing.T) { // Download modules with a rate limit. We may run out of file descriptors // or cause timeouts without a limit. needUpdate := false + fetcher := modfetch.NewFetcher() for i := range tests { test := &tests[i] name := fmt.Sprintf("%s@%s", strings.ReplaceAll(test.m.Path, "/", "_"), test.m.Version) @@ -119,7 +120,7 @@ func TestZipSums(t *testing.T) { t.Parallel() ctx := context.Background() - zipPath, err := modfetch.Fetcher_.DownloadZip(ctx, test.m) + zipPath, err := fetcher.DownloadZip(ctx, test.m) if err != nil { if *updateTestData { t.Logf("%s: could not download module: %s (will remove from testdata)", test.m, err) From 06412288cfd31ab365fe8ee6368742dffa759803 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Tue, 19 Aug 2025 17:00:29 -0700 Subject: [PATCH 048/140] runtime: panic on AddCleanup with self pointer For #75066 Change-Id: Ifd555586fb448e7510fed16372648bdd7ec0ab4a Reviewed-on: https://go-review.googlesource.com/c/go/+/697535 Reviewed-by: Cherry Mui Auto-Submit: Ian Lance Taylor Reviewed-by: Michael Knyszek LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Pratt --- src/runtime/mcleanup.go | 21 +++++++++++++++++++-- src/runtime/mcleanup_test.go | 17 +++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/runtime/mcleanup.go b/src/runtime/mcleanup.go index fc71af9f3f8..d190653213f 100644 --- a/src/runtime/mcleanup.go +++ b/src/runtime/mcleanup.go @@ -84,7 +84,8 @@ func AddCleanup[T, S any](ptr *T, cleanup func(S), arg S) Cleanup { // Check that arg is not equal to ptr. argType := abi.TypeOf(arg) - if kind := argType.Kind(); kind == abi.Pointer || kind == abi.UnsafePointer { + kind := argType.Kind() + if kind == abi.Pointer || kind == abi.UnsafePointer { if unsafe.Pointer(ptr) == *((*unsafe.Pointer)(unsafe.Pointer(&arg))) { panic("runtime.AddCleanup: ptr is equal to arg, cleanup will never run") } @@ -119,7 +120,7 @@ func AddCleanup[T, S any](ptr *T, cleanup func(S), arg S) Cleanup { *argv = arg // Find the containing object. - base, _, _ := findObject(usptr, 0, 0) + base, span, _ := findObject(usptr, 0, 0) if base == 0 { if isGoPointerWithoutSpan(unsafe.Pointer(ptr)) { // Cleanup is a noop. @@ -128,6 +129,22 @@ func AddCleanup[T, S any](ptr *T, cleanup func(S), arg S) Cleanup { panic("runtime.AddCleanup: ptr not in allocated block") } + // Check that arg is not within ptr. + if kind == abi.Pointer || kind == abi.UnsafePointer { + argPtr := uintptr(*(*unsafe.Pointer)(unsafe.Pointer(&arg))) + if argPtr >= base && argPtr < base+span.elemsize { + // It's possible that both pointers are separate + // parts of a tiny allocation, which is OK. + // We side-stepped the tiny allocator above for + // the allocation for the cleanup, + // but the argument itself can still overlap + // with the value to which the cleanup is attached. + if span.spanclass != tinySpanClass { + panic("runtime.AddCleanup: ptr is within arg, cleanup will never run") + } + } + } + // Create another G if necessary. if gcCleanups.needG() { gcCleanups.createGs() diff --git a/src/runtime/mcleanup_test.go b/src/runtime/mcleanup_test.go index 341d30afa7d..28fc7f407fb 100644 --- a/src/runtime/mcleanup_test.go +++ b/src/runtime/mcleanup_test.go @@ -337,6 +337,23 @@ func TestCleanupLost(t *testing.T) { } } +func TestCleanupUnreachable(t *testing.T) { + defer func() { + if recover() == nil { + t.Error("AddCleanup failed to detect self-pointer") + } + }() + + type T struct { + p *byte // use *byte to avoid tiny allocator + f int + } + v := &T{} + runtime.AddCleanup(v, func(*int) { + t.Error("cleanup ran unexpectedly") + }, &v.f) +} + // BenchmarkAddCleanupAndStop benchmarks adding and removing a cleanup // from the same allocation. // From eb63ef9d6676dc0edb112cd297820306a327017a Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Fri, 22 Aug 2025 13:47:42 -0700 Subject: [PATCH 049/140] runtime: panic if cleanup function closes over cleanup pointer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This would catch problems like https://go.dev/cl/696295. Benchmark effect with this CL plus CL 697535: goos: linux goarch: amd64 pkg: runtime cpu: 12th Gen Intel(R) Core(TM) i7-1260P │ /tmp/foo.1 │ /tmp/foo.2 │ │ sec/op │ sec/op vs base │ AddCleanupAndStop-16 81.93n ± 1% 82.87n ± 1% +1.14% (p=0.041 n=10) For #75066 Change-Id: Ia41d0d6b88baebf6055cb7e5d42bc8210b31630f Reviewed-on: https://go-review.googlesource.com/c/go/+/714000 Reviewed-by: Michael Pratt Reviewed-by: Michael Knyszek Auto-Submit: Ian Lance Taylor LUCI-TryBot-Result: Go LUCI --- src/runtime/mcleanup.go | 24 +++++++++++++++++ src/runtime/mcleanup_test.go | 20 +++++++++++++- .../testdata/testprog/checkfinalizers.go | 27 +++++++++++++++++-- 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/src/runtime/mcleanup.go b/src/runtime/mcleanup.go index d190653213f..e6f2fb00b3e 100644 --- a/src/runtime/mcleanup.go +++ b/src/runtime/mcleanup.go @@ -71,7 +71,14 @@ import ( // mentions it. To ensure a cleanup does not get called prematurely, // pass the object to the [KeepAlive] function after the last point // where the object must remain reachable. +// +//go:nocheckptr func AddCleanup[T, S any](ptr *T, cleanup func(S), arg S) Cleanup { + // This is marked nocheckptr because checkptr doesn't understand the + // pointer manipulation done when looking at closure pointers. + // Similar code in mbitmap.go works because the functions are + // go:nosplit, which implies go:nocheckptr (CL 202158). + // Explicitly force ptr and cleanup to escape to the heap. ptr = abi.Escape(ptr) cleanup = abi.Escape(cleanup) @@ -145,6 +152,23 @@ func AddCleanup[T, S any](ptr *T, cleanup func(S), arg S) Cleanup { } } + // Check that the cleanup function doesn't close over the pointer. + cleanupFV := unsafe.Pointer(*(**funcval)(unsafe.Pointer(&cleanup))) + cBase, cSpan, _ := findObject(uintptr(cleanupFV), 0, 0) + if cBase != 0 { + tp := cSpan.typePointersOfUnchecked(cBase) + for { + var addr uintptr + if tp, addr = tp.next(cBase + cSpan.elemsize); addr == 0 { + break + } + ptr := *(*uintptr)(unsafe.Pointer(addr)) + if ptr >= base && ptr < base+span.elemsize { + panic("runtime.AddCleanup: cleanup function closes over ptr, cleanup will never run") + } + } + } + // Create another G if necessary. if gcCleanups.needG() { gcCleanups.createGs() diff --git a/src/runtime/mcleanup_test.go b/src/runtime/mcleanup_test.go index 28fc7f407fb..5afe85e1036 100644 --- a/src/runtime/mcleanup_test.go +++ b/src/runtime/mcleanup_test.go @@ -337,7 +337,7 @@ func TestCleanupLost(t *testing.T) { } } -func TestCleanupUnreachable(t *testing.T) { +func TestCleanupUnreachablePointer(t *testing.T) { defer func() { if recover() == nil { t.Error("AddCleanup failed to detect self-pointer") @@ -354,6 +354,24 @@ func TestCleanupUnreachable(t *testing.T) { }, &v.f) } +func TestCleanupUnreachableClosure(t *testing.T) { + defer func() { + if recover() == nil { + t.Error("AddCleanup failed to detect closure pointer") + } + }() + + type T struct { + p *byte // use *byte to avoid tiny allocator + f int + } + v := &T{} + runtime.AddCleanup(v, func(int) { + t.Log(v.f) + t.Error("cleanup ran unexpectedly") + }, 0) +} + // BenchmarkAddCleanupAndStop benchmarks adding and removing a cleanup // from the same allocation. // diff --git a/src/runtime/testdata/testprog/checkfinalizers.go b/src/runtime/testdata/testprog/checkfinalizers.go index ea352a4e3e9..9f082a25ff1 100644 --- a/src/runtime/testdata/testprog/checkfinalizers.go +++ b/src/runtime/testdata/testprog/checkfinalizers.go @@ -28,17 +28,40 @@ func DetectFinalizerAndCleanupLeaks() { // Leak a cleanup. cLeak := new(T) + + // Use an extra closure to avoid the simple + // checking done by AddCleanup. + var closeOverCLeak func(int) + closeOverCLeak = func(x int) { + // Use recursion to avoid inlining. + if x <= 0 { + **cLeak = x + } else { + closeOverCLeak(x - 1) + } + } + runtime.AddCleanup(cLeak, func(x int) { - **cLeak = x + closeOverCLeak(x) }, int(0)) // Have a regular cleanup to make sure it doesn't trip the detector. cNoLeak := new(T) runtime.AddCleanup(cNoLeak, func(_ int) {}, int(0)) + // Like closeOverCLeak. + var closeOverCNoLeak func(int) + closeOverCNoLeak = func(x int) { + if x <= 0 { + **cNoLeak = x + } else { + closeOverCNoLeak(x - 1) + } + } + // Add a cleanup that only temporarily leaks cNoLeak. runtime.AddCleanup(cNoLeak, func(x int) { - **cNoLeak = x + closeOverCNoLeak(x) }, int(0)).Stop() if !asan.Enabled && !race.Enabled { From 54b82e944ebccb612f4f300c66f9a0230b43b24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Wed, 2 Jul 2025 11:26:17 +0200 Subject: [PATCH 050/140] internal/trace: support event constructor for testing Implement the new APIs described in #74826. Closes #74826 Change-Id: I6a6a6964229548e9d54e7af95185011e183ee50b Reviewed-on: https://go-review.googlesource.com/c/go/+/691815 Reviewed-by: Michael Knyszek Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI --- src/go/build/deps_test.go | 2 +- src/internal/trace/base.go | 10 + src/internal/trace/event.go | 513 +++++++++++++++++++-- src/internal/trace/event_test.go | 743 ++++++++++++++++++++++++++++++- src/internal/trace/resources.go | 6 +- src/internal/trace/value.go | 6 +- 6 files changed, 1243 insertions(+), 37 deletions(-) diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index b02db785c13..295c69425ee 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -766,7 +766,7 @@ var depsRules = ` FMT, internal/trace/version, io, sort, encoding/binary < internal/trace/internal/tracev1; - FMT, encoding/binary, internal/trace/version, internal/trace/internal/tracev1, container/heap, math/rand + FMT, encoding/binary, internal/trace/version, internal/trace/internal/tracev1, container/heap, math/rand, regexp < internal/trace; # cmd/trace dependencies. diff --git a/src/internal/trace/base.go b/src/internal/trace/base.go index 1f17daa5f53..a452622973e 100644 --- a/src/internal/trace/base.go +++ b/src/internal/trace/base.go @@ -108,6 +108,16 @@ func (d *dataTable[EI, E]) insert(id EI, data E) error { return nil } +// append adds a new element to the data table and returns its ID. +func (d *dataTable[EI, E]) append(data E) EI { + if d.sparse == nil { + d.sparse = make(map[EI]E) + } + id := EI(len(d.sparse)) + 1 + d.sparse[id] = data + return id +} + // compactify attempts to compact sparse into dense. // // This is intended to be called only once after insertions are done. diff --git a/src/internal/trace/event.go b/src/internal/trace/event.go index b78e5232946..a8914729622 100644 --- a/src/internal/trace/event.go +++ b/src/internal/trace/event.go @@ -5,9 +5,13 @@ package trace import ( + "errors" "fmt" + "io" "iter" "math" + "regexp" + "slices" "strconv" "strings" "time" @@ -256,6 +260,19 @@ type Log struct { Message string } +// StackSample is used to construct StackSample events via MakeEvent. There are +// no details associated with it, use EventConfig.Stack instead. +type StackSample struct{} + +// MakeStack create a stack from a list of stack frames. +func MakeStack(frames []StackFrame) Stack { + // TODO(felixge): support evTable reuse. + tbl := &evTable{pcs: make(map[uint64]frame)} + tbl.strings.compactify() + tbl.stacks.compactify() + return Stack{table: tbl, id: addStack(tbl, frames)} +} + // Stack represents a stack. It's really a handle to a stack and it's trivially comparable. // // If two Stacks are equal then their Frames are guaranteed to be identical. If they are not @@ -287,6 +304,22 @@ func (s Stack) Frames() iter.Seq[StackFrame] { } } +// String returns the stack as a human-readable string. +// +// The format of the string is intended for debugging and is subject to change. +func (s Stack) String() string { + var sb strings.Builder + printStack(&sb, "", s.Frames()) + return sb.String() +} + +func printStack(w io.Writer, prefix string, frames iter.Seq[StackFrame]) { + for f := range frames { + fmt.Fprintf(w, "%s%s @ 0x%x\n", prefix, f.Func, f.PC) + fmt.Fprintf(w, "%s\t%s:%d\n", prefix, f.File, f.Line) + } +} + // NoStack is a sentinel value that can be compared against any Stack value, indicating // a lack of a stack trace. var NoStack = Stack{} @@ -332,9 +365,9 @@ func (e ExperimentalEvent) ArgValue(i int) Value { } if strings.HasSuffix(e.Args[i], "string") { s := e.table.strings.mustGet(stringID(e.argValues[i])) - return stringValue(s) + return StringValue(s) } - return uint64Value(e.argValues[i]) + return Uint64Value(e.argValues[i]) } // ExperimentalBatch represents a packet of unparsed data along with metadata about that packet. @@ -346,6 +379,416 @@ type ExperimentalBatch struct { Data []byte } +type EventDetails interface { + Metric | Label | Range | StateTransition | Sync | Task | Region | Log | StackSample +} + +// EventConfig holds the data for constructing a trace event. +type EventConfig[T EventDetails] struct { + // Time is the timestamp of the event. + Time Time + + // Kind is the kind of the event. + Kind EventKind + + // Goroutine is the goroutine ID of the event. + Goroutine GoID + + // Proc is the proc ID of the event. + Proc ProcID + + // Thread is the thread ID of the event. + Thread ThreadID + + // Stack is the stack of the event. + Stack Stack + + // Details is the kind specific details of the event. + Details T +} + +// MakeEvent creates a new trace event from the given configuration. +func MakeEvent[T EventDetails](c EventConfig[T]) (e Event, err error) { + // TODO(felixge): make the evTable reusable. + e = Event{ + table: &evTable{pcs: make(map[uint64]frame), sync: sync{freq: 1}}, + base: baseEvent{time: c.Time}, + ctx: schedCtx{G: c.Goroutine, P: c.Proc, M: c.Thread}, + } + defer func() { + // N.b. evSync is not in tracev2.Specs() + if err != nil || e.base.typ == evSync { + return + } + spec := tracev2.Specs()[e.base.typ] + if len(spec.StackIDs) > 0 && c.Stack != NoStack { + // The stack for the main execution context is always the + // first stack listed in StackIDs. Subtract one from this + // because we've peeled away the timestamp argument. + e.base.args[spec.StackIDs[0]-1] = uint64(addStack(e.table, slices.Collect(c.Stack.Frames()))) + } + + e.table.strings.compactify() + e.table.stacks.compactify() + }() + var defaultKind EventKind + switch c.Kind { + case defaultKind: + return Event{}, fmt.Errorf("the Kind field must be provided") + case EventMetric: + if m, ok := any(c.Details).(Metric); ok { + return makeMetricEvent(e, m) + } + case EventLabel: + if l, ok := any(c.Details).(Label); ok { + return makeLabelEvent(e, l) + } + case EventRangeBegin, EventRangeActive, EventRangeEnd: + if r, ok := any(c.Details).(Range); ok { + return makeRangeEvent(e, c.Kind, r) + } + case EventStateTransition: + if t, ok := any(c.Details).(StateTransition); ok { + return makeStateTransitionEvent(e, t) + } + case EventSync: + if s, ok := any(c.Details).(Sync); ok { + return makeSyncEvent(e, s) + } + case EventTaskBegin, EventTaskEnd: + if t, ok := any(c.Details).(Task); ok { + return makeTaskEvent(e, c.Kind, t) + } + case EventRegionBegin, EventRegionEnd: + if r, ok := any(c.Details).(Region); ok { + return makeRegionEvent(e, c.Kind, r) + } + case EventLog: + if l, ok := any(c.Details).(Log); ok { + return makeLogEvent(e, l) + } + case EventStackSample: + if _, ok := any(c.Details).(StackSample); ok { + return makeStackSampleEvent(e, c.Stack) + } + } + return Event{}, fmt.Errorf("the Kind field %s is incompatible with Details type %T", c.Kind, c.Details) +} + +func makeMetricEvent(e Event, m Metric) (Event, error) { + if m.Value.Kind() != ValueUint64 { + return Event{}, fmt.Errorf("metric value must be a uint64, got: %s", m.Value.String()) + } + switch m.Name { + case "/sched/gomaxprocs:threads": + e.base.typ = tracev2.EvProcsChange + case "/memory/classes/heap/objects:bytes": + e.base.typ = tracev2.EvHeapAlloc + case "/gc/heap/goal:bytes": + e.base.typ = tracev2.EvHeapGoal + default: + return Event{}, fmt.Errorf("unknown metric name: %s", m.Name) + } + e.base.args[0] = uint64(m.Value.Uint64()) + return e, nil +} + +func makeLabelEvent(e Event, l Label) (Event, error) { + if l.Resource.Kind != ResourceGoroutine { + return Event{}, fmt.Errorf("resource must be a goroutine: %s", l.Resource) + } + e.base.typ = tracev2.EvGoLabel + e.base.args[0] = uint64(e.table.strings.append(l.Label)) + // TODO(felixge): check against sched ctx and return error on mismatch + e.ctx.G = l.Resource.Goroutine() + return e, nil +} + +var stwRangeRegexp = regexp.MustCompile(`^stop-the-world \((.*)\)$`) + +// TODO(felixge): should this ever manipulate the e ctx? Or just report mismatches? +func makeRangeEvent(e Event, kind EventKind, r Range) (Event, error) { + // TODO(felixge): Should we add dedicated range kinds rather than using + // string names? + switch r.Name { + case "GC concurrent mark phase": + if r.Scope.Kind != ResourceNone { + return Event{}, fmt.Errorf("unexpected scope: %s", r.Scope) + } + switch kind { + case EventRangeBegin: + e.base.typ = tracev2.EvGCBegin + case EventRangeActive: + e.base.typ = tracev2.EvGCActive + case EventRangeEnd: + e.base.typ = tracev2.EvGCEnd + default: + return Event{}, fmt.Errorf("unexpected range kind: %s", kind) + } + case "GC incremental sweep": + if r.Scope.Kind != ResourceProc { + return Event{}, fmt.Errorf("unexpected scope: %s", r.Scope) + } + switch kind { + case EventRangeBegin: + e.base.typ = tracev2.EvGCSweepBegin + e.ctx.P = r.Scope.Proc() + case EventRangeActive: + e.base.typ = tracev2.EvGCSweepActive + e.base.args[0] = uint64(r.Scope.Proc()) + case EventRangeEnd: + e.base.typ = tracev2.EvGCSweepEnd + // TODO(felixge): check against sched ctx and return error on mismatch + e.ctx.P = r.Scope.Proc() + default: + return Event{}, fmt.Errorf("unexpected range kind: %s", kind) + } + case "GC mark assist": + if r.Scope.Kind != ResourceGoroutine { + return Event{}, fmt.Errorf("unexpected scope: %s", r.Scope) + } + switch kind { + case EventRangeBegin: + e.base.typ = tracev2.EvGCMarkAssistBegin + e.ctx.G = r.Scope.Goroutine() + case EventRangeActive: + e.base.typ = tracev2.EvGCMarkAssistActive + e.base.args[0] = uint64(r.Scope.Goroutine()) + case EventRangeEnd: + e.base.typ = tracev2.EvGCMarkAssistEnd + // TODO(felixge): check against sched ctx and return error on mismatch + e.ctx.G = r.Scope.Goroutine() + default: + return Event{}, fmt.Errorf("unexpected range kind: %s", kind) + } + default: + match := stwRangeRegexp.FindStringSubmatch(r.Name) + if len(match) != 2 { + return Event{}, fmt.Errorf("unexpected range name: %s", r.Name) + } + if r.Scope.Kind != ResourceGoroutine { + return Event{}, fmt.Errorf("unexpected scope: %s", r.Scope) + } + switch kind { + case EventRangeBegin: + e.base.typ = tracev2.EvSTWBegin + // TODO(felixge): check against sched ctx and return error on mismatch + e.ctx.G = r.Scope.Goroutine() + case EventRangeEnd: + e.base.typ = tracev2.EvSTWEnd + // TODO(felixge): check against sched ctx and return error on mismatch + e.ctx.G = r.Scope.Goroutine() + default: + return Event{}, fmt.Errorf("unexpected range kind: %s", kind) + } + e.base.args[0] = uint64(e.table.strings.append(match[1])) + } + return e, nil +} + +func makeStateTransitionEvent(e Event, t StateTransition) (Event, error) { + switch t.Resource.Kind { + case ResourceProc: + from, to := ProcState(t.oldState), ProcState(t.newState) + switch { + case from == ProcIdle && to == ProcIdle: + // TODO(felixge): Could this also be a ProcStatus event? + e.base.typ = tracev2.EvProcSteal + e.base.args[0] = uint64(t.Resource.Proc()) + e.base.extra(version.Go122)[0] = uint64(tracev2.ProcSyscallAbandoned) + case from == ProcIdle && to == ProcRunning: + e.base.typ = tracev2.EvProcStart + e.base.args[0] = uint64(t.Resource.Proc()) + case from == ProcRunning && to == ProcIdle: + e.base.typ = tracev2.EvProcStop + if t.Resource.Proc() != e.ctx.P { + e.base.typ = tracev2.EvProcSteal + e.base.args[0] = uint64(t.Resource.Proc()) + } + default: + e.base.typ = tracev2.EvProcStatus + e.base.args[0] = uint64(t.Resource.Proc()) + e.base.args[1] = uint64(procState2Tracev2ProcStatus[to]) + e.base.extra(version.Go122)[0] = uint64(procState2Tracev2ProcStatus[from]) + return e, nil + } + case ResourceGoroutine: + from, to := GoState(t.oldState), GoState(t.newState) + stack := slices.Collect(t.Stack.Frames()) + goroutine := t.Resource.Goroutine() + + if (from == GoUndetermined || from == to) && from != GoNotExist { + e.base.typ = tracev2.EvGoStatus + if len(stack) > 0 { + e.base.typ = tracev2.EvGoStatusStack + } + e.base.args[0] = uint64(goroutine) + e.base.args[2] = uint64(from)<<32 | uint64(goState2Tracev2GoStatus[to]) + } else { + switch from { + case GoNotExist: + switch to { + case GoWaiting: + e.base.typ = tracev2.EvGoCreateBlocked + e.base.args[0] = uint64(goroutine) + e.base.args[1] = uint64(addStack(e.table, stack)) + case GoRunnable: + e.base.typ = tracev2.EvGoCreate + e.base.args[0] = uint64(goroutine) + e.base.args[1] = uint64(addStack(e.table, stack)) + case GoSyscall: + e.base.typ = tracev2.EvGoCreateSyscall + e.base.args[0] = uint64(goroutine) + default: + return Event{}, fmt.Errorf("unexpected transition: %s -> %s", from, to) + } + case GoRunnable: + e.base.typ = tracev2.EvGoStart + e.base.args[0] = uint64(goroutine) + case GoRunning: + switch to { + case GoNotExist: + e.base.typ = tracev2.EvGoDestroy + e.ctx.G = goroutine + case GoRunnable: + e.base.typ = tracev2.EvGoStop + e.ctx.G = goroutine + e.base.args[0] = uint64(e.table.strings.append(t.Reason)) + case GoWaiting: + e.base.typ = tracev2.EvGoBlock + e.ctx.G = goroutine + e.base.args[0] = uint64(e.table.strings.append(t.Reason)) + case GoSyscall: + e.base.typ = tracev2.EvGoSyscallBegin + e.ctx.G = goroutine + default: + return Event{}, fmt.Errorf("unexpected transition: %s -> %s", from, to) + } + case GoSyscall: + switch to { + case GoNotExist: + e.base.typ = tracev2.EvGoDestroySyscall + e.ctx.G = goroutine + case GoRunning: + e.base.typ = tracev2.EvGoSyscallEnd + e.ctx.G = goroutine + case GoRunnable: + e.base.typ = tracev2.EvGoSyscallEndBlocked + e.ctx.G = goroutine + default: + return Event{}, fmt.Errorf("unexpected transition: %s -> %s", from, to) + } + case GoWaiting: + switch to { + case GoRunnable: + e.base.typ = tracev2.EvGoUnblock + e.base.args[0] = uint64(goroutine) + default: + return Event{}, fmt.Errorf("unexpected transition: %s -> %s", from, to) + } + default: + return Event{}, fmt.Errorf("unexpected transition: %s -> %s", from, to) + } + } + default: + return Event{}, fmt.Errorf("unsupported state transition resource: %s", t.Resource) + } + return e, nil +} + +func makeSyncEvent(e Event, s Sync) (Event, error) { + e.base.typ = evSync + e.base.args[0] = uint64(s.N) + if e.table.expBatches == nil { + e.table.expBatches = make(map[tracev2.Experiment][]ExperimentalBatch) + } + for name, batches := range s.ExperimentalBatches { + var found bool + for id, exp := range tracev2.Experiments() { + if exp == name { + found = true + e.table.expBatches[tracev2.Experiment(id)] = batches + break + } + } + if !found { + return Event{}, fmt.Errorf("unknown experiment: %s", name) + } + } + if s.ClockSnapshot != nil { + e.table.hasClockSnapshot = true + e.table.snapWall = s.ClockSnapshot.Wall + e.table.snapMono = s.ClockSnapshot.Mono + // N.b. MakeEvent sets e.table.freq to 1. + e.table.snapTime = timestamp(s.ClockSnapshot.Trace) + } + return e, nil +} + +func makeTaskEvent(e Event, kind EventKind, t Task) (Event, error) { + if t.ID == NoTask { + return Event{}, errors.New("task ID cannot be NoTask") + } + e.base.args[0] = uint64(t.ID) + switch kind { + case EventTaskBegin: + e.base.typ = tracev2.EvUserTaskBegin + e.base.args[1] = uint64(t.Parent) + e.base.args[2] = uint64(e.table.strings.append(t.Type)) + case EventTaskEnd: + e.base.typ = tracev2.EvUserTaskEnd + e.base.extra(version.Go122)[0] = uint64(t.Parent) + e.base.extra(version.Go122)[1] = uint64(e.table.addExtraString(t.Type)) + default: + // TODO(felixge): also do this for ranges? + panic("unexpected task kind") + } + return e, nil +} + +func makeRegionEvent(e Event, kind EventKind, r Region) (Event, error) { + e.base.args[0] = uint64(r.Task) + e.base.args[1] = uint64(e.table.strings.append(r.Type)) + switch kind { + case EventRegionBegin: + e.base.typ = tracev2.EvUserRegionBegin + case EventRegionEnd: + e.base.typ = tracev2.EvUserRegionEnd + default: + panic("unexpected region kind") + } + return e, nil +} + +func makeLogEvent(e Event, l Log) (Event, error) { + e.base.typ = tracev2.EvUserLog + e.base.args[0] = uint64(l.Task) + e.base.args[1] = uint64(e.table.strings.append(l.Category)) + e.base.args[2] = uint64(e.table.strings.append(l.Message)) + return e, nil +} + +func makeStackSampleEvent(e Event, s Stack) (Event, error) { + e.base.typ = tracev2.EvCPUSample + frames := slices.Collect(s.Frames()) + e.base.args[0] = uint64(addStack(e.table, frames)) + return e, nil +} + +func addStack(table *evTable, frames []StackFrame) stackID { + var pcs []uint64 + for _, f := range frames { + table.pcs[f.PC] = frame{ + pc: f.PC, + funcID: table.strings.append(f.Func), + fileID: table.strings.append(f.File), + line: f.Line, + } + pcs = append(pcs, f.PC) + } + return table.stacks.append(stack{pcs: pcs}) +} + // Event represents a single event in the trace. type Event struct { table *evTable @@ -435,13 +878,13 @@ func (e Event) Metric() Metric { switch e.base.typ { case tracev2.EvProcsChange: m.Name = "/sched/gomaxprocs:threads" - m.Value = uint64Value(e.base.args[0]) + m.Value = Uint64Value(e.base.args[0]) case tracev2.EvHeapAlloc: m.Name = "/memory/classes/heap/objects:bytes" - m.Value = uint64Value(e.base.args[0]) + m.Value = Uint64Value(e.base.args[0]) case tracev2.EvHeapGoal: m.Name = "/gc/heap/goal:bytes" - m.Value = uint64Value(e.base.args[0]) + m.Value = Uint64Value(e.base.args[0]) default: panic(fmt.Sprintf("internal error: unexpected wire-format event type for Metric kind: %d", e.base.typ)) } @@ -516,11 +959,11 @@ func (e Event) RangeAttributes() []RangeAttribute { return []RangeAttribute{ { Name: "bytes swept", - Value: uint64Value(e.base.args[0]), + Value: Uint64Value(e.base.args[0]), }, { Name: "bytes reclaimed", - Value: uint64Value(e.base.args[1]), + Value: Uint64Value(e.base.args[1]), }, } } @@ -594,9 +1037,9 @@ func (e Event) StateTransition() StateTransition { var s StateTransition switch e.base.typ { case tracev2.EvProcStart: - s = procStateTransition(ProcID(e.base.args[0]), ProcIdle, ProcRunning) + s = MakeProcStateTransition(ProcID(e.base.args[0]), ProcIdle, ProcRunning) case tracev2.EvProcStop: - s = procStateTransition(e.ctx.P, ProcRunning, ProcIdle) + s = MakeProcStateTransition(e.ctx.P, ProcRunning, ProcIdle) case tracev2.EvProcSteal: // N.B. ordering.advance populates e.base.extra. beforeState := ProcRunning @@ -607,49 +1050,50 @@ func (e Event) StateTransition() StateTransition { // transition. beforeState = ProcIdle } - s = procStateTransition(ProcID(e.base.args[0]), beforeState, ProcIdle) + s = MakeProcStateTransition(ProcID(e.base.args[0]), beforeState, ProcIdle) case tracev2.EvProcStatus: // N.B. ordering.advance populates e.base.extra. - s = procStateTransition(ProcID(e.base.args[0]), ProcState(e.base.extra(version.Go122)[0]), tracev2ProcStatus2ProcState[e.base.args[1]]) + s = MakeProcStateTransition(ProcID(e.base.args[0]), ProcState(e.base.extra(version.Go122)[0]), tracev2ProcStatus2ProcState[e.base.args[1]]) case tracev2.EvGoCreate, tracev2.EvGoCreateBlocked: status := GoRunnable if e.base.typ == tracev2.EvGoCreateBlocked { status = GoWaiting } - s = goStateTransition(GoID(e.base.args[0]), GoNotExist, status) + s = MakeGoStateTransition(GoID(e.base.args[0]), GoNotExist, status) s.Stack = Stack{table: e.table, id: stackID(e.base.args[1])} case tracev2.EvGoCreateSyscall: - s = goStateTransition(GoID(e.base.args[0]), GoNotExist, GoSyscall) + s = MakeGoStateTransition(GoID(e.base.args[0]), GoNotExist, GoSyscall) case tracev2.EvGoStart: - s = goStateTransition(GoID(e.base.args[0]), GoRunnable, GoRunning) + s = MakeGoStateTransition(GoID(e.base.args[0]), GoRunnable, GoRunning) case tracev2.EvGoDestroy: - s = goStateTransition(e.ctx.G, GoRunning, GoNotExist) + s = MakeGoStateTransition(e.ctx.G, GoRunning, GoNotExist) case tracev2.EvGoDestroySyscall: - s = goStateTransition(e.ctx.G, GoSyscall, GoNotExist) + s = MakeGoStateTransition(e.ctx.G, GoSyscall, GoNotExist) case tracev2.EvGoStop: - s = goStateTransition(e.ctx.G, GoRunning, GoRunnable) + s = MakeGoStateTransition(e.ctx.G, GoRunning, GoRunnable) s.Reason = e.table.strings.mustGet(stringID(e.base.args[0])) s.Stack = e.Stack() // This event references the resource the event happened on. case tracev2.EvGoBlock: - s = goStateTransition(e.ctx.G, GoRunning, GoWaiting) + s = MakeGoStateTransition(e.ctx.G, GoRunning, GoWaiting) s.Reason = e.table.strings.mustGet(stringID(e.base.args[0])) s.Stack = e.Stack() // This event references the resource the event happened on. case tracev2.EvGoUnblock, tracev2.EvGoSwitch, tracev2.EvGoSwitchDestroy: // N.B. GoSwitch and GoSwitchDestroy both emit additional events, but // the first thing they both do is unblock the goroutine they name, // identically to an unblock event (even their arguments match). - s = goStateTransition(GoID(e.base.args[0]), GoWaiting, GoRunnable) + s = MakeGoStateTransition(GoID(e.base.args[0]), GoWaiting, GoRunnable) case tracev2.EvGoSyscallBegin: - s = goStateTransition(e.ctx.G, GoRunning, GoSyscall) + s = MakeGoStateTransition(e.ctx.G, GoRunning, GoSyscall) s.Stack = e.Stack() // This event references the resource the event happened on. case tracev2.EvGoSyscallEnd: - s = goStateTransition(e.ctx.G, GoSyscall, GoRunning) + s = MakeGoStateTransition(e.ctx.G, GoSyscall, GoRunning) case tracev2.EvGoSyscallEndBlocked: - s = goStateTransition(e.ctx.G, GoSyscall, GoRunnable) + s = MakeGoStateTransition(e.ctx.G, GoSyscall, GoRunnable) case tracev2.EvGoStatus, tracev2.EvGoStatusStack: packedStatus := e.base.args[2] from, to := packedStatus>>32, packedStatus&((1<<32)-1) - s = goStateTransition(GoID(e.base.args[0]), GoState(from), tracev2GoStatus2GoState[to]) + s = MakeGoStateTransition(GoID(e.base.args[0]), GoState(from), tracev2GoStatus2GoState[to]) + s.Stack = e.Stack() // This event references the resource the event happened on. default: panic(fmt.Sprintf("internal error: unexpected wire-format event type for StateTransition kind: %d", e.base.typ)) } @@ -793,6 +1237,13 @@ var tracev2GoStatus2GoState = [...]GoState{ tracev2.GoSyscall: GoSyscall, } +var goState2Tracev2GoStatus = [...]tracev2.GoStatus{ + GoRunnable: tracev2.GoRunnable, + GoRunning: tracev2.GoRunning, + GoWaiting: tracev2.GoWaiting, + GoSyscall: tracev2.GoSyscall, +} + var tracev2ProcStatus2ProcState = [...]ProcState{ tracev2.ProcRunning: ProcRunning, tracev2.ProcIdle: ProcIdle, @@ -800,6 +1251,12 @@ var tracev2ProcStatus2ProcState = [...]ProcState{ tracev2.ProcSyscallAbandoned: ProcIdle, } +var procState2Tracev2ProcStatus = [...]tracev2.ProcStatus{ + ProcRunning: tracev2.ProcRunning, + ProcIdle: tracev2.ProcIdle, + // TODO(felixge): how to map ProcSyscall and ProcSyscallAbandoned? +} + // String returns the event as a human-readable string. // // The format of the string is intended for debugging and is subject to change. @@ -857,10 +1314,7 @@ func (e Event) String() string { if s.Stack != NoStack { fmt.Fprintln(&sb) fmt.Fprintln(&sb, "TransitionStack=") - for f := range s.Stack.Frames() { - fmt.Fprintf(&sb, "\t%s @ 0x%x\n", f.Func, f.PC) - fmt.Fprintf(&sb, "\t\t%s:%d\n", f.File, f.Line) - } + printStack(&sb, "\t", s.Stack.Frames()) } case EventExperimental: r := e.Experimental() @@ -886,10 +1340,7 @@ func (e Event) String() string { if stk := e.Stack(); stk != NoStack { fmt.Fprintln(&sb) fmt.Fprintln(&sb, "Stack=") - for f := range stk.Frames() { - fmt.Fprintf(&sb, "\t%s @ 0x%x\n", f.Func, f.PC) - fmt.Fprintf(&sb, "\t\t%s:%d\n", f.File, f.Line) - } + printStack(&sb, "\t", stk.Frames()) } return sb.String() } diff --git a/src/internal/trace/event_test.go b/src/internal/trace/event_test.go index d39d6b75bd7..1ae01af2c19 100644 --- a/src/internal/trace/event_test.go +++ b/src/internal/trace/event_test.go @@ -4,7 +4,748 @@ package trace -import "testing" +import ( + "fmt" + "internal/diff" + "reflect" + "slices" + "testing" + "time" +) + +func TestMakeEvent(t *testing.T) { + checkTime := func(t *testing.T, ev Event, want Time) { + t.Helper() + if ev.Time() != want { + t.Errorf("expected time to be %d, got %d", want, ev.Time()) + } + } + checkValid := func(t *testing.T, err error, valid bool) bool { + t.Helper() + if valid && err == nil { + return true + } + if valid && err != nil { + t.Errorf("expected no error, got %v", err) + } else if !valid && err == nil { + t.Errorf("expected error, got %v", err) + } + return false + } + type stackType string + const ( + schedStack stackType = "sched stack" + stStack stackType = "state transition stack" + ) + checkStack := func(t *testing.T, got Stack, want Stack, which stackType) { + t.Helper() + diff := diff.Diff("want", []byte(want.String()), "got", []byte(got.String())) + if len(diff) > 0 { + t.Errorf("unexpected %s: %s", which, diff) + } + } + stk1 := MakeStack([]StackFrame{ + {PC: 1, Func: "foo", File: "foo.go", Line: 10}, + {PC: 2, Func: "bar", File: "bar.go", Line: 20}, + }) + stk2 := MakeStack([]StackFrame{ + {PC: 1, Func: "foo", File: "foo.go", Line: 10}, + {PC: 2, Func: "bar", File: "bar.go", Line: 20}, + }) + + t.Run("Metric", func(t *testing.T) { + tests := []struct { + name string + metric string + val uint64 + stack Stack + valid bool + }{ + {name: "gomaxprocs", metric: "/sched/gomaxprocs:threads", valid: true, val: 1, stack: NoStack}, + {name: "gomaxprocs with stack", metric: "/sched/gomaxprocs:threads", valid: true, val: 1, stack: stk1}, + {name: "heap objects", metric: "/memory/classes/heap/objects:bytes", valid: true, val: 2, stack: NoStack}, + {name: "heap goal", metric: "/gc/heap/goal:bytes", valid: true, val: 3, stack: NoStack}, + {name: "invalid metric", metric: "/test", valid: false, val: 4, stack: NoStack}, + } + for i, test := range tests { + t.Run(test.name, func(t *testing.T) { + ev, err := MakeEvent(EventConfig[Metric]{ + Kind: EventMetric, + Time: Time(42 + i), + Details: Metric{Name: test.metric, Value: Uint64Value(test.val)}, + Stack: test.stack, + }) + if !checkValid(t, err, test.valid) { + return + } + checkTime(t, ev, Time(42+i)) + checkStack(t, ev.Stack(), test.stack, schedStack) + got := ev.Metric() + if got.Name != test.metric { + t.Errorf("expected name to be %q, got %q", test.metric, got.Name) + } + if got.Value.Uint64() != test.val { + t.Errorf("expected value to be %d, got %d", test.val, got.Value.Uint64()) + } + }) + } + }) + + t.Run("Label", func(t *testing.T) { + ev, err := MakeEvent(EventConfig[Label]{ + Kind: EventLabel, + Time: 42, + Details: Label{Label: "test", Resource: MakeResourceID(GoID(23))}, + }) + if !checkValid(t, err, true) { + return + } + label := ev.Label() + if label.Label != "test" { + t.Errorf("expected label to be test, got %q", label.Label) + } + if label.Resource.Kind != ResourceGoroutine { + t.Errorf("expected label resource to be goroutine, got %d", label.Resource.Kind) + } + if label.Resource.id != 23 { + t.Errorf("expected label resource to be 23, got %d", label.Resource.id) + } + checkTime(t, ev, 42) + }) + + t.Run("Range", func(t *testing.T) { + tests := []struct { + kind EventKind + name string + scope ResourceID + valid bool + }{ + {kind: EventRangeBegin, name: "GC concurrent mark phase", scope: ResourceID{}, valid: true}, + {kind: EventRangeActive, name: "GC concurrent mark phase", scope: ResourceID{}, valid: true}, + {kind: EventRangeEnd, name: "GC concurrent mark phase", scope: ResourceID{}, valid: true}, + {kind: EventMetric, name: "GC concurrent mark phase", scope: ResourceID{}, valid: false}, + {kind: EventRangeBegin, name: "GC concurrent mark phase - INVALID", scope: ResourceID{}, valid: false}, + + {kind: EventRangeBegin, name: "GC incremental sweep", scope: MakeResourceID(ProcID(1)), valid: true}, + {kind: EventRangeActive, name: "GC incremental sweep", scope: MakeResourceID(ProcID(2)), valid: true}, + {kind: EventRangeEnd, name: "GC incremental sweep", scope: MakeResourceID(ProcID(3)), valid: true}, + {kind: EventMetric, name: "GC incremental sweep", scope: MakeResourceID(ProcID(4)), valid: false}, + {kind: EventRangeBegin, name: "GC incremental sweep - INVALID", scope: MakeResourceID(ProcID(5)), valid: false}, + + {kind: EventRangeBegin, name: "GC mark assist", scope: MakeResourceID(GoID(1)), valid: true}, + {kind: EventRangeActive, name: "GC mark assist", scope: MakeResourceID(GoID(2)), valid: true}, + {kind: EventRangeEnd, name: "GC mark assist", scope: MakeResourceID(GoID(3)), valid: true}, + {kind: EventMetric, name: "GC mark assist", scope: MakeResourceID(GoID(4)), valid: false}, + {kind: EventRangeBegin, name: "GC mark assist - INVALID", scope: MakeResourceID(GoID(5)), valid: false}, + + {kind: EventRangeBegin, name: "stop-the-world (for a good reason)", scope: MakeResourceID(GoID(1)), valid: true}, + {kind: EventRangeActive, name: "stop-the-world (for a good reason)", scope: MakeResourceID(GoID(2)), valid: false}, + {kind: EventRangeEnd, name: "stop-the-world (for a good reason)", scope: MakeResourceID(GoID(3)), valid: true}, + {kind: EventMetric, name: "stop-the-world (for a good reason)", scope: MakeResourceID(GoID(4)), valid: false}, + {kind: EventRangeBegin, name: "stop-the-world (for a good reason) - INVALID", scope: MakeResourceID(GoID(5)), valid: false}, + } + + for i, test := range tests { + name := fmt.Sprintf("%s/%s/%s", test.kind, test.name, test.scope) + t.Run(name, func(t *testing.T) { + ev, err := MakeEvent(EventConfig[Range]{ + Time: Time(42 + i), + Kind: test.kind, + Details: Range{Name: test.name, Scope: test.scope}, + }) + if !checkValid(t, err, test.valid) { + return + } + got := ev.Range() + if got.Name != test.name { + t.Errorf("expected name to be %q, got %q", test.name, got.Name) + } + if ev.Kind() != test.kind { + t.Errorf("expected kind to be %s, got %s", test.kind, ev.Kind()) + } + if got.Scope.String() != test.scope.String() { + t.Errorf("expected scope to be %s, got %s", test.scope.String(), got.Scope.String()) + } + checkTime(t, ev, Time(42+i)) + }) + } + }) + + t.Run("GoroutineTransition", func(t *testing.T) { + const anotherG = 999 // indicates hat sched g is different from transition g + tests := []struct { + name string + g GoID + stack Stack + stG GoID + from GoState + to GoState + reason string + stStack Stack + valid bool + }{ + { + name: "EvGoCreate", + g: anotherG, + stack: stk1, + stG: 1, + from: GoNotExist, + to: GoRunnable, + reason: "", + stStack: stk2, + valid: true, + }, + { + name: "EvGoCreateBlocked", + g: anotherG, + stack: stk1, + stG: 2, + from: GoNotExist, + to: GoWaiting, + reason: "", + stStack: stk2, + valid: true, + }, + { + name: "EvGoCreateSyscall", + g: anotherG, + stack: NoStack, + stG: 3, + from: GoNotExist, + to: GoSyscall, + reason: "", + stStack: NoStack, + valid: true, + }, + { + name: "EvGoStart", + g: anotherG, + stack: NoStack, + stG: 4, + from: GoRunnable, + to: GoRunning, + reason: "", + stStack: NoStack, + valid: true, + }, + { + name: "EvGoDestroy", + g: 5, + stack: NoStack, + stG: 5, + from: GoRunning, + to: GoNotExist, + reason: "", + stStack: NoStack, + valid: true, + }, + { + name: "EvGoDestroySyscall", + g: 6, + stack: NoStack, + stG: 6, + from: GoSyscall, + to: GoNotExist, + reason: "", + stStack: NoStack, + valid: true, + }, + { + name: "EvGoStop", + g: 7, + stack: stk1, + stG: 7, + from: GoRunning, + to: GoRunnable, + reason: "preempted", + stStack: stk1, + valid: true, + }, + { + name: "EvGoBlock", + g: 8, + stack: stk1, + stG: 8, + from: GoRunning, + to: GoWaiting, + reason: "blocked", + stStack: stk1, + valid: true, + }, + { + name: "EvGoUnblock", + g: 9, + stack: stk1, + stG: anotherG, + from: GoWaiting, + to: GoRunnable, + reason: "", + stStack: NoStack, + valid: true, + }, + // N.b. EvGoUnblock, EvGoSwitch and EvGoSwitchDestroy cannot be + // distinguished from each other in Event form, so MakeEvent only + // produces EvGoUnblock events for Waiting -> Runnable transitions. + { + name: "EvGoSyscallBegin", + g: 10, + stack: stk1, + stG: 10, + from: GoRunning, + to: GoSyscall, + reason: "", + stStack: stk1, + valid: true, + }, + { + name: "EvGoSyscallEnd", + g: 11, + stack: NoStack, + stG: 11, + from: GoSyscall, + to: GoRunning, + reason: "", + stStack: NoStack, + valid: true, + }, + { + name: "EvGoSyscallEndBlocked", + g: 12, + stack: NoStack, + stG: 12, + from: GoSyscall, + to: GoRunnable, + reason: "", + stStack: NoStack, + valid: true, + }, + // TODO(felixge): Use coverage testsing to check if we need all these GoStatus/GoStatusStack cases + { + name: "GoStatus Undetermined->Waiting", + g: anotherG, + stack: NoStack, + stG: 13, + from: GoUndetermined, + to: GoWaiting, + reason: "", + stStack: NoStack, + valid: true, + }, + { + name: "GoStatus Undetermined->Running", + g: anotherG, + stack: NoStack, + stG: 14, + from: GoUndetermined, + to: GoRunning, + reason: "", + stStack: NoStack, + valid: true, + }, + { + name: "GoStatusStack Undetermined->Waiting", + g: anotherG, + stack: stk1, + stG: 15, + from: GoUndetermined, + to: GoWaiting, + reason: "", + stStack: stk1, + valid: true, + }, + { + name: "GoStatusStack Undetermined->Runnable", + g: anotherG, + stack: stk1, + stG: 16, + from: GoUndetermined, + to: GoRunnable, + reason: "", + stStack: stk1, + valid: true, + }, + { + name: "GoStatus Runnable->Runnable", + g: anotherG, + stack: NoStack, + stG: 17, + from: GoRunnable, + to: GoRunnable, + reason: "", + stStack: NoStack, + valid: true, + }, + { + name: "GoStatus Runnable->Running", + g: anotherG, + stack: NoStack, + stG: 18, + from: GoRunnable, + to: GoRunning, + reason: "", + stStack: NoStack, + valid: true, + }, + { + name: "invalid NotExits->NotExists", + g: anotherG, + stack: stk1, + stG: 18, + from: GoNotExist, + to: GoNotExist, + reason: "", + stStack: NoStack, + valid: false, + }, + { + name: "invalid Running->Undetermined", + g: anotherG, + stack: stk1, + stG: 19, + from: GoRunning, + to: GoUndetermined, + reason: "", + stStack: NoStack, + valid: false, + }, + } + + for i, test := range tests { + t.Run(test.name, func(t *testing.T) { + st := MakeGoStateTransition(test.stG, test.from, test.to) + st.Stack = test.stStack + st.Reason = test.reason + ev, err := MakeEvent(EventConfig[StateTransition]{ + Kind: EventStateTransition, + Time: Time(42 + i), + Goroutine: test.g, + Stack: test.stack, + Details: st, + }) + if !checkValid(t, err, test.valid) { + return + } + checkStack(t, ev.Stack(), test.stack, schedStack) + if ev.Goroutine() != test.g { + t.Errorf("expected goroutine to be %d, got %d", test.g, ev.Goroutine()) + } + got := ev.StateTransition() + if got.Resource.Goroutine() != test.stG { + t.Errorf("expected resource to be %d, got %d", test.stG, got.Resource.Goroutine()) + } + from, to := got.Goroutine() + if from != test.from { + t.Errorf("from got=%s want=%s", from, test.from) + } + if to != test.to { + t.Errorf("to got=%s want=%s", to, test.to) + } + if got.Reason != test.reason { + t.Errorf("expected reason to be %s, got %s", test.reason, got.Reason) + } + checkStack(t, got.Stack, test.stStack, stStack) + checkTime(t, ev, Time(42+i)) + }) + } + }) + + t.Run("ProcTransition", func(t *testing.T) { + tests := []struct { + name string + proc ProcID + schedProc ProcID + from ProcState + to ProcState + valid bool + }{ + {name: "ProcStart", proc: 1, schedProc: 99, from: ProcIdle, to: ProcRunning, valid: true}, + {name: "ProcStop", proc: 2, schedProc: 2, from: ProcRunning, to: ProcIdle, valid: true}, + {name: "ProcSteal", proc: 3, schedProc: 99, from: ProcRunning, to: ProcIdle, valid: true}, + {name: "ProcSteal lost info", proc: 4, schedProc: 99, from: ProcIdle, to: ProcIdle, valid: true}, + {name: "ProcStatus", proc: 5, schedProc: 99, from: ProcUndetermined, to: ProcRunning, valid: true}, + } + for i, test := range tests { + t.Run(test.name, func(t *testing.T) { + st := MakeProcStateTransition(test.proc, test.from, test.to) + ev, err := MakeEvent(EventConfig[StateTransition]{ + Kind: EventStateTransition, + Time: Time(42 + i), + Proc: test.schedProc, + Details: st, + }) + if !checkValid(t, err, test.valid) { + return + } + checkTime(t, ev, Time(42+i)) + gotSt := ev.StateTransition() + from, to := gotSt.Proc() + if from != test.from { + t.Errorf("from got=%s want=%s", from, test.from) + } + if to != test.to { + t.Errorf("to got=%s want=%s", to, test.to) + } + if ev.Proc() != test.schedProc { + t.Errorf("expected proc to be %d, got %d", test.schedProc, ev.Proc()) + } + if gotSt.Resource.Proc() != test.proc { + t.Errorf("expected resource to be %d, got %d", test.proc, gotSt.Resource.Proc()) + } + }) + } + }) + + t.Run("Sync", func(t *testing.T) { + tests := []struct { + name string + kind EventKind + n int + clock *ClockSnapshot + batches map[string][]ExperimentalBatch + valid bool + }{ + { + name: "invalid kind", + n: 1, + valid: false, + }, + { + name: "N", + kind: EventSync, + n: 1, + batches: map[string][]ExperimentalBatch{}, + valid: true, + }, + { + name: "N+ClockSnapshot", + kind: EventSync, + n: 1, + batches: map[string][]ExperimentalBatch{}, + clock: &ClockSnapshot{ + Trace: 1, + Wall: time.Unix(59, 123456789), + Mono: 2, + }, + valid: true, + }, + { + name: "N+Batches", + kind: EventSync, + n: 1, + batches: map[string][]ExperimentalBatch{ + "AllocFree": {{Thread: 1, Data: []byte{1, 2, 3}}}, + }, + valid: true, + }, + { + name: "unknown experiment", + kind: EventSync, + n: 1, + batches: map[string][]ExperimentalBatch{ + "does-not-exist": {{Thread: 1, Data: []byte{1, 2, 3}}}, + }, + valid: false, + }, + } + for i, test := range tests { + t.Run(test.name, func(t *testing.T) { + ev, err := MakeEvent(EventConfig[Sync]{ + Kind: test.kind, + Time: Time(42 + i), + Details: Sync{N: test.n, ClockSnapshot: test.clock, ExperimentalBatches: test.batches}, + }) + if !checkValid(t, err, test.valid) { + return + } + got := ev.Sync() + checkTime(t, ev, Time(42+i)) + if got.N != test.n { + t.Errorf("expected N to be %d, got %d", test.n, got.N) + } + if test.clock != nil && got.ClockSnapshot == nil { + t.Fatalf("expected ClockSnapshot to be non-nil") + } else if test.clock == nil && got.ClockSnapshot != nil { + t.Fatalf("expected ClockSnapshot to be nil") + } else if test.clock != nil && got.ClockSnapshot != nil { + if got.ClockSnapshot.Trace != test.clock.Trace { + t.Errorf("expected ClockSnapshot.Trace to be %d, got %d", test.clock.Trace, got.ClockSnapshot.Trace) + } + if !got.ClockSnapshot.Wall.Equal(test.clock.Wall) { + t.Errorf("expected ClockSnapshot.Wall to be %s, got %s", test.clock.Wall, got.ClockSnapshot.Wall) + } + if got.ClockSnapshot.Mono != test.clock.Mono { + t.Errorf("expected ClockSnapshot.Mono to be %d, got %d", test.clock.Mono, got.ClockSnapshot.Mono) + } + } + if !reflect.DeepEqual(got.ExperimentalBatches, test.batches) { + t.Errorf("expected ExperimentalBatches to be %#v, got %#v", test.batches, got.ExperimentalBatches) + } + }) + } + }) + + t.Run("Task", func(t *testing.T) { + tests := []struct { + name string + kind EventKind + id TaskID + parent TaskID + typ string + valid bool + }{ + {name: "no task", kind: EventTaskBegin, id: NoTask, parent: 1, typ: "type-0", valid: false}, + {name: "invalid kind", kind: EventMetric, id: 1, parent: 2, typ: "type-1", valid: false}, + {name: "EvUserTaskBegin", kind: EventTaskBegin, id: 2, parent: 3, typ: "type-2", valid: true}, + {name: "EvUserTaskEnd", kind: EventTaskEnd, id: 3, parent: 4, typ: "type-3", valid: true}, + {name: "no parent", kind: EventTaskBegin, id: 4, parent: NoTask, typ: "type-4", valid: true}, + } + + for i, test := range tests { + t.Run(test.name, func(t *testing.T) { + ev, err := MakeEvent(EventConfig[Task]{ + Kind: test.kind, + Time: Time(42 + i), + Details: Task{ID: test.id, Parent: test.parent, Type: test.typ}, + }) + if !checkValid(t, err, test.valid) { + return + } + checkTime(t, ev, Time(42+i)) + got := ev.Task() + if got.ID != test.id { + t.Errorf("expected ID to be %d, got %d", test.id, got.ID) + } + if got.Parent != test.parent { + t.Errorf("expected Parent to be %d, got %d", test.parent, got.Parent) + } + if got.Type != test.typ { + t.Errorf("expected Type to be %s, got %s", test.typ, got.Type) + } + }) + } + }) + + t.Run("Region", func(t *testing.T) { + tests := []struct { + name string + kind EventKind + task TaskID + typ string + valid bool + }{ + {name: "invalid kind", kind: EventMetric, task: 1, typ: "type-1", valid: false}, + {name: "EvUserRegionBegin", kind: EventRegionBegin, task: 2, typ: "type-2", valid: true}, + {name: "EvUserRegionEnd", kind: EventRegionEnd, task: 3, typ: "type-3", valid: true}, + } + + for i, test := range tests { + t.Run(test.name, func(t *testing.T) { + ev, err := MakeEvent(EventConfig[Region]{ + Kind: test.kind, + Time: Time(42 + i), + Details: Region{Task: test.task, Type: test.typ}, + }) + if !checkValid(t, err, test.valid) { + return + } + checkTime(t, ev, Time(42+i)) + got := ev.Region() + if got.Task != test.task { + t.Errorf("expected Task to be %d, got %d", test.task, got.Task) + } + if got.Type != test.typ { + t.Errorf("expected Type to be %s, got %s", test.typ, got.Type) + } + }) + } + }) + + t.Run("Log", func(t *testing.T) { + tests := []struct { + name string + kind EventKind + task TaskID + category string + message string + valid bool + }{ + {name: "invalid kind", kind: EventMetric, task: 1, category: "category-1", message: "message-1", valid: false}, + {name: "basic", kind: EventLog, task: 2, category: "category-2", message: "message-2", valid: true}, + } + + for i, test := range tests { + t.Run(test.name, func(t *testing.T) { + ev, err := MakeEvent(EventConfig[Log]{ + Kind: test.kind, + Time: Time(42 + i), + Details: Log{Task: test.task, Category: test.category, Message: test.message}, + }) + if !checkValid(t, err, test.valid) { + return + } + checkTime(t, ev, Time(42+i)) + got := ev.Log() + if got.Task != test.task { + t.Errorf("expected Task to be %d, got %d", test.task, got.Task) + } + if got.Category != test.category { + t.Errorf("expected Category to be %s, got %s", test.category, got.Category) + } + if got.Message != test.message { + t.Errorf("expected Message to be %s, got %s", test.message, got.Message) + } + }) + } + + }) + + t.Run("StackSample", func(t *testing.T) { + tests := []struct { + name string + kind EventKind + stack Stack + valid bool + }{ + {name: "invalid kind", kind: EventMetric, stack: stk1, valid: false}, + {name: "basic", kind: EventStackSample, stack: stk1, valid: true}, + } + + for i, test := range tests { + t.Run(test.name, func(t *testing.T) { + ev, err := MakeEvent(EventConfig[StackSample]{ + Kind: test.kind, + Time: Time(42 + i), + Stack: test.stack, + // N.b. Details defaults to StackSample{}, so we can + // omit it here. + }) + if !checkValid(t, err, test.valid) { + return + } + checkTime(t, ev, Time(42+i)) + got := ev.Stack() + checkStack(t, got, test.stack, schedStack) + }) + } + + }) +} + +func TestMakeStack(t *testing.T) { + frames := []StackFrame{ + {PC: 1, Func: "foo", File: "foo.go", Line: 10}, + {PC: 2, Func: "bar", File: "bar.go", Line: 20}, + } + got := slices.Collect(MakeStack(frames).Frames()) + if len(got) != len(frames) { + t.Errorf("got=%d want=%d", len(got), len(frames)) + } + for i := range got { + if got[i] != frames[i] { + t.Errorf("got=%v want=%v", got[i], frames[i]) + } + } +} func TestPanicEvent(t *testing.T) { // Use a sync event for this because it doesn't have any extra metadata. diff --git a/src/internal/trace/resources.go b/src/internal/trace/resources.go index 24db2f8d77a..951159ddbdc 100644 --- a/src/internal/trace/resources.go +++ b/src/internal/trace/resources.go @@ -228,7 +228,8 @@ type StateTransition struct { newState uint8 } -func goStateTransition(id GoID, from, to GoState) StateTransition { +// MakeGoStateTransition creates a goroutine state transition. +func MakeGoStateTransition(id GoID, from, to GoState) StateTransition { return StateTransition{ Resource: ResourceID{Kind: ResourceGoroutine, id: int64(id)}, oldState: uint8(from), @@ -236,7 +237,8 @@ func goStateTransition(id GoID, from, to GoState) StateTransition { } } -func procStateTransition(id ProcID, from, to ProcState) StateTransition { +// MakeProcStateTransition creates a proc state transition. +func MakeProcStateTransition(id ProcID, from, to ProcState) StateTransition { return StateTransition{ Resource: ResourceID{Kind: ResourceProc, id: int64(id)}, oldState: uint8(from), diff --git a/src/internal/trace/value.go b/src/internal/trace/value.go index fc2808e5975..41eeb50d87f 100644 --- a/src/internal/trace/value.go +++ b/src/internal/trace/value.go @@ -58,10 +58,12 @@ func (v Value) String() string { return "Value{Bad}" } -func uint64Value(x uint64) Value { +// Uint64Value creates a value of kind ValueUint64. +func Uint64Value(x uint64) Value { return Value{kind: ValueUint64, scalar: x} } -func stringValue(s string) Value { +// StringValue creates a value of kind ValueString. +func StringValue(s string) Value { return Value{kind: ValueString, scalar: uint64(len(s)), pointer: unsafe.Pointer(unsafe.StringData(s))} } From ab2829ec06cbe7fb1464bcf929fffcd6a7ad68b8 Mon Sep 17 00:00:00 2001 From: David Chase Date: Mon, 17 Nov 2025 13:53:58 -0500 Subject: [PATCH 051/140] cmd/compile: adjust start heap size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TLDR - not-huge increase to default starting heap boost, - small improvement in build performance, - remove concurrency dependence of starting heap, - aligns RSS behavior with GOMEMLIMIT, - adds a gcflags=-d=gcstart=N (N -> N MiB) flag for people who want to trade a lot of memory for a little build performance improvement. This removes concurrency (-c flag) sensitivity and increases the nominal default to 128MiB. Refactored the startheap code into a separate file, to make it easier to extract and reuse. Added sensitivity to concurrency=1 and GOMEMLIMIT!="" (in addition to existing GOGC!=""), those disable the default starting heap boost because the compiler-invoker has indicated either a desire to control the GC or a desire to run in minimum memory(or both). Adds a -d flag gcstart=N (N is number of MiB) for tinkering/experiments. This always enables the starting heap. (`GOGC=XXX` and `-d=gcstart=YYY` will use `GOGC=XXX` after starting heap size is achieved.) Derated the "boost" obtained by a factor of .70 so that `-d=gcstart=2000` yields the same RSS as `GOMEMLIMIT=2000MiB` (Actually adjusts the boost with a high-low breakpoint.) The parent, with concurrency sensitivity, provided 64MB of plain boost. Derating reduces the effects of boosting the starting heap slightly. The benchmark here shows that maintaining 64MB results in a minor regression, while increasing it to 128MB produces a slight improvement, and does not grow the RSS versus 64MB. ``` │ parent │ sh64 │ sh128 │ sh1024 │ │ sec/op │ sec/op vs base │ sec/op vs base │ sec/op vs base │ std 10.164 ± 1% 10.527 ± 1% +3.57% (p=0.000 n=50) 10.084 ± 1% -0.79% (p=0.000 n=50) 9.631 ± 1% -5.24% (p=0.000 n=50) compile 21.05 ± 1% 20.78 ± 0% -1.28% (p=0.000 n=50) 20.74 ± 1% -1.46% (p=0.000 n=50) 20.77 ± 0% -1.32% (p=0.001 n=50) ast 20.45 ± 1% 20.39 ± 1% ~ (p=0.334 n=50) 20.44 ± 0% ~ (p=0.818 n=50) 20.11 ± 1% -1.65% (p=0.000 n=50) geomean 16.35 16.46 +0.65% 16.23 -0.76% 15.90 -2.75% │ parent │ sh64 │ sh128 │ sh1024 │ │ user-sec/op │ user-sec/op vs base │ user-sec/op vs base │ user-sec/op vs base │ std 66.06 ± 0% 69.74 ± 0% +5.56% (p=0.000 n=50) 64.68 ± 0% -2.09% (p=0.000 n=50) 59.51 ± 0% -9.91% (p=0.000 n=50) compile 84.69 ± 1% 82.54 ± 0% -2.53% (p=0.000 n=50) 82.63 ± 0% -2.43% (p=0.000 n=50) 82.66 ± 1% -2.40% (p=0.000 n=50) ast 59.41 ± 0% 58.84 ± 1% -0.95% (p=0.011 n=50) 59.48 ± 1% ~ (p=0.341 n=50) 57.13 ± 1% -3.83% (p=0.000 n=50) geomean 69.27 69.71 +0.63% 68.25 -1.47% 65.50 -5.44% │ parent │ sh64 │ sh128 │ sh1024 │ │ sys-sec/op │ sys-sec/op vs base │ sys-sec/op vs base │ sys-sec/op vs base │ std 9.599 ± 1% 10.031 ± 1% +4.50% (p=0.000 n=50) 9.513 ± 1% -0.90% (p=0.014 n=50) 9.359 ± 1% -2.50% (p=0.000 n=50) compile 6.813 ± 1% 6.740 ± 1% -1.08% (p=0.017 n=50) 6.716 ± 1% -1.42% (p=0.006 n=50) 6.696 ± 1% -1.72% (p=0.000 n=50) ast 4.315 ± 1% 4.291 ± 1% ~ (p=0.781 n=50) 4.296 ± 1% ~ (p=0.792 n=50) 4.279 ± 2% ~ (p=0.124 n=50) geomean 6.559 6.620 +0.93% 6.499 -0.92% 6.449 -1.68% │ parent │ sh64 │ sh128 │ sh1024 │ │ peak-RSS-bytes │ peak-RSS-bytes vs base │ peak-RSS-bytes vs base │ peak-RSS-bytes vs base │ std 257.1Mi ± 1% 257.2Mi ± 1% ~ (p=0.754 n=50) 257.0Mi ± 0% ~ (p=0.570 n=50) 605.6Mi ± 0% +135.59% (p=0.000 n=50) compile 1007.2Mi ± 1% 1004.3Mi ± 0% ~ (p=0.064 n=50) 1007.4Mi ± 0% ~ (p=0.348 n=50) 1009.4Mi ± 1% ~ (p=0.598 n=50) ast 1.848Gi ± 0% 1.842Gi ± 0% ~ (p=0.079 n=50) 1.824Gi ± 0% -1.25% (p=0.000 n=50) 1.856Gi ± 0% +0.47% (p=0.000 n=50) geomean 788.3Mi 786.8Mi -0.19% 785.0Mi -0.41% 1.027Gi +33.37% ``` Updates #73044 Change-Id: I6359642a94b396e696dd57e64ed1f2c4cf178475 Reviewed-on: https://go-review.googlesource.com/c/go/+/724441 Reviewed-by: Keith Randall Reviewed-by: Keith Randall LUCI-TryBot-Result: Go LUCI --- src/cmd/compile/internal/base/base.go | 194 --------------- src/cmd/compile/internal/base/debug.go | 1 + src/cmd/compile/internal/base/startheap.go | 272 +++++++++++++++++++++ src/cmd/compile/internal/gc/main.go | 11 +- 4 files changed, 280 insertions(+), 198 deletions(-) create mode 100644 src/cmd/compile/internal/base/startheap.go diff --git a/src/cmd/compile/internal/base/base.go b/src/cmd/compile/internal/base/base.go index ee3772c5ca2..405c7938a51 100644 --- a/src/cmd/compile/internal/base/base.go +++ b/src/cmd/compile/internal/base/base.go @@ -5,11 +5,7 @@ package base import ( - "fmt" "os" - "runtime" - "runtime/debug" - "runtime/metrics" ) var atExitFuncs []func() @@ -29,193 +25,3 @@ func Exit(code int) { // To enable tracing support (-t flag), set EnableTrace to true. const EnableTrace = false - -// forEachGC calls fn each GC cycle until it returns false. -func forEachGC(fn func() bool) { - type T [32]byte // large enough to avoid runtime's tiny object allocator - - var finalizer func(*T) - finalizer = func(p *T) { - if fn() { - runtime.SetFinalizer(p, finalizer) - } - } - - finalizer(new(T)) -} - -// AdjustStartingHeap modifies GOGC so that GC should not occur until the heap -// grows to the requested size. This is intended but not promised, though it -// is true-mostly, depending on when the adjustment occurs and on the -// compiler's input and behavior. Once this size is approximately reached -// GOGC is reset to 100; subsequent GCs may reduce the heap below the requested -// size, but this function does not affect that. -// -// -d=gcadjust=1 enables logging of GOGC adjustment events. -// -// NOTE: If you think this code would help startup time in your own -// application and you decide to use it, please benchmark first to see if it -// actually works for you (it may not: the Go compiler is not typical), and -// whatever the outcome, please leave a comment on bug #56546. This code -// uses supported interfaces, but depends more than we like on -// current+observed behavior of the garbage collector, so if many people need -// this feature, we should consider/propose a better way to accomplish it. -func AdjustStartingHeap(requestedHeapGoal uint64) { - logHeapTweaks := Debug.GCAdjust == 1 - mp := runtime.GOMAXPROCS(0) - gcConcurrency := Flag.LowerC - - const ( - goal = "/gc/heap/goal:bytes" - count = "/gc/cycles/total:gc-cycles" - allocs = "/gc/heap/allocs:bytes" - frees = "/gc/heap/frees:bytes" - ) - - sample := []metrics.Sample{{Name: goal}, {Name: count}, {Name: allocs}, {Name: frees}} - const ( - GOAL = 0 - COUNT = 1 - ALLOCS = 2 - FREES = 3 - ) - - // Assumptions and observations of Go's garbage collector, as of Go 1.17-1.20: - - // - the initial heap goal is 4M, by fiat. It is possible for Go to start - // with a heap as small as 512k, so this may change in the future. - - // - except for the first heap goal, heap goal is a function of - // observed-live at the previous GC and current GOGC. After the first - // GC, adjusting GOGC immediately updates GOGC; before the first GC, - // adjusting GOGC does not modify goal (but the change takes effect after - // the first GC). - - // - the before/after first GC behavior is not guaranteed anywhere, it's - // just behavior, and it's a bad idea to rely on it. - - // - we don't know exactly when GC will run, even after we adjust GOGC; the - // first GC may not have happened yet, may have already happened, or may - // be currently in progress, and GCs can start for several reasons. - - // - forEachGC above will run the provided function at some delay after each - // GC's mark phase terminates; finalizers are run after marking as the - // spans containing finalizable objects are swept, driven by GC - // background activity and allocation demand. - - // - "live at last GC" is not available through the current metrics - // interface. Instead, live is estimated by knowing the adjusted value of - // GOGC and the new heap goal following a GC (this requires knowing that - // at least one GC has occurred): - // estLive = 100 * newGoal / (100 + currentGogc) - // this new value of GOGC - // newGogc = 100*requestedHeapGoal/estLive - 100 - // will result in the desired goal. The logging code checks that the - // resulting goal is correct. - - // There's a small risk that the finalizer will be slow to run after a GC - // that expands the goal to a huge value, and that this will lead to - // out-of-memory. This doesn't seem to happen; in experiments on a variety - // of machines with a variety of extra loads to disrupt scheduling, the - // worst overshoot observed was 50% past requestedHeapGoal. - - metrics.Read(sample) - for _, s := range sample { - if s.Value.Kind() == metrics.KindBad { - // Just return, a slightly slower compilation is a tolerable outcome. - if logHeapTweaks { - fmt.Fprintf(os.Stderr, "GCAdjust: Regret unexpected KindBad for metric %s\n", s.Name) - } - return - } - } - - // Tinker with GOGC to make the heap grow rapidly at first. - currentGoal := sample[GOAL].Value.Uint64() // Believe this will be 4MByte or less, perhaps 512k - myGogc := 100 * requestedHeapGoal / currentGoal - if myGogc <= 150 { - return - } - - if logHeapTweaks { - sample := append([]metrics.Sample(nil), sample...) // avoid races with GC callback - AtExit(func() { - metrics.Read(sample) - goal := sample[GOAL].Value.Uint64() - count := sample[COUNT].Value.Uint64() - oldGogc := debug.SetGCPercent(100) - if oldGogc == 100 { - fmt.Fprintf(os.Stderr, "GCAdjust: AtExit goal %d gogc %d count %d maxprocs %d gcConcurrency %d\n", - goal, oldGogc, count, mp, gcConcurrency) - } else { - inUse := sample[ALLOCS].Value.Uint64() - sample[FREES].Value.Uint64() - overPct := 100 * (int(inUse) - int(requestedHeapGoal)) / int(requestedHeapGoal) - fmt.Fprintf(os.Stderr, "GCAdjust: AtExit goal %d gogc %d count %d maxprocs %d gcConcurrency %d overPct %d\n", - goal, oldGogc, count, mp, gcConcurrency, overPct) - - } - }) - } - - debug.SetGCPercent(int(myGogc)) - - adjustFunc := func() bool { - - metrics.Read(sample) - goal := sample[GOAL].Value.Uint64() - count := sample[COUNT].Value.Uint64() - - if goal <= requestedHeapGoal { // Stay the course - if logHeapTweaks { - fmt.Fprintf(os.Stderr, "GCAdjust: Reuse GOGC adjust, current goal %d, count is %d, current gogc %d\n", - goal, count, myGogc) - } - return true - } - - // Believe goal has been adjusted upwards, else it would be less-than-or-equal than requestedHeapGoal - calcLive := 100 * goal / (100 + myGogc) - - if 2*calcLive < requestedHeapGoal { // calcLive can exceed requestedHeapGoal! - myGogc = 100*requestedHeapGoal/calcLive - 100 - - if myGogc > 125 { - // Not done growing the heap. - oldGogc := debug.SetGCPercent(int(myGogc)) - - if logHeapTweaks { - // Check that the new goal looks right - inUse := sample[ALLOCS].Value.Uint64() - sample[FREES].Value.Uint64() - metrics.Read(sample) - newGoal := sample[GOAL].Value.Uint64() - pctOff := 100 * (int64(newGoal) - int64(requestedHeapGoal)) / int64(requestedHeapGoal) - // Check that the new goal is close to requested. 3% of make.bash fails this test. Why, TBD. - if pctOff < 2 { - fmt.Fprintf(os.Stderr, "GCAdjust: Retry GOGC adjust, current goal %d, count is %d, gogc was %d, is now %d, calcLive %d pctOff %d\n", - goal, count, oldGogc, myGogc, calcLive, pctOff) - } else { - // The GC is being annoying and not giving us the goal that we requested, say more to help understand when/why. - fmt.Fprintf(os.Stderr, "GCAdjust: Retry GOGC adjust, current goal %d, count is %d, gogc was %d, is now %d, calcLive %d pctOff %d inUse %d\n", - goal, count, oldGogc, myGogc, calcLive, pctOff, inUse) - } - } - return true - } - } - - // In this case we're done boosting GOGC, set it to 100 and don't set a new finalizer. - oldGogc := debug.SetGCPercent(100) - // inUse helps estimate how late the finalizer ran; at the instant the previous GC ended, - // it was (in theory) equal to the previous GC's heap goal. In a growing heap it is - // expected to grow to the new heap goal. - inUse := sample[ALLOCS].Value.Uint64() - sample[FREES].Value.Uint64() - overPct := 100 * (int(inUse) - int(requestedHeapGoal)) / int(requestedHeapGoal) - if logHeapTweaks { - fmt.Fprintf(os.Stderr, "GCAdjust: Reset GOGC adjust, old goal %d, count is %d, gogc was %d, calcLive %d inUse %d overPct %d\n", - goal, count, oldGogc, calcLive, inUse, overPct) - } - return false - } - - forEachGC(adjustFunc) -} diff --git a/src/cmd/compile/internal/base/debug.go b/src/cmd/compile/internal/base/debug.go index b532bf435e8..e32a07d4617 100644 --- a/src/cmd/compile/internal/base/debug.go +++ b/src/cmd/compile/internal/base/debug.go @@ -38,6 +38,7 @@ type DebugFlags struct { GCAdjust int `help:"log adjustments to GOGC" concurrent:"ok"` GCCheck int `help:"check heap/gc use by compiler" concurrent:"ok"` GCProg int `help:"print dump of GC programs"` + GCStart int `help:"specify \"starting\" compiler's heap size in MiB" concurrent:"ok"` Gossahash string `help:"hash value for use in debugging the compiler"` InlFuncsWithClosures int `help:"allow functions with closures to be inlined" concurrent:"ok"` InlStaticInit int `help:"allow static initialization of inlined calls" concurrent:"ok"` diff --git a/src/cmd/compile/internal/base/startheap.go b/src/cmd/compile/internal/base/startheap.go new file mode 100644 index 00000000000..1d2713efdbc --- /dev/null +++ b/src/cmd/compile/internal/base/startheap.go @@ -0,0 +1,272 @@ +// Copyright 2025 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 base + +import ( + "fmt" + "os" + "runtime" + "runtime/debug" + "runtime/metrics" + "sync" +) + +// forEachGC calls fn each GC cycle until it returns false. +func forEachGC(fn func() bool) { + type T [32]byte // large enough to avoid runtime's tiny object allocator + var finalizer func(*T) + finalizer = func(p *T) { + + if fn() { + runtime.SetFinalizer(p, finalizer) + } + } + + finalizer(new(T)) +} + +// AdjustStartingHeap modifies GOGC so that GC should not occur until the heap +// grows to the requested size. This is intended but not promised, though it +// is true-mostly, depending on when the adjustment occurs and on the +// compiler's input and behavior. Once the live heap is approximately half +// this size, GOGC is reset to its value when AdjustStartingHeap was called; +// subsequent GCs may reduce the heap below the requested size, but this +// function does not affect that. +// +// logHeapTweaks (-d=gcadjust=1) enables logging of GOGC adjustment events. +// +// The temporarily requested GOGC is derated from what would be the "obvious" +// value necessary to hit the starting heap goal because the obvious +// (goal/live-1)*100 value seems to grow RSS a little more than it "should" +// (compared to GOMEMLIMIT, e.g.) and the assumption is that the GC's control +// algorithms are tuned for GOGC near 100, and not tuned for huge values of +// GOGC. Different derating factors apply for "lo" and "hi" values of GOGC; +// lo is below derateBreak, hi is above derateBreak. The derating factors, +// expressed as integer percentages, are derateLoPct and derateHiPct. +// 60-75 is an okay value for derateLoPct, 30-65 seems like a good value for +// derateHiPct, and 600 seems like a good value for derateBreak. If these +// are zero, defaults are used instead. +// +// NOTE: If you think this code would help startup time in your own +// application and you decide to use it, please benchmark first to see if it +// actually works for you (it may not: the Go compiler is not typical), and +// whatever the outcome, please leave a comment on bug #56546. This code +// uses supported interfaces, but depends more than we like on +// current+observed behavior of the garbage collector, so if many people need +// this feature, we should consider/propose a better way to accomplish it. +func AdjustStartingHeap(requestedHeapGoal, derateBreak, derateLoPct, derateHiPct uint64, logHeapTweaks bool) { + mp := runtime.GOMAXPROCS(0) + + const ( + SHgoal = "/gc/heap/goal:bytes" + SHcount = "/gc/cycles/total:gc-cycles" + SHallocs = "/gc/heap/allocs:bytes" + SHfrees = "/gc/heap/frees:bytes" + ) + + var sample = []metrics.Sample{{Name: SHgoal}, {Name: SHcount}, {Name: SHallocs}, {Name: SHfrees}} + + const ( + SH_GOAL = 0 + SH_COUNT = 1 + SH_ALLOCS = 2 + SH_FREES = 3 + + MB = 1_000_000 + ) + + // These particular magic numbers are designed to make the RSS footprint of -d=-gcstart=2000 + // resemble that of GOMEMLIMIT=2000MiB GOGC=10000 when building large projects + // (e.g. the Go compiler itself, and the microsoft's typescript AST package), + // with the further restriction that these magic numbers did a good job of reducing user-cpu + // for builds at either gcstart=2000 or gcstart=128. + // + // The benchmarking to obtain this was (a version of): + // + // for i in {1..50} ; do + // for what in std cmd/compile cmd/fix cmd/go github.com/microsoft/typescript-go/internal/ast ; do + // whatbase=`basename ${what}` + // for sh in 128 2000 ; do + // for br in 500 600 ; do + // for shlo in 65 70; do + // for shhi in 55 60 ; do + // benchcmd -n=2 ${whatbase} go build -a \ + // -gcflags=all=-d=gcstart=${sh},gcstartloderate=${shlo},gcstarthiderate=${shhi},gcstartbreak=${br} \ + // ${what} | tee -a startheap${sh}_${br}_${shhi}_${shlo}.bench + // done + // done + // done + // done + // done + // done + // + // benchcmd is "go install github.com/aclements/go-misc/benchcmd@latest" + + if derateBreak == 0 { + derateBreak = 600 + } + if derateLoPct == 0 { + derateLoPct = 70 + } + if derateHiPct == 0 { + derateHiPct = 55 + } + + gogcDerate := func(myGogc uint64) uint64 { + if myGogc < derateBreak { + return (myGogc * derateLoPct) / 100 + } + return (myGogc * derateHiPct) / 100 + } + + // Assumptions and observations of Go's garbage collector, as of Go 1.17-1.20: + + // - the initial heap goal is 4MiB, by fiat. It is possible for Go to start + // with a heap as small as 512k, so this may change in the future. + + // - except for the first heap goal, heap goal is a function of + // observed-live at the previous GC and current GOGC. After the first + // GC, adjusting GOGC immediately updates GOGC; before the first GC, + // adjusting GOGC does not modify goal (but the change takes effect after + // the first GC). + + // - the before/after first GC behavior is not guaranteed anywhere, it's + // just behavior, and it's a bad idea to rely on it. + + // - we don't know exactly when GC will run, even after we adjust GOGC; the + // first GC may not have happened yet, may have already happened, or may + // be currently in progress, and GCs can start for several reasons. + + // - forEachGC above will run the provided function at some delay after each + // GC's mark phase terminates; finalizers are run after marking as the + // spans containing finalizable objects are swept, driven by GC + // background activity and allocation demand. + + // - "live at last GC" is not available through the current metrics + // interface. Instead, live is estimated by knowing the adjusted value of + // GOGC and the new heap goal following a GC (this requires knowing that + // at least one GC has occurred): + // estLive = 100 * newGoal / (100 + currentGogc) + // this new value of GOGC + // newGogc = 100*requestedHeapGoal/estLive - 100 + // will result in the desired goal. The logging code checks that the + // resulting goal is correct. + + // There's a small risk that the finalizer will be slow to run after a GC + // that expands the goal to a huge value, and that this will lead to + // out-of-memory. This doesn't seem to happen; in experiments on a variety + // of machines with a variety of extra loads to disrupt scheduling, the + // worst overshoot observed was 50% past requestedHeapGoal. + + metrics.Read(sample) + for _, s := range sample { + if s.Value.Kind() == metrics.KindBad { + // Just return, a slightly slower compilation is a tolerable outcome. + if logHeapTweaks { + fmt.Fprintf(os.Stderr, "GCAdjust: Regret unexpected KindBad for metric %s\n", s.Name) + } + return + } + } + + // Tinker with GOGC to make the heap grow rapidly at first. + currentGoal := sample[SH_GOAL].Value.Uint64() // Believe this will be 4MByte or less, perhaps 512k + myGogc := 100 * requestedHeapGoal / currentGoal + myGogc = gogcDerate(myGogc) + if myGogc <= 125 { + return + } + + if logHeapTweaks { + sample := append([]metrics.Sample(nil), sample...) // avoid races with GC callback + AtExit(func() { + metrics.Read(sample) + goal := sample[SH_GOAL].Value.Uint64() + count := sample[SH_COUNT].Value.Uint64() + oldGogc := debug.SetGCPercent(100) + if oldGogc == 100 { + fmt.Fprintf(os.Stderr, "GCAdjust: AtExit goal %dMB gogc %d count %d maxprocs %d\n", + goal/MB, oldGogc, count, mp) + } else { + inUse := sample[SH_ALLOCS].Value.Uint64() - sample[SH_FREES].Value.Uint64() + overPct := 100 * (int(inUse) - int(requestedHeapGoal)) / int(requestedHeapGoal) + fmt.Fprintf(os.Stderr, "GCAdjust: AtExit goal %dMB gogc %d count %d maxprocs %d overPct %d\n", + goal/MB, oldGogc, count, mp, overPct) + + } + }) + } + + originalGOGC := debug.SetGCPercent(int(myGogc)) + + // forEachGC finalizers ought not overlap, but they could run in separate threads. + // This ought not matter, but just in case it bothers the/a race detector, + // use this mutex. + var forEachGCLock sync.Mutex + + adjustFunc := func() bool { + + forEachGCLock.Lock() + defer forEachGCLock.Unlock() + + metrics.Read(sample) + goal := sample[SH_GOAL].Value.Uint64() + count := sample[SH_COUNT].Value.Uint64() + + if goal <= requestedHeapGoal { // Stay the course + if logHeapTweaks { + fmt.Fprintf(os.Stderr, "GCAdjust: Reuse GOGC adjust, current goal %dMB, count is %d, current gogc %d\n", + goal/MB, count, myGogc) + } + return true + } + + // Believe goal has been adjusted upwards, else it would be less-than-or-equal to requestedHeapGoal + calcLive := 100 * goal / (100 + myGogc) + + if 2*calcLive < requestedHeapGoal { // calcLive can exceed requestedHeapGoal! + myGogc = 100*requestedHeapGoal/calcLive - 100 + myGogc = gogcDerate(myGogc) + + if myGogc > 125 { + // Not done growing the heap. + oldGogc := debug.SetGCPercent(int(myGogc)) + + if logHeapTweaks { + // Check that the new goal looks right + inUse := sample[SH_ALLOCS].Value.Uint64() - sample[SH_FREES].Value.Uint64() + metrics.Read(sample) + newGoal := sample[SH_GOAL].Value.Uint64() + pctOff := 100 * (int64(newGoal) - int64(requestedHeapGoal)) / int64(requestedHeapGoal) + // Check that the new goal is close to requested. 3% of make.bash fails this test. Why, TBD. + if pctOff < 2 { + fmt.Fprintf(os.Stderr, "GCAdjust: Retry GOGC adjust, current goal %dMB, count is %d, gogc was %d, is now %d, calcLive %dMB pctOff %d\n", + goal/MB, count, oldGogc, myGogc, calcLive/MB, pctOff) + } else { + // The GC is being annoying and not giving us the goal that we requested, say more to help understand when/why. + fmt.Fprintf(os.Stderr, "GCAdjust: Retry GOGC adjust, current goal %dMB, count is %d, gogc was %d, is now %d, calcLive %dMB pctOff %d inUse %dMB\n", + goal/MB, count, oldGogc, myGogc, calcLive/MB, pctOff, inUse/MB) + } + } + return true + } + } + + // In this case we're done boosting GOGC, set it to its original value and don't set a new finalizer. + oldGogc := debug.SetGCPercent(originalGOGC) + // inUse helps estimate how late the finalizer ran; at the instant the previous GC ended, + // it was (in theory) equal to the previous GC's heap goal. In a growing heap it is + // expected to grow to the new heap goal. + if logHeapTweaks { + inUse := sample[SH_ALLOCS].Value.Uint64() - sample[SH_FREES].Value.Uint64() + overPct := 100 * (int(inUse) - int(requestedHeapGoal)) / int(requestedHeapGoal) + fmt.Fprintf(os.Stderr, "GCAdjust: Reset GOGC adjust, old goal %dMB, count is %d, gogc was %d, gogc is now %d, calcLive %dMB inUse %dMB overPct %d\n", + goal/MB, count, oldGogc, originalGOGC, calcLive/MB, inUse/MB, overPct) + } + return false + } + + forEachGC(adjustFunc) +} diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index 2a3a3f786b3..afa16508d19 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -83,10 +83,13 @@ func Main(archInit func(*ssagen.ArchInfo)) { base.DebugSSA = ssa.PhaseOption base.ParseFlags() - if os.Getenv("GOGC") == "" { // GOGC set disables starting heap adjustment - // More processors will use more heap, but assume that more memory is available. - // So 1 processor -> 40MB, 4 -> 64MB, 12 -> 128MB - base.AdjustStartingHeap(uint64(32+8*base.Flag.LowerC) << 20) + if flagGCStart := base.Debug.GCStart; flagGCStart > 0 || // explicit flags overrides environment variable disable of GC boost + os.Getenv("GOGC") == "" && os.Getenv("GOMEMLIMIT") == "" && base.Flag.LowerC != 1 { // explicit GC knobs or no concurrency implies default heap + startHeapMB := int64(128) + if flagGCStart > 0 { + startHeapMB = int64(flagGCStart) + } + base.AdjustStartingHeap(uint64(startHeapMB)<<20, 0, 0, 0, base.Debug.GCAdjust == 1) } types.LocalPkg = types.NewPkg(base.Ctxt.Pkgpath, "") From c1ef3d588126f5ed96112f8392db3e63c710b5af Mon Sep 17 00:00:00 2001 From: Ian Alexander Date: Mon, 24 Nov 2025 18:36:07 -0500 Subject: [PATCH 052/140] cmd/go/internal/modcmd: remove references to modfetch.Fetcher_ This commit removes references to the global modfetch.Fetcher_ variable from the modcmd package. Change-Id: Ie2966401d1f6964e21ddede65d39ff53fea6e867 Reviewed-on: https://go-review.googlesource.com/c/go/+/724245 LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Matloob Reviewed-by: Michael Matloob --- src/cmd/go/internal/modcmd/download.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/cmd/go/internal/modcmd/download.go b/src/cmd/go/internal/modcmd/download.go index 8b94ba1fd57..661cddcd79d 100644 --- a/src/cmd/go/internal/modcmd/download.go +++ b/src/cmd/go/internal/modcmd/download.go @@ -264,7 +264,7 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) { } sem <- token{} go func() { - err := DownloadModule(ctx, m) + err := DownloadModule(ctx, moduleLoaderState.Fetcher(), m) if err != nil { downloadErrs.Store(m, err) m.Error = err.Error() @@ -364,27 +364,27 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) { // DownloadModule runs 'go mod download' for m.Path@m.Version, // leaving the results (including any error) in m itself. -func DownloadModule(ctx context.Context, m *ModuleJSON) error { +func DownloadModule(ctx context.Context, f *modfetch.Fetcher, m *ModuleJSON) error { var err error - _, file, err := modfetch.Fetcher_.InfoFile(ctx, m.Path, m.Version) + _, file, err := f.InfoFile(ctx, m.Path, m.Version) if err != nil { return err } m.Info = file - m.GoMod, err = modfetch.Fetcher_.GoModFile(ctx, m.Path, m.Version) + m.GoMod, err = f.GoModFile(ctx, m.Path, m.Version) if err != nil { return err } - m.GoModSum, err = modfetch.Fetcher_.GoModSum(ctx, m.Path, m.Version) + m.GoModSum, err = f.GoModSum(ctx, m.Path, m.Version) if err != nil { return err } mod := module.Version{Path: m.Path, Version: m.Version} - m.Zip, err = modfetch.Fetcher_.DownloadZip(ctx, mod) + m.Zip, err = f.DownloadZip(ctx, mod) if err != nil { return err } m.Sum = modfetch.Sum(ctx, mod) - m.Dir, err = modfetch.Fetcher_.Download(ctx, mod) + m.Dir, err = f.Download(ctx, mod) return err } From a3a6c9f62ae5337b6563c941eee85ca9efcacc58 Mon Sep 17 00:00:00 2001 From: Ian Alexander Date: Mon, 24 Nov 2025 18:38:13 -0500 Subject: [PATCH 053/140] cmd/go/internal/load: remove references to modfetch.Fetcher_ This commit removes references to the global modfetch.Fetcher_ variable from the load package. Change-Id: Ic579743079252afd4d2d12e5118de09d86340267 Reviewed-on: https://go-review.googlesource.com/c/go/+/724246 LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Matloob Reviewed-by: Michael Matloob --- src/cmd/go/internal/load/pkg.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index 705f4aa6a98..91602aa5bff 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -7,7 +7,6 @@ package load import ( "bytes" - "cmd/internal/objabi" "context" "encoding/json" "errors" @@ -31,6 +30,8 @@ import ( "unicode" "unicode/utf8" + "cmd/internal/objabi" + "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/fips140" @@ -3416,7 +3417,7 @@ func PackagesAndErrorsOutsideModule(loaderstate *modload.State, ctx context.Cont if deprecation != "" { fmt.Fprintf(os.Stderr, "go: module %s is deprecated: %s\n", rootMod.Path, modload.ShortMessage(deprecation, "")) } - data, err := modfetch.Fetcher_.GoMod(ctx, rootMod.Path, rootMod.Version) + data, err := loaderstate.Fetcher().GoMod(ctx, rootMod.Path, rootMod.Version) if err != nil { return nil, fmt.Errorf("%s: %w", args[0], err) } From 46d5e3ea0e030a8bd01d611f0cb24bdd3db66cef Mon Sep 17 00:00:00 2001 From: Ian Alexander Date: Mon, 24 Nov 2025 18:39:53 -0500 Subject: [PATCH 054/140] cmd/go/internal/modget: remove references to modfetch.Fetcher_ This commit removes references to the global modfetch.Fetcher_ variable from the modget package. Change-Id: I62dfcc0e9cf9722a6706bbdf7b6e561130ed82d1 Reviewed-on: https://go-review.googlesource.com/c/go/+/724247 LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Matloob Reviewed-by: Michael Matloob --- src/cmd/go/internal/modget/get.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go index d2458eba9bc..b731ccc047d 100644 --- a/src/cmd/go/internal/modget/get.go +++ b/src/cmd/go/internal/modget/get.go @@ -1788,11 +1788,11 @@ func (r *resolver) checkPackageProblems(loaderstate *modload.State, ctx context. if oldRepl := modload.Replacement(loaderstate, old); oldRepl.Path != "" { oldActual = oldRepl } - if mActual == oldActual || mActual.Version == "" || !modfetch.HaveSum(modfetch.Fetcher_, oldActual) { + if mActual == oldActual || mActual.Version == "" || !modfetch.HaveSum(loaderstate.Fetcher(), oldActual) { continue } r.work.Add(func() { - if _, err := modfetch.Fetcher_.DownloadZip(ctx, mActual); err != nil { + if _, err := loaderstate.Fetcher().DownloadZip(ctx, mActual); err != nil { verb := "upgraded" if gover.ModCompare(m.Path, m.Version, old.Version) < 0 { verb = "downgraded" From 08bf23cb977c9a8620efed6ac841996280d30f3a Mon Sep 17 00:00:00 2001 From: Ian Alexander Date: Mon, 24 Nov 2025 18:48:32 -0500 Subject: [PATCH 055/140] cmd/go/internal/toolchain: remove references to modfetch.Fetcher_ This commit removes references to the global modfetch.Fetcher_ variable from the toolchain package. Change-Id: Id366bec88e5904098b90371ec103f92d402174d5 Reviewed-on: https://go-review.googlesource.com/c/go/+/724248 Reviewed-by: Michael Matloob Reviewed-by: Michael Matloob LUCI-TryBot-Result: Go LUCI --- src/cmd/go/internal/toolchain/select.go | 13 +++++++------ src/cmd/go/internal/toolchain/switch.go | 13 ++++++++----- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/cmd/go/internal/toolchain/select.go b/src/cmd/go/internal/toolchain/select.go index c27bcb5bbdd..192fb62fc26 100644 --- a/src/cmd/go/internal/toolchain/select.go +++ b/src/cmd/go/internal/toolchain/select.go @@ -25,7 +25,6 @@ import ( "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/gover" - "cmd/go/internal/modfetch" "cmd/go/internal/modload" "cmd/go/internal/run" "cmd/go/internal/work" @@ -86,8 +85,10 @@ func FilterEnv(env []string) []string { return out } -var counterErrorsInvalidToolchainInFile = counter.New("go/errors:invalid-toolchain-in-file") -var toolchainTrace = godebug.New("#toolchaintrace").Value() == "1" +var ( + counterErrorsInvalidToolchainInFile = counter.New("go/errors:invalid-toolchain-in-file") + toolchainTrace = godebug.New("#toolchaintrace").Value() == "1" +) // Select invokes a different Go toolchain if directed by // the GOTOOLCHAIN environment variable or the user's configuration @@ -360,7 +361,7 @@ func Exec(s *modload.State, gotoolchain string) { Path: gotoolchainModule, Version: gotoolchainVersion + "-" + gotoolchain + "." + runtime.GOOS + "-" + runtime.GOARCH, } - dir, err := modfetch.Fetcher_.Download(context.Background(), m) + dir, err := s.Fetcher().Download(context.Background(), m) if err != nil { if errors.Is(err, fs.ErrNotExist) { toolVers := gover.FromToolchain(gotoolchain) @@ -380,7 +381,7 @@ func Exec(s *modload.State, gotoolchain string) { if err != nil { base.Fatalf("download %s: %v", gotoolchain, err) } - if info.Mode()&0111 == 0 { + if info.Mode()&0o111 == 0 { // allowExec sets the exec permission bits on all files found in dir if pattern is the empty string, // or only those files that match the pattern if it's non-empty. allowExec := func(dir, pattern string) { @@ -399,7 +400,7 @@ func Exec(s *modload.State, gotoolchain string) { if err != nil { return err } - if err := os.Chmod(path, info.Mode()&0777|0111); err != nil { + if err := os.Chmod(path, info.Mode()&0o777|0o111); err != nil { return err } } diff --git a/src/cmd/go/internal/toolchain/switch.go b/src/cmd/go/internal/toolchain/switch.go index 4ddc28a0e90..ff4fce03074 100644 --- a/src/cmd/go/internal/toolchain/switch.go +++ b/src/cmd/go/internal/toolchain/switch.go @@ -97,7 +97,7 @@ func (s *Switcher) Switch(ctx context.Context) { } // Switch to newer Go toolchain if necessary and possible. - tv, err := NewerToolchain(ctx, s.TooNew.GoVersion) + tv, err := NewerToolchain(ctx, s.loaderstate.Fetcher(), s.TooNew.GoVersion) if err != nil { for _, err := range s.Errors { base.Error(err) @@ -130,8 +130,11 @@ func SwitchOrFatal(loaderstate *modload.State, ctx context.Context, err error) { // If the latest major release is 1.N.0, we use the latest patch release of 1.(N-1) if that's >= version. // Otherwise we use the latest 1.N if that's allowed. // Otherwise we use the latest release. -func NewerToolchain(ctx context.Context, version string) (string, error) { - fetch := autoToolchains +func NewerToolchain(ctx context.Context, f *modfetch.Fetcher, version string) (string, error) { + fetch := func(ctx context.Context) ([]string, error) { + return autoToolchains(ctx, f) + } + if !HasAuto() { fetch = pathToolchains } @@ -143,10 +146,10 @@ func NewerToolchain(ctx context.Context, version string) (string, error) { } // autoToolchains returns the list of toolchain versions available to GOTOOLCHAIN=auto or =min+auto mode. -func autoToolchains(ctx context.Context) ([]string, error) { +func autoToolchains(ctx context.Context, f *modfetch.Fetcher) ([]string, error) { var versions *modfetch.Versions err := modfetch.TryProxies(func(proxy string) error { - v, err := modfetch.Fetcher_.Lookup(ctx, proxy, "go").Versions(ctx, "") + v, err := f.Lookup(ctx, proxy, "go").Versions(ctx, "") if err != nil { return err } From 4976606a2f9203f4520c28d775e67fc32f5853ed Mon Sep 17 00:00:00 2001 From: Ian Alexander Date: Mon, 24 Nov 2025 20:24:51 -0500 Subject: [PATCH 056/140] cmd/go: remove final references to modfetch.Fetcher_ This commit removes the final references to the global Fetcher_ variable from the modfetch and modload packages. This completes the removal of global state from the modfetch package. Change-Id: Ibb5309acdc7d05f1a7591ddcf890b44b6cc4cb2f Reviewed-on: https://go-review.googlesource.com/c/go/+/724249 Reviewed-by: Michael Matloob Reviewed-by: Michael Matloob LUCI-TryBot-Result: Go LUCI --- src/cmd/go/internal/load/pkg.go | 6 +- src/cmd/go/internal/load/test.go | 2 +- src/cmd/go/internal/modfetch/cache.go | 19 ++--- src/cmd/go/internal/modfetch/coderepo_test.go | 14 ++-- src/cmd/go/internal/modfetch/fetch.go | 72 +++++++++---------- src/cmd/go/internal/modfetch/repo.go | 6 +- src/cmd/go/internal/modload/build.go | 4 +- src/cmd/go/internal/modload/init.go | 7 +- src/cmd/go/internal/modload/load.go | 6 +- 9 files changed, 67 insertions(+), 69 deletions(-) diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index 91602aa5bff..e2a77d7d7df 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -2050,7 +2050,7 @@ func (p *Package) load(loaderstate *modload.State, ctx context.Context, opts Pac // Consider starting this as a background goroutine and retrieving the result // asynchronously when we're actually ready to build the package, or when we // actually need to evaluate whether the package's metadata is stale. - p.setBuildInfo(ctx, opts.AutoVCS) + p.setBuildInfo(ctx, loaderstate.Fetcher(), opts.AutoVCS) } // If cgo is not enabled, ignore cgo supporting sources @@ -2323,7 +2323,7 @@ func appendBuildSetting(info *debug.BuildInfo, key, value string) { // // Note that the GoVersion field is not set here to avoid encoding it twice. // It is stored separately in the binary, mostly for historical reasons. -func (p *Package) setBuildInfo(ctx context.Context, autoVCS bool) { +func (p *Package) setBuildInfo(ctx context.Context, f *modfetch.Fetcher, autoVCS bool) { setPkgErrorf := func(format string, args ...any) { if p.Error == nil { p.Error = &PackageError{Err: fmt.Errorf(format, args...)} @@ -2595,7 +2595,7 @@ func (p *Package) setBuildInfo(ctx context.Context, autoVCS bool) { if !ok { goto omitVCS } - repo := modfetch.LookupLocal(ctx, codeRoot, p.Module.Path, repoDir) + repo := f.LookupLocal(ctx, codeRoot, p.Module.Path, repoDir) revInfo, err := repo.Stat(ctx, st.Revision) if err != nil { goto omitVCS diff --git a/src/cmd/go/internal/load/test.go b/src/cmd/go/internal/load/test.go index c7c58fd5487..e5c074fa193 100644 --- a/src/cmd/go/internal/load/test.go +++ b/src/cmd/go/internal/load/test.go @@ -301,7 +301,7 @@ func TestPackagesAndErrors(loaderstate *modload.State, ctx context.Context, done // pmain won't have buildinfo set (since we copy it from the package under test). If the default GODEBUG // used for the package under test is different from that of the test main, the BuildInfo assigned above from the package // under test incorrect for the test main package. Either set or correct pmain's build info. - pmain.setBuildInfo(ctx, opts.AutoVCS) + pmain.setBuildInfo(ctx, loaderstate.Fetcher(), opts.AutoVCS) } // The generated main also imports testing, regexp, and os. diff --git a/src/cmd/go/internal/modfetch/cache.go b/src/cmd/go/internal/modfetch/cache.go index 3886d3b1feb..f035c359b13 100644 --- a/src/cmd/go/internal/modfetch/cache.go +++ b/src/cmd/go/internal/modfetch/cache.go @@ -156,7 +156,7 @@ func lockVersion(ctx context.Context, mod module.Version) (unlock func(), err er if err != nil { return nil, err } - if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil { + if err := os.MkdirAll(filepath.Dir(path), 0o777); err != nil { return nil, err } return lockedfile.MutexAt(path).Lock() @@ -172,7 +172,7 @@ func SideLock(ctx context.Context) (unlock func(), err error) { } path := filepath.Join(cfg.GOMODCACHE, "cache", "lock") - if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil { + if err := os.MkdirAll(filepath.Dir(path), 0o777); err != nil { return nil, fmt.Errorf("failed to create cache directory: %w", err) } @@ -194,12 +194,14 @@ type cachingRepo struct { once sync.Once initRepo func(context.Context) (Repo, error) r Repo + fetcher *Fetcher } -func newCachingRepo(ctx context.Context, path string, initRepo func(context.Context) (Repo, error)) *cachingRepo { +func newCachingRepo(ctx context.Context, fetcher *Fetcher, path string, initRepo func(context.Context) (Repo, error)) *cachingRepo { return &cachingRepo{ path: path, initRepo: initRepo, + fetcher: fetcher, } } @@ -226,7 +228,6 @@ func (r *cachingRepo) Versions(ctx context.Context, prefix string) (*Versions, e v, err := r.versionsCache.Do(prefix, func() (*Versions, error) { return r.repo(ctx).Versions(ctx, prefix) }) - if err != nil { return nil, err } @@ -309,7 +310,7 @@ func (r *cachingRepo) GoMod(ctx context.Context, version string) ([]byte, error) return r.repo(ctx).GoMod(ctx, version) } text, err := r.gomodCache.Do(version, func() ([]byte, error) { - file, text, err := Fetcher_.readDiskGoMod(ctx, r.path, version) + file, text, err := r.fetcher.readDiskGoMod(ctx, r.path, version) if err == nil { // Note: readDiskGoMod already called checkGoMod. return text, nil @@ -317,7 +318,7 @@ func (r *cachingRepo) GoMod(ctx context.Context, version string) ([]byte, error) text, err = r.repo(ctx).GoMod(ctx, version) if err == nil { - if err := checkGoMod(Fetcher_, r.path, version, text); err != nil { + if err := checkGoMod(r.fetcher, r.path, version, text); err != nil { return text, err } if err := writeDiskGoMod(ctx, file, text); err != nil { @@ -653,13 +654,13 @@ func writeDiskCache(ctx context.Context, file string, data []byte) error { return nil } // Make sure directory for file exists. - if err := os.MkdirAll(filepath.Dir(file), 0777); err != nil { + if err := os.MkdirAll(filepath.Dir(file), 0o777); err != nil { return err } // Write the file to a temporary location, and then rename it to its final // path to reduce the likelihood of a corrupt file existing at that final path. - f, err := tempFile(ctx, filepath.Dir(file), filepath.Base(file), 0666) + f, err := tempFile(ctx, filepath.Dir(file), filepath.Base(file), 0o666) if err != nil { return err } @@ -812,7 +813,7 @@ func checkCacheDir(ctx context.Context) error { statCacheErr = fmt.Errorf("could not create module cache: %w", err) return } - if err := os.MkdirAll(cfg.GOMODCACHE, 0777); err != nil { + if err := os.MkdirAll(cfg.GOMODCACHE, 0o777); err != nil { statCacheErr = fmt.Errorf("could not create module cache: %w", err) return } diff --git a/src/cmd/go/internal/modfetch/coderepo_test.go b/src/cmd/go/internal/modfetch/coderepo_test.go index df6e3ed0445..a403e83b847 100644 --- a/src/cmd/go/internal/modfetch/coderepo_test.go +++ b/src/cmd/go/internal/modfetch/coderepo_test.go @@ -36,7 +36,6 @@ func TestMain(m *testing.M) { } func testMain(m *testing.M) (err error) { - cfg.GOPROXY = "direct" // The sum database is populated using a released version of the go command, @@ -56,7 +55,7 @@ func testMain(m *testing.M) (err error) { }() cfg.GOMODCACHE = filepath.Join(dir, "modcache") - if err := os.Mkdir(cfg.GOMODCACHE, 0755); err != nil { + if err := os.Mkdir(cfg.GOMODCACHE, 0o755); err != nil { return err } @@ -589,6 +588,7 @@ var codeRepoTests = []codeRepoTest{ func TestCodeRepo(t *testing.T) { testenv.MustHaveExternalNetwork(t) tmpdir := t.TempDir() + fetcher := NewFetcher() for _, tt := range codeRepoTests { f := func(tt codeRepoTest) func(t *testing.T) { @@ -603,7 +603,7 @@ func TestCodeRepo(t *testing.T) { } ctx := context.Background() - repo := Fetcher_.Lookup(ctx, "direct", tt.path) + repo := fetcher.Lookup(ctx, "direct", tt.path) if tt.mpath == "" { tt.mpath = tt.path @@ -817,7 +817,7 @@ var codeRepoVersionsTests = []struct { func TestCodeRepoVersions(t *testing.T) { testenv.MustHaveExternalNetwork(t) - + fetcher := NewFetcher() for _, tt := range codeRepoVersionsTests { tt := tt t.Run(strings.ReplaceAll(tt.path, "/", "_"), func(t *testing.T) { @@ -831,7 +831,7 @@ func TestCodeRepoVersions(t *testing.T) { } ctx := context.Background() - repo := Fetcher_.Lookup(ctx, "direct", tt.path) + repo := fetcher.Lookup(ctx, "direct", tt.path) list, err := repo.Versions(ctx, tt.prefix) if err != nil { t.Fatalf("Versions(%q): %v", tt.prefix, err) @@ -898,7 +898,7 @@ var latestTests = []struct { func TestLatest(t *testing.T) { testenv.MustHaveExternalNetwork(t) - + fetcher := NewFetcher() for _, tt := range latestTests { name := strings.ReplaceAll(tt.path, "/", "_") t.Run(name, func(t *testing.T) { @@ -909,7 +909,7 @@ func TestLatest(t *testing.T) { } ctx := context.Background() - repo := Fetcher_.Lookup(ctx, "direct", tt.path) + repo := fetcher.Lookup(ctx, "direct", tt.path) info, err := repo.Latest(ctx) if err != nil { if tt.err != "" { diff --git a/src/cmd/go/internal/modfetch/fetch.go b/src/cmd/go/internal/modfetch/fetch.go index 87f69156256..7c9280f1d09 100644 --- a/src/cmd/go/internal/modfetch/fetch.go +++ b/src/cmd/go/internal/modfetch/fetch.go @@ -475,8 +475,6 @@ type Fetcher struct { sumState sumState } -var Fetcher_ *Fetcher = NewFetcher() - func NewFetcher() *Fetcher { f := new(Fetcher) f.lookupCache = new(par.Cache[lookupCacheKey, Repo]) @@ -498,15 +496,15 @@ func (f *Fetcher) AddWorkspaceGoSumFile(file string) { // Reset resets globals in the modfetch package, so previous loads don't affect // contents of go.sum files. -func Reset() { - SetState(NewFetcher()) +func (f *Fetcher) Reset() { + f.SetState(NewFetcher()) } // SetState sets the global state of the modfetch package to the newState, and returns the previous // global state. newState should have been returned by SetState, or be an empty State. // There should be no concurrent calls to any of the exported functions of this package with // a call to SetState because it will modify the global state in a non-thread-safe way. -func SetState(newState *Fetcher) (oldState *Fetcher) { +func (f *Fetcher) SetState(newState *Fetcher) (oldState *Fetcher) { if newState.lookupCache == nil { newState.lookupCache = new(par.Cache[lookupCacheKey, Repo]) } @@ -514,26 +512,26 @@ func SetState(newState *Fetcher) (oldState *Fetcher) { newState.downloadCache = new(par.ErrCache[module.Version, string]) } - Fetcher_.mu.Lock() - defer Fetcher_.mu.Unlock() + f.mu.Lock() + defer f.mu.Unlock() oldState = &Fetcher{ - goSumFile: Fetcher_.goSumFile, - workspaceGoSumFiles: Fetcher_.workspaceGoSumFiles, - lookupCache: Fetcher_.lookupCache, - downloadCache: Fetcher_.downloadCache, - sumState: Fetcher_.sumState, + goSumFile: f.goSumFile, + workspaceGoSumFiles: f.workspaceGoSumFiles, + lookupCache: f.lookupCache, + downloadCache: f.downloadCache, + sumState: f.sumState, } - Fetcher_.SetGoSumFile(newState.goSumFile) - Fetcher_.workspaceGoSumFiles = newState.workspaceGoSumFiles + f.SetGoSumFile(newState.goSumFile) + f.workspaceGoSumFiles = newState.workspaceGoSumFiles // Uses of lookupCache and downloadCache both can call checkModSum, // which in turn sets the used bit on goSum.status for modules. // Set (or reset) them so used can be computed properly. - Fetcher_.lookupCache = newState.lookupCache - Fetcher_.downloadCache = newState.downloadCache + f.lookupCache = newState.lookupCache + f.downloadCache = newState.downloadCache // Set, or reset all fields on goSum. If being reset to empty, it will be initialized later. - Fetcher_.sumState = newState.sumState + f.sumState = newState.sumState return oldState } @@ -666,20 +664,20 @@ func HaveSum(f *Fetcher, mod module.Version) bool { // The entry's hash must be generated with a known hash algorithm. // mod.Version may have a "/go.mod" suffix to distinguish sums for // .mod and .zip files. -func RecordedSum(mod module.Version) (sum string, ok bool) { - Fetcher_.mu.Lock() - defer Fetcher_.mu.Unlock() - inited, err := Fetcher_.initGoSum() +func (f *Fetcher) RecordedSum(mod module.Version) (sum string, ok bool) { + f.mu.Lock() + defer f.mu.Unlock() + inited, err := f.initGoSum() foundSum := "" if err != nil || !inited { return "", false } - for _, goSums := range Fetcher_.sumState.w { + for _, goSums := range f.sumState.w { for _, h := range goSums[mod] { if !strings.HasPrefix(h, "h1:") { continue } - if !Fetcher_.sumState.status[modSum{mod, h}].dirty { + if !f.sumState.status[modSum{mod, h}].dirty { if foundSum != "" && foundSum != h { // conflicting sums exist return "", false } @@ -687,11 +685,11 @@ func RecordedSum(mod module.Version) (sum string, ok bool) { } } } - for _, h := range Fetcher_.sumState.m[mod] { + for _, h := range f.sumState.m[mod] { if !strings.HasPrefix(h, "h1:") { continue } - if !Fetcher_.sumState.status[modSum{mod, h}].dirty { + if !f.sumState.status[modSum{mod, h}].dirty { if foundSum != "" && foundSum != h { // conflicting sums exist return "", false } @@ -977,14 +975,14 @@ Outer: // TidyGoSum returns a tidy version of the go.sum file. // A missing go.sum file is treated as if empty. -func TidyGoSum(keep map[module.Version]bool) (before, after []byte) { - Fetcher_.mu.Lock() - defer Fetcher_.mu.Unlock() - before, err := lockedfile.Read(Fetcher_.goSumFile) +func (f *Fetcher) TidyGoSum(keep map[module.Version]bool) (before, after []byte) { + f.mu.Lock() + defer f.mu.Unlock() + before, err := lockedfile.Read(f.goSumFile) if err != nil && !errors.Is(err, fs.ErrNotExist) { base.Fatalf("reading go.sum: %v", err) } - after = tidyGoSum(Fetcher_, before, keep) + after = tidyGoSum(f, before, keep) return before, after } @@ -1041,10 +1039,10 @@ func sumInWorkspaceModulesLocked(f *Fetcher, m module.Version) bool { // keep is used to check whether a sum should be retained in go.mod. It should // have entries for both module content sums and go.mod sums (version ends // with "/go.mod"). -func TrimGoSum(keep map[module.Version]bool) { - Fetcher_.mu.Lock() - defer Fetcher_.mu.Unlock() - inited, err := Fetcher_.initGoSum() +func (f *Fetcher) TrimGoSum(keep map[module.Version]bool) { + f.mu.Lock() + defer f.mu.Unlock() + inited, err := f.initGoSum() if err != nil { base.Fatalf("%s", err) } @@ -1052,12 +1050,12 @@ func TrimGoSum(keep map[module.Version]bool) { return } - for m, hs := range Fetcher_.sumState.m { + for m, hs := range f.sumState.m { if !keep[m] { for _, h := range hs { - Fetcher_.sumState.status[modSum{m, h}] = modSumStatus{used: false, dirty: true} + f.sumState.status[modSum{m, h}] = modSumStatus{used: false, dirty: true} } - Fetcher_.sumState.overwrite = true + f.sumState.overwrite = true } } } diff --git a/src/cmd/go/internal/modfetch/repo.go b/src/cmd/go/internal/modfetch/repo.go index b1e197284fe..5ed2f259a00 100644 --- a/src/cmd/go/internal/modfetch/repo.go +++ b/src/cmd/go/internal/modfetch/repo.go @@ -206,7 +206,7 @@ func (f *Fetcher) Lookup(ctx context.Context, proxy, path string) Repo { } return f.lookupCache.Do(lookupCacheKey{proxy, path}, func() Repo { - return newCachingRepo(ctx, path, func(ctx context.Context) (Repo, error) { + return newCachingRepo(ctx, f, path, func(ctx context.Context) (Repo, error) { r, err := lookup(f, ctx, proxy, path) if err == nil && traceRepo { r = newLoggingRepo(r) @@ -223,13 +223,13 @@ var lookupLocalCache = new(par.Cache[string, Repo]) // path, Repo // codeRoot is the module path of the root module in the repository. // path is the module path of the module being looked up. // dir is the file system path of the repository containing the module. -func LookupLocal(ctx context.Context, codeRoot string, path string, dir string) Repo { +func (f *Fetcher) LookupLocal(ctx context.Context, codeRoot string, path string, dir string) Repo { if traceRepo { defer logCall("LookupLocal(%q)", path)() } return lookupLocalCache.Do(path, func() Repo { - return newCachingRepo(ctx, path, func(ctx context.Context) (Repo, error) { + return newCachingRepo(ctx, f, path, func(ctx context.Context) (Repo, error) { repoDir, vcsCmd, err := vcs.FromDir(dir, "") if err != nil { return nil, err diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go index e89a34e0ce9..b560dd6a617 100644 --- a/src/cmd/go/internal/modload/build.go +++ b/src/cmd/go/internal/modload/build.go @@ -368,7 +368,7 @@ func moduleInfo(loaderstate *State, ctx context.Context, rs *Requirements, m mod m.GoMod = gomod } } - if gomodsum, ok := modfetch.RecordedSum(modkey(mod)); ok { + if gomodsum, ok := loaderstate.fetcher.RecordedSum(modkey(mod)); ok { m.GoModSum = gomodsum } } @@ -377,7 +377,7 @@ func moduleInfo(loaderstate *State, ctx context.Context, rs *Requirements, m mod if err == nil { m.Dir = dir } - if sum, ok := modfetch.RecordedSum(mod); ok { + if sum, ok := loaderstate.fetcher.RecordedSum(mod); ok { m.Sum = sum } } diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go index d78a41d3c61..8bfae266928 100644 --- a/src/cmd/go/internal/modload/init.go +++ b/src/cmd/go/internal/modload/init.go @@ -60,7 +60,7 @@ func EnterModule(loaderstate *State, ctx context.Context, enterModroot string) { loaderstate.MainModules = nil // reset MainModules loaderstate.requirements = nil loaderstate.workFilePath = "" // Force module mode - modfetch.Reset() + loaderstate.Fetcher().Reset() loaderstate.modRoots = []string{enterModroot} LoadModFile(loaderstate, ctx) @@ -411,8 +411,7 @@ func (s *State) setState(new *State) (old *State) { // The modfetch package's global state is used to compute // the go.sum file, so save and restore it along with the // modload state. - s.fetcher = new.fetcher - old.fetcher = modfetch.SetState(s.fetcher) // TODO(jitsu): remove after completing global state elimination + old.fetcher = s.fetcher.SetState(new.fetcher) return old } @@ -457,7 +456,7 @@ type State struct { func NewState() *State { s := new(State) - s.fetcher = modfetch.Fetcher_ + s.fetcher = modfetch.NewFetcher() return s } diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go index bbc81263d5a..a432862429b 100644 --- a/src/cmd/go/internal/modload/load.go +++ b/src/cmd/go/internal/modload/load.go @@ -462,13 +462,13 @@ func LoadPackages(loaderstate *State, ctx context.Context, opts PackageOpts, pat } goModDiff := diff.Diff("current/go.mod", currentGoMod, "tidy/go.mod", updatedGoMod) - modfetch.TrimGoSum(keep) + loaderstate.Fetcher().TrimGoSum(keep) // Dropping compatibility for 1.16 may result in a strictly smaller go.sum. // Update the keep map with only the loaded.requirements. if gover.Compare(compatVersion, "1.16") > 0 { keep = keepSums(loaderstate, ctx, loaded, loaderstate.requirements, addBuildListZipSums) } - currentGoSum, tidyGoSum := modfetch.TidyGoSum(keep) + currentGoSum, tidyGoSum := loaderstate.fetcher.TidyGoSum(keep) goSumDiff := diff.Diff("current/go.sum", currentGoSum, "tidy/go.sum", tidyGoSum) if len(goModDiff) > 0 { @@ -483,7 +483,7 @@ func LoadPackages(loaderstate *State, ctx context.Context, opts PackageOpts, pat } if !ExplicitWriteGoMod { - modfetch.TrimGoSum(keep) + loaderstate.Fetcher().TrimGoSum(keep) // commitRequirements below will also call WriteGoSum, but the "keep" map // we have here could be strictly larger: commitRequirements only commits From dda7c8253dd5e4dc5732187f1c039325bca53c8c Mon Sep 17 00:00:00 2001 From: Alexander Musman Date: Sat, 5 Jul 2025 23:16:36 +0300 Subject: [PATCH 057/140] cmd/compile,internal/bytealg: add MemEq intrinsic for runtime.memequal Introduce a new MemEq SSA operation for runtime.memequal. The operation is initially implemented for arm64. The change adds opt rules (following existing rules for call to runtime.memequal), working with MemEq, and a later op version LoweredMemEq which may be lowered differently for more constant size cases in future (for other targets as well as for arm64). The new MemEq SSA operation does not have memory result, allowing cse of loads operations around it. Code size difference (for arm64 linux): Executable Old .text New .text Change ------------------------------------------------------- asm 1970420 1969668 -0.04% cgo 1741220 1740212 -0.06% compile 8956756 8959428 +0.03% cover 1879332 1878772 -0.03% link 2574116 2572660 -0.06% preprofile 867124 866820 -0.04% vet 2890404 2888596 -0.06% Change-Id: I6ab507929b861884d17d5818cfbd152cf7879751 Reviewed-on: https://go-review.googlesource.com/c/go/+/686655 Reviewed-by: Keith Randall LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui Auto-Submit: Keith Randall Reviewed-by: Keith Randall --- src/cmd/compile/internal/arm64/ssa.go | 5 + src/cmd/compile/internal/ir/symtab.go | 1 + src/cmd/compile/internal/ssa/_gen/ARM64.rules | 1 + src/cmd/compile/internal/ssa/_gen/ARM64Ops.go | 3 +- .../compile/internal/ssa/_gen/generic.rules | 35 +++ .../compile/internal/ssa/_gen/genericOps.go | 3 + src/cmd/compile/internal/ssa/opGen.go | 27 +++ src/cmd/compile/internal/ssa/regalloc.go | 17 +- src/cmd/compile/internal/ssa/rewriteARM64.go | 3 + .../compile/internal/ssa/rewritegeneric.go | 218 ++++++++++++++++++ src/cmd/compile/internal/ssagen/intrinsics.go | 6 + .../internal/ssagen/intrinsics_test.go | 1 + src/cmd/compile/internal/ssagen/ssa.go | 1 + test/codegen/comparisons.go | 11 + test/codegen/memcse.go | 17 ++ 15 files changed, 345 insertions(+), 4 deletions(-) create mode 100644 test/codegen/memcse.go diff --git a/src/cmd/compile/internal/arm64/ssa.go b/src/cmd/compile/internal/arm64/ssa.go index 43ecb6b4b71..74371104a31 100644 --- a/src/cmd/compile/internal/arm64/ssa.go +++ b/src/cmd/compile/internal/arm64/ssa.go @@ -1322,6 +1322,11 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { p.To.Name = obj.NAME_EXTERN // AuxInt encodes how many buffer entries we need. p.To.Sym = ir.Syms.GCWriteBarrier[v.AuxInt-1] + case ssa.OpARM64LoweredMemEq: + p := s.Prog(obj.ACALL) + p.To.Type = obj.TYPE_MEM + p.To.Name = obj.NAME_EXTERN + p.To.Sym = ir.Syms.Memequal case ssa.OpARM64LoweredPanicBoundsRR, ssa.OpARM64LoweredPanicBoundsRC, ssa.OpARM64LoweredPanicBoundsCR, ssa.OpARM64LoweredPanicBoundsCC: // Compute the constant we put in the PCData entry for this call. diff --git a/src/cmd/compile/internal/ir/symtab.go b/src/cmd/compile/internal/ir/symtab.go index 4b5bf17a3de..32297354644 100644 --- a/src/cmd/compile/internal/ir/symtab.go +++ b/src/cmd/compile/internal/ir/symtab.go @@ -40,6 +40,7 @@ type symsStruct struct { MallocGCSmallScanNoHeader [27]*obj.LSym MallocGCTiny [16]*obj.LSym Memmove *obj.LSym + Memequal *obj.LSym Msanread *obj.LSym Msanwrite *obj.LSym Msanmove *obj.LSym diff --git a/src/cmd/compile/internal/ssa/_gen/ARM64.rules b/src/cmd/compile/internal/ssa/_gen/ARM64.rules index 53bb35d2897..4ade43f1a14 100644 --- a/src/cmd/compile/internal/ssa/_gen/ARM64.rules +++ b/src/cmd/compile/internal/ssa/_gen/ARM64.rules @@ -481,6 +481,7 @@ (GetClosurePtr ...) => (LoweredGetClosurePtr ...) (GetCallerSP ...) => (LoweredGetCallerSP ...) (GetCallerPC ...) => (LoweredGetCallerPC ...) +(MemEq ...) => (LoweredMemEq ...) // Absorb pseudo-ops into blocks. (If (Equal cc) yes no) => (EQ cc yes no) diff --git a/src/cmd/compile/internal/ssa/_gen/ARM64Ops.go b/src/cmd/compile/internal/ssa/_gen/ARM64Ops.go index b710724cca1..c84b24cad12 100644 --- a/src/cmd/compile/internal/ssa/_gen/ARM64Ops.go +++ b/src/cmd/compile/internal/ssa/_gen/ARM64Ops.go @@ -534,7 +534,8 @@ func init() { {name: "CALLinter", argLength: -1, reg: regInfo{inputs: []regMask{gp}, clobbers: callerSave}, aux: "CallOff", clobberFlags: true, call: true}, // call fn by pointer. arg0=codeptr, last arg=mem, auxint=argsize, returns mem // pseudo-ops - {name: "LoweredNilCheck", argLength: 2, reg: regInfo{inputs: []regMask{gpg}}, nilCheck: true, faultOnNilArg0: true}, // panic if arg0 is nil. arg1=mem. + {name: "LoweredNilCheck", argLength: 2, reg: regInfo{inputs: []regMask{gpg}}, nilCheck: true, faultOnNilArg0: true}, // panic if arg0 is nil. arg1=mem. + {name: "LoweredMemEq", argLength: 4, reg: regInfo{inputs: []regMask{buildReg("R0"), buildReg("R1"), buildReg("R2")}, outputs: []regMask{buildReg("R0")}, clobbers: callerSave}, typ: "Bool", faultOnNilArg0: true, faultOnNilArg1: true, clobberFlags: true, call: true}, // arg0, arg1 - pointers to memory, arg2=size, arg3=mem. {name: "Equal", argLength: 1, reg: readflags}, // bool, true flags encode x==y false otherwise. {name: "NotEqual", argLength: 1, reg: readflags}, // bool, true flags encode x!=y false otherwise. diff --git a/src/cmd/compile/internal/ssa/_gen/generic.rules b/src/cmd/compile/internal/ssa/_gen/generic.rules index 90ff0b74eca..7710f6f2097 100644 --- a/src/cmd/compile/internal/ssa/_gen/generic.rules +++ b/src/cmd/compile/internal/ssa/_gen/generic.rules @@ -1525,6 +1525,41 @@ && isSamePtr(p, q) => (MakeResult (ConstBool [true]) mem) +(MemEq sptr tptr (Const64 [1]) mem) + => (Eq8 (Load sptr mem) (Load tptr mem)) + +(Load sptr:(Addr {scon} (SB)) mem) + && symIsRO(scon) + => (Const8 [int8(read8(scon,0))]) + +(MemEq sptr tptr (Const64 [2]) mem) + && canLoadUnaligned(config) + => (Eq16 (Load sptr mem) (Load tptr mem)) + +(Load sptr:(Addr {scon} (SB)) mem) + && symIsRO(scon) + => (Const16 [int16(read16(scon,0,config.ctxt.Arch.ByteOrder))]) + +(MemEq sptr tptr (Const64 [4]) mem) + && canLoadUnaligned(config) + => (Eq32 (Load sptr mem) (Load tptr mem)) + +(Load sptr:(Addr {scon} (SB)) mem) + && symIsRO(scon) + => (Const32 [int32(read32(scon,0,config.ctxt.Arch.ByteOrder))]) + +(MemEq sptr tptr (Const64 [8]) mem) + && canLoadUnaligned(config) && config.PtrSize == 8 + => (Eq64 (Load sptr mem) (Load tptr mem)) + +(Load sptr:(Addr {scon} (SB)) mem) + && symIsRO(scon) + => (Const64 [int64(read64(scon,0,config.ctxt.Arch.ByteOrder))]) + +(MemEq _ _ (Const64 [0]) _) => (ConstBool [true]) + +(MemEq p q _ _) && isSamePtr(p, q) => (ConstBool [true]) + // Turn known-size calls to memclrNoHeapPointers into a Zero. // Note that we are using types.Types[types.TUINT8] instead of sptr.Type.Elem() - see issue 55122 and CL 431496 for more details. (SelectN [0] call:(StaticCall {sym} sptr (Const(64|32) [c]) mem)) diff --git a/src/cmd/compile/internal/ssa/_gen/genericOps.go b/src/cmd/compile/internal/ssa/_gen/genericOps.go index ce01f2c0e3d..8637133e5f7 100644 --- a/src/cmd/compile/internal/ssa/_gen/genericOps.go +++ b/src/cmd/compile/internal/ssa/_gen/genericOps.go @@ -679,6 +679,9 @@ var genericOps = []opData{ {name: "PrefetchCache", argLength: 2, hasSideEffects: true}, // Do prefetch arg0 to cache. arg0=addr, arg1=memory. {name: "PrefetchCacheStreamed", argLength: 2, hasSideEffects: true}, // Do non-temporal or streamed prefetch arg0 to cache. arg0=addr, arg1=memory. + // Helper instruction which is semantically equivalent to calling runtime.memequal, but some targets may prefer to custom lower it later, e.g. for specific constant sizes. + {name: "MemEq", argLength: 4, commutative: true, typ: "Bool"}, // arg0=ptr0, arg1=ptr1, arg2=size, arg3=memory. + // SIMD {name: "ZeroSIMD", argLength: 0}, // zero value of a vector diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index 966d15b83ca..fee0228e7f5 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -4202,6 +4202,7 @@ const ( OpARM64CALLclosure OpARM64CALLinter OpARM64LoweredNilCheck + OpARM64LoweredMemEq OpARM64Equal OpARM64NotEqual OpARM64LessThan @@ -5916,6 +5917,7 @@ const ( OpClobberReg OpPrefetchCache OpPrefetchCacheStreamed + OpMemEq OpZeroSIMD OpCvt16toMask8x16 OpCvt32toMask8x32 @@ -65520,6 +65522,25 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + name: "LoweredMemEq", + argLen: 4, + clobberFlags: true, + call: true, + faultOnNilArg0: true, + faultOnNilArg1: true, + reg: regInfo{ + inputs: []inputInfo{ + {0, 1}, // R0 + {1, 2}, // R1 + {2, 4}, // R2 + }, + clobbers: 9223372035109945343, // R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R19 R20 R21 R22 R23 R24 R25 R26 g R30 F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 F16 F17 F18 F19 F20 F21 F22 F23 F24 F25 F26 F27 F28 F29 F30 F31 + outputs: []outputInfo{ + {0, 1}, // R0 + }, + }, + }, { name: "Equal", argLen: 1, @@ -85517,6 +85538,12 @@ var opcodeTable = [...]opInfo{ hasSideEffects: true, generic: true, }, + { + name: "MemEq", + argLen: 4, + commutative: true, + generic: true, + }, { name: "ZeroSIMD", argLen: 0, diff --git a/src/cmd/compile/internal/ssa/regalloc.go b/src/cmd/compile/internal/ssa/regalloc.go index 11dd53bfc7d..a0257f30641 100644 --- a/src/cmd/compile/internal/ssa/regalloc.go +++ b/src/cmd/compile/internal/ssa/regalloc.go @@ -897,7 +897,15 @@ func (s *regAllocState) dropIfUnused(v *Value) { } vi := &s.values[v.ID] r := vi.uses - if r == nil || (!opcodeTable[v.Op].fixedReg && r.dist > s.nextCall[s.curIdx]) { + nextCall := s.nextCall[s.curIdx] + if opcodeTable[v.Op].call { + if s.curIdx == len(s.nextCall)-1 { + nextCall = math.MaxInt32 + } else { + nextCall = s.nextCall[s.curIdx+1] + } + } + if r == nil || (!opcodeTable[v.Op].fixedReg && r.dist > nextCall) { s.freeRegs(vi.regs) } } @@ -1036,8 +1044,11 @@ func (s *regAllocState) regalloc(f *Func) { regValLiveSet.add(v.ID) } } - if len(s.nextCall) < len(b.Values) { - s.nextCall = append(s.nextCall, make([]int32, len(b.Values)-len(s.nextCall))...) + if cap(s.nextCall) < len(b.Values) { + c := cap(s.nextCall) + s.nextCall = append(s.nextCall[:c], make([]int32, len(b.Values)-c)...) + } else { + s.nextCall = s.nextCall[:len(b.Values)] } var nextCall int32 = math.MaxInt32 for i := len(b.Values) - 1; i >= 0; i-- { diff --git a/src/cmd/compile/internal/ssa/rewriteARM64.go b/src/cmd/compile/internal/ssa/rewriteARM64.go index b3f790dbda5..25a1c9c0fc1 100644 --- a/src/cmd/compile/internal/ssa/rewriteARM64.go +++ b/src/cmd/compile/internal/ssa/rewriteARM64.go @@ -840,6 +840,9 @@ func rewriteValueARM64(v *Value) bool { case OpMax64F: v.Op = OpARM64FMAXD return true + case OpMemEq: + v.Op = OpARM64LoweredMemEq + return true case OpMin32F: v.Op = OpARM64FMINS return true diff --git a/src/cmd/compile/internal/ssa/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go index d40333f0196..fea126bd4d4 100644 --- a/src/cmd/compile/internal/ssa/rewritegeneric.go +++ b/src/cmd/compile/internal/ssa/rewritegeneric.go @@ -224,6 +224,8 @@ func rewriteValuegeneric(v *Value) bool { return rewriteValuegeneric_OpLsh8x64(v) case OpLsh8x8: return rewriteValuegeneric_OpLsh8x8(v) + case OpMemEq: + return rewriteValuegeneric_OpMemEq(v) case OpMod16: return rewriteValuegeneric_OpMod16(v) case OpMod16u: @@ -11869,6 +11871,8 @@ func rewriteValuegeneric_OpLoad(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] b := v.Block + config := b.Func.Config + typ := &b.Func.Config.Types // match: (Load p1 (Store {t2} p2 x _)) // cond: isSamePtr(p1, p2) && copyCompatibleType(t1, x.Type) && t1.Size() == t2.Size() // result: x @@ -12453,6 +12457,102 @@ func rewriteValuegeneric_OpLoad(v *Value) bool { v.AddArg(v0) return true } + // match: (Load sptr:(Addr {scon} (SB)) mem) + // cond: symIsRO(scon) + // result: (Const8 [int8(read8(scon,0))]) + for { + if v.Type != typ.Int8 { + break + } + sptr := v_0 + if sptr.Op != OpAddr { + break + } + scon := auxToSym(sptr.Aux) + sptr_0 := sptr.Args[0] + if sptr_0.Op != OpSB { + break + } + if !(symIsRO(scon)) { + break + } + v.reset(OpConst8) + v.Type = typ.Int8 + v.AuxInt = int8ToAuxInt(int8(read8(scon, 0))) + return true + } + // match: (Load sptr:(Addr {scon} (SB)) mem) + // cond: symIsRO(scon) + // result: (Const16 [int16(read16(scon,0,config.ctxt.Arch.ByteOrder))]) + for { + if v.Type != typ.Int16 { + break + } + sptr := v_0 + if sptr.Op != OpAddr { + break + } + scon := auxToSym(sptr.Aux) + sptr_0 := sptr.Args[0] + if sptr_0.Op != OpSB { + break + } + if !(symIsRO(scon)) { + break + } + v.reset(OpConst16) + v.Type = typ.Int16 + v.AuxInt = int16ToAuxInt(int16(read16(scon, 0, config.ctxt.Arch.ByteOrder))) + return true + } + // match: (Load sptr:(Addr {scon} (SB)) mem) + // cond: symIsRO(scon) + // result: (Const32 [int32(read32(scon,0,config.ctxt.Arch.ByteOrder))]) + for { + if v.Type != typ.Int32 { + break + } + sptr := v_0 + if sptr.Op != OpAddr { + break + } + scon := auxToSym(sptr.Aux) + sptr_0 := sptr.Args[0] + if sptr_0.Op != OpSB { + break + } + if !(symIsRO(scon)) { + break + } + v.reset(OpConst32) + v.Type = typ.Int32 + v.AuxInt = int32ToAuxInt(int32(read32(scon, 0, config.ctxt.Arch.ByteOrder))) + return true + } + // match: (Load sptr:(Addr {scon} (SB)) mem) + // cond: symIsRO(scon) + // result: (Const64 [int64(read64(scon,0,config.ctxt.Arch.ByteOrder))]) + for { + if v.Type != typ.Int64 { + break + } + sptr := v_0 + if sptr.Op != OpAddr { + break + } + scon := auxToSym(sptr.Aux) + sptr_0 := sptr.Args[0] + if sptr_0.Op != OpSB { + break + } + if !(symIsRO(scon)) { + break + } + v.reset(OpConst64) + v.Type = typ.Int64 + v.AuxInt = int64ToAuxInt(int64(read64(scon, 0, config.ctxt.Arch.ByteOrder))) + return true + } // match: (Load (Addr {s} sb) _) // cond: isFixedLoad(v, s, 0) // result: rewriteFixedLoad(v, s, sb, 0) @@ -14767,6 +14867,124 @@ func rewriteValuegeneric_OpLsh8x8(v *Value) bool { } return false } +func rewriteValuegeneric_OpMemEq(v *Value) bool { + v_3 := v.Args[3] + v_2 := v.Args[2] + v_1 := v.Args[1] + v_0 := v.Args[0] + b := v.Block + config := b.Func.Config + typ := &b.Func.Config.Types + // match: (MemEq sptr tptr (Const64 [1]) mem) + // result: (Eq8 (Load sptr mem) (Load tptr mem)) + for { + sptr := v_0 + tptr := v_1 + if v_2.Op != OpConst64 || auxIntToInt64(v_2.AuxInt) != 1 { + break + } + mem := v_3 + v.reset(OpEq8) + v0 := b.NewValue0(v.Pos, OpLoad, typ.Int8) + v0.AddArg2(sptr, mem) + v1 := b.NewValue0(v.Pos, OpLoad, typ.Int8) + v1.AddArg2(tptr, mem) + v.AddArg2(v0, v1) + return true + } + // match: (MemEq sptr tptr (Const64 [2]) mem) + // cond: canLoadUnaligned(config) + // result: (Eq16 (Load sptr mem) (Load tptr mem)) + for { + sptr := v_0 + tptr := v_1 + if v_2.Op != OpConst64 || auxIntToInt64(v_2.AuxInt) != 2 { + break + } + mem := v_3 + if !(canLoadUnaligned(config)) { + break + } + v.reset(OpEq16) + v0 := b.NewValue0(v.Pos, OpLoad, typ.Int16) + v0.AddArg2(sptr, mem) + v1 := b.NewValue0(v.Pos, OpLoad, typ.Int16) + v1.AddArg2(tptr, mem) + v.AddArg2(v0, v1) + return true + } + // match: (MemEq sptr tptr (Const64 [4]) mem) + // cond: canLoadUnaligned(config) + // result: (Eq32 (Load sptr mem) (Load tptr mem)) + for { + sptr := v_0 + tptr := v_1 + if v_2.Op != OpConst64 || auxIntToInt64(v_2.AuxInt) != 4 { + break + } + mem := v_3 + if !(canLoadUnaligned(config)) { + break + } + v.reset(OpEq32) + v0 := b.NewValue0(v.Pos, OpLoad, typ.Int32) + v0.AddArg2(sptr, mem) + v1 := b.NewValue0(v.Pos, OpLoad, typ.Int32) + v1.AddArg2(tptr, mem) + v.AddArg2(v0, v1) + return true + } + // match: (MemEq sptr tptr (Const64 [8]) mem) + // cond: canLoadUnaligned(config) && config.PtrSize == 8 + // result: (Eq64 (Load sptr mem) (Load tptr mem)) + for { + sptr := v_0 + tptr := v_1 + if v_2.Op != OpConst64 || auxIntToInt64(v_2.AuxInt) != 8 { + break + } + mem := v_3 + if !(canLoadUnaligned(config) && config.PtrSize == 8) { + break + } + v.reset(OpEq64) + v0 := b.NewValue0(v.Pos, OpLoad, typ.Int64) + v0.AddArg2(sptr, mem) + v1 := b.NewValue0(v.Pos, OpLoad, typ.Int64) + v1.AddArg2(tptr, mem) + v.AddArg2(v0, v1) + return true + } + // match: (MemEq _ _ (Const64 [0]) _) + // result: (ConstBool [true]) + for { + if v_2.Op != OpConst64 || auxIntToInt64(v_2.AuxInt) != 0 { + break + } + v.reset(OpConstBool) + v.Type = typ.Bool + v.AuxInt = boolToAuxInt(true) + return true + } + // match: (MemEq p q _ _) + // cond: isSamePtr(p, q) + // result: (ConstBool [true]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + p := v_0 + q := v_1 + if !(isSamePtr(p, q)) { + continue + } + v.reset(OpConstBool) + v.Type = typ.Bool + v.AuxInt = boolToAuxInt(true) + return true + } + break + } + return false +} func rewriteValuegeneric_OpMod16(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] diff --git a/src/cmd/compile/internal/ssagen/intrinsics.go b/src/cmd/compile/internal/ssagen/intrinsics.go index e346b00a1b3..17beb7b8488 100644 --- a/src/cmd/compile/internal/ssagen/intrinsics.go +++ b/src/cmd/compile/internal/ssagen/intrinsics.go @@ -196,6 +196,12 @@ func initIntrinsics(cfg *intrinsicBuildConfig) { }, sys.AMD64, sys.I386, sys.ARM64, sys.ARM, sys.Loong64, sys.S390X) + addF("runtime", "memequal", + func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value { + return s.newValue4(ssa.OpMemEq, s.f.Config.Types.Bool, args[0], args[1], args[2], s.mem()) + }, + sys.ARM64) + if cfg.goppc64 >= 10 { // Use only on Power10 as the new byte reverse instructions that Power10 provide // make it worthwhile as an intrinsic diff --git a/src/cmd/compile/internal/ssagen/intrinsics_test.go b/src/cmd/compile/internal/ssagen/intrinsics_test.go index 0c483d49c33..91b975c913f 100644 --- a/src/cmd/compile/internal/ssagen/intrinsics_test.go +++ b/src/cmd/compile/internal/ssagen/intrinsics_test.go @@ -327,6 +327,7 @@ var wantIntrinsics = map[testIntrinsicKey]struct{}{ {"arm64", "math/bits", "TrailingZeros64"}: struct{}{}, {"arm64", "math/bits", "TrailingZeros8"}: struct{}{}, {"arm64", "runtime", "KeepAlive"}: struct{}{}, + {"arm64", "runtime", "memequal"}: struct{}{}, {"arm64", "runtime", "publicationBarrier"}: struct{}{}, {"arm64", "runtime", "slicebytetostringtmp"}: struct{}{}, {"arm64", "sync", "runtime_LoadAcquintptr"}: struct{}{}, diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 6dc150f5b2c..830e0136972 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -141,6 +141,7 @@ func InitConfig() { } ir.Syms.MallocGC = typecheck.LookupRuntimeFunc("mallocgc") ir.Syms.Memmove = typecheck.LookupRuntimeFunc("memmove") + ir.Syms.Memequal = typecheck.LookupRuntimeFunc("memequal") ir.Syms.Msanread = typecheck.LookupRuntimeFunc("msanread") ir.Syms.Msanwrite = typecheck.LookupRuntimeFunc("msanwrite") ir.Syms.Msanmove = typecheck.LookupRuntimeFunc("msanmove") diff --git a/test/codegen/comparisons.go b/test/codegen/comparisons.go index 74a7d689da3..bcce21e4044 100644 --- a/test/codegen/comparisons.go +++ b/test/codegen/comparisons.go @@ -660,6 +660,17 @@ func equalVarString8(a string) bool { return a[:8] == b } +func equalVarStringNoSpill(a,b string) bool { + s := string("ZZZZZZZZZ") + // arm64:".*memequal" + memeq1 := a[:9] == s + // arm64:-".*" + memeq2 := s == a[:9] + // arm64:-"MOVB\tR0,.*SP",".*memequal" + memeq3 := s == b[:9] + return memeq1 && memeq2 && memeq3 +} + func cmpToCmn(a, b, c, d int) int { var c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11 int // arm64:`CMN`,-`CMP` diff --git a/test/codegen/memcse.go b/test/codegen/memcse.go new file mode 100644 index 00000000000..d2eb1561689 --- /dev/null +++ b/test/codegen/memcse.go @@ -0,0 +1,17 @@ +// asmcheck + +// Copyright 2025 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. + +// Test common subexpression elimination of loads around other operations. + +package codegen + +func loadsAroundMemEqual(p *int, s1, s2 string) (int, bool) { + x := *p + eq := s1 == s2 + y := *p + // arm64:"MOVD ZR, R0" + return x - y, eq +} From 03fcb33c0ef76ebbdfa5e9ff483e26d5a250abd5 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Wed, 26 Nov 2025 09:27:45 +0100 Subject: [PATCH 058/140] cmd/compile: add tests bruteforcing limit negation and improve limit addition I had to improve addition to make the tests pass. Change-Id: I4daba2ee0f24a0dbc3929bf9afadd2116e16efae Reviewed-on: https://go-review.googlesource.com/c/go/+/724600 LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui Auto-Submit: Keith Randall Auto-Submit: Jorropo Reviewed-by: Keith Randall Reviewed-by: Keith Randall --- src/cmd/compile/internal/ssa/prove.go | 52 +++++++++++++++- src/cmd/compile/internal/ssa/prove_test.go | 69 ++++++++++++++++++++++ 2 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 src/cmd/compile/internal/ssa/prove_test.go diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go index 536965a0a0a..1aab7e3eb77 100644 --- a/src/cmd/compile/internal/ssa/prove.go +++ b/src/cmd/compile/internal/ssa/prove.go @@ -250,9 +250,51 @@ func fitsInBitsU(x uint64, b uint) bool { return x>>b == 0 } +func noLimitForBitsize(bitsize uint) limit { + return limit{min: -(1 << (bitsize - 1)), max: 1<<(bitsize-1) - 1, umin: 0, umax: 1< realBiggest { + realBiggest = result + } + } + + l := limit{int64(min), int64(max), 0, math.MaxUint64} + l = op(l, 8) + l = l.intersect(sizeLimit) // We assume this is gonna be used by newLimit which is seeded by the op size already. + + if l.min != int64(realSmallest) || l.max != int64(realBiggest) { + t.Errorf("%s(%d..%d) = %d..%d; want %d..%d", opName, min, max, l.min, l.max, realSmallest, realBiggest) + } + } + } +} + +func testLimitUnaryOpUnsigned8(t *testing.T, opName string, op func(l limit, bitsize uint) limit, opImpl func(uint8) uint8) { + sizeLimit := noLimitForBitsize(8) + for min := 0; min <= math.MaxUint8; min++ { + for max := min; max <= math.MaxUint8; max++ { + realSmallest, realBiggest := uint8(math.MaxUint8), uint8(0) + for i := min; i <= max; i++ { + result := opImpl(uint8(i)) + if result < realSmallest { + realSmallest = result + } + if result > realBiggest { + realBiggest = result + } + } + + l := limit{math.MinInt64, math.MaxInt64, uint64(min), uint64(max)} + l = op(l, 8) + l = l.intersect(sizeLimit) // We assume this is gonna be used by newLimit which is seeded by the op size already. + + if l.umin != uint64(realSmallest) || l.umax != uint64(realBiggest) { + t.Errorf("%s(%d..%d) = %d..%d; want %d..%d", opName, min, max, l.umin, l.umax, realSmallest, realBiggest) + } + } + } +} + +func TestLimitNegSigned(t *testing.T) { + testLimitUnaryOpSigned8(t, "neg", limit.neg, func(x int8) int8 { return -x }) +} +func TestLimitNegUnsigned(t *testing.T) { + testLimitUnaryOpUnsigned8(t, "neg", limit.neg, func(x uint8) uint8 { return -x }) +} From 71f8f031b27502e057c569fef8cd4f2cb9187504 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Wed, 26 Nov 2025 08:26:49 +0000 Subject: [PATCH 059/140] crypto/internal/fips140/aes: optimize ctrBlocks8Asm on amd64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement overflow-aware optimization in ctrBlocks8Asm: make a fast branch in case when there is no overflow. One branch per 8 blocks is faster than 7 increments in general purpose registers and transfers from them to XMM. Added AES-192 and AES-256 modes to the AES-CTR benchmark. Added a correctness test in ctr_test.go for the overflow optimization. This improves performance, especially in AES-128 mode. goos: windows goarch: amd64 pkg: crypto/cipher cpu: AMD Ryzen 7 5800H with Radeon Graphics │ B/s │ B/s vs base AESCTR/128/50-16 1.377Gi ± 0% 1.384Gi ± 0% +0.51% (p=0.028 n=20) AESCTR/128/1K-16 6.164Gi ± 0% 6.892Gi ± 1% +11.81% (p=0.000 n=20) AESCTR/128/8K-16 7.372Gi ± 0% 8.768Gi ± 1% +18.95% (p=0.000 n=20) AESCTR/192/50-16 1.289Gi ± 0% 1.279Gi ± 0% -0.75% (p=0.001 n=20) AESCTR/192/1K-16 5.734Gi ± 0% 6.011Gi ± 0% +4.83% (p=0.000 n=20) AESCTR/192/8K-16 6.889Gi ± 1% 7.437Gi ± 0% +7.96% (p=0.000 n=20) AESCTR/256/50-16 1.170Gi ± 0% 1.163Gi ± 0% -0.54% (p=0.005 n=20) AESCTR/256/1K-16 5.235Gi ± 0% 5.391Gi ± 0% +2.98% (p=0.000 n=20) AESCTR/256/8K-16 6.361Gi ± 0% 6.676Gi ± 0% +4.94% (p=0.000 n=20) geomean 3.681Gi 3.882Gi +5.46% The slight slowdown on 50-byte workloads is unrelated to this change, because such workloads never use ctrBlocks8Asm. Updates #76061 Change-Id: Idfd628ac8bb282d9c73c6adf048eb12274a41379 GitHub-Last-Rev: 5aadd39351806fbbf5201e07511aac05bdcb0529 GitHub-Pull-Request: golang/go#76059 Reviewed-on: https://go-review.googlesource.com/c/go/+/714361 Reviewed-by: Cherry Mui Reviewed-by: AHMAD ابو وليد Reviewed-by: Filippo Valsorda LUCI-TryBot-Result: Go LUCI Reviewed-by: Roland Shoemaker Auto-Submit: Filippo Valsorda --- src/crypto/cipher/benchmark_test.go | 29 ++++--- src/crypto/cipher/ctr_aes_test.go | 64 +++++++++++++++ .../fips140/aes/_asm/ctr/ctr_amd64_asm.go | 82 ++++++++++++++++--- src/crypto/internal/fips140/aes/ctr_amd64.s | 39 +++++++-- 4 files changed, 185 insertions(+), 29 deletions(-) diff --git a/src/crypto/cipher/benchmark_test.go b/src/crypto/cipher/benchmark_test.go index 181d08c9b14..1a5b1b1ddd5 100644 --- a/src/crypto/cipher/benchmark_test.go +++ b/src/crypto/cipher/benchmark_test.go @@ -65,12 +65,12 @@ func BenchmarkAESGCM(b *testing.B) { } } -func benchmarkAESStream(b *testing.B, mode func(cipher.Block, []byte) cipher.Stream, buf []byte) { +func benchmarkAESStream(b *testing.B, mode func(cipher.Block, []byte) cipher.Stream, buf []byte, keySize int) { b.SetBytes(int64(len(buf))) - var key [16]byte + key := make([]byte, keySize) var iv [16]byte - aes, _ := aes.NewCipher(key[:]) + aes, _ := aes.NewCipher(key) stream := mode(aes, iv[:]) b.ResetTimer() @@ -87,15 +87,20 @@ const almost1K = 1024 - 5 const almost8K = 8*1024 - 5 func BenchmarkAESCTR(b *testing.B) { - b.Run("50", func(b *testing.B) { - benchmarkAESStream(b, cipher.NewCTR, make([]byte, 50)) - }) - b.Run("1K", func(b *testing.B) { - benchmarkAESStream(b, cipher.NewCTR, make([]byte, almost1K)) - }) - b.Run("8K", func(b *testing.B) { - benchmarkAESStream(b, cipher.NewCTR, make([]byte, almost8K)) - }) + for _, keyBits := range []int{128, 192, 256} { + keySize := keyBits / 8 + b.Run(strconv.Itoa(keyBits), func(b *testing.B) { + b.Run("50", func(b *testing.B) { + benchmarkAESStream(b, cipher.NewCTR, make([]byte, 50), keySize) + }) + b.Run("1K", func(b *testing.B) { + benchmarkAESStream(b, cipher.NewCTR, make([]byte, almost1K), keySize) + }) + b.Run("8K", func(b *testing.B) { + benchmarkAESStream(b, cipher.NewCTR, make([]byte, almost8K), keySize) + }) + }) + } } func BenchmarkAESCBCEncrypt1K(b *testing.B) { diff --git a/src/crypto/cipher/ctr_aes_test.go b/src/crypto/cipher/ctr_aes_test.go index 9b7d30e2164..1d8ae78674e 100644 --- a/src/crypto/cipher/ctr_aes_test.go +++ b/src/crypto/cipher/ctr_aes_test.go @@ -17,6 +17,7 @@ import ( "crypto/internal/boring" "crypto/internal/cryptotest" fipsaes "crypto/internal/fips140/aes" + "encoding/binary" "encoding/hex" "fmt" "math/rand" @@ -117,6 +118,60 @@ func makeTestingCiphers(aesBlock cipher.Block, iv []byte) (genericCtr, multibloc return cipher.NewCTR(wrap(aesBlock), iv), cipher.NewCTR(aesBlock, iv) } +// TestCTR_AES_blocks8FastPathMatchesGeneric ensures the overlow aware branch +// produces identical keystreams to the generic counter walker across +// representative IVs, including near-overflow cases. +func TestCTR_AES_blocks8FastPathMatchesGeneric(t *testing.T) { + key := make([]byte, aes.BlockSize) + block, err := aes.NewCipher(key) + if err != nil { + t.Fatal(err) + } + if _, ok := block.(*fipsaes.Block); !ok { + t.Skip("requires crypto/internal/fips140/aes") + } + + keystream := make([]byte, 8*aes.BlockSize) + + testCases := []struct { + name string + hi uint64 + lo uint64 + }{ + {"Zero", 0, 0}, + {"NearOverflowMinus7", 1, ^uint64(0) - 7}, + {"NearOverflowMinus6", 2, ^uint64(0) - 6}, + {"Overflow", 0, ^uint64(0)}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var iv [aes.BlockSize]byte + binary.BigEndian.PutUint64(iv[0:8], tc.hi) + binary.BigEndian.PutUint64(iv[8:], tc.lo) + + generic, multiblock := makeTestingCiphers(block, iv[:]) + + genericOut := make([]byte, len(keystream)) + multiblockOut := make([]byte, len(keystream)) + + generic.XORKeyStream(genericOut, keystream) + multiblock.XORKeyStream(multiblockOut, keystream) + + if !bytes.Equal(multiblockOut, genericOut) { + t.Fatalf("mismatch for iv %#x:%#x\n"+ + "asm keystream: %x\n"+ + "gen keystream: %x\n"+ + "asm counters: %x\n"+ + "gen counters: %x", + tc.hi, tc.lo, multiblockOut, genericOut, + extractCounters(block, multiblockOut), + extractCounters(block, genericOut)) + } + }) + } +} + func randBytes(t *testing.T, r *rand.Rand, count int) []byte { t.Helper() buf := make([]byte, count) @@ -297,3 +352,12 @@ func TestCTR_AES_multiblock_XORKeyStreamAt(t *testing.T) { }) } } + +func extractCounters(block cipher.Block, keystream []byte) []byte { + blockSize := block.BlockSize() + res := make([]byte, len(keystream)) + for i := 0; i < len(keystream); i += blockSize { + block.Decrypt(res[i:i+blockSize], keystream[i:i+blockSize]) + } + return res +} diff --git a/src/crypto/internal/fips140/aes/_asm/ctr/ctr_amd64_asm.go b/src/crypto/internal/fips140/aes/_asm/ctr/ctr_amd64_asm.go index 775d4a8acc5..e3dbdf66d70 100644 --- a/src/crypto/internal/fips140/aes/_asm/ctr/ctr_amd64_asm.go +++ b/src/crypto/internal/fips140/aes/_asm/ctr/ctr_amd64_asm.go @@ -40,19 +40,79 @@ func ctrBlocks(numBlocks int) { bswap := XMM() MOVOU(bswapMask(), bswap) - blocks := make([]VecVirtual, 0, numBlocks) + blocks := make([]VecVirtual, numBlocks) - // Lay out counter block plaintext. - for i := 0; i < numBlocks; i++ { - x := XMM() - blocks = append(blocks, x) + // For the 8-block case we optimize counter generation. We build the first + // counter as usual, then check whether the remaining seven increments will + // overflow. When they do not (the common case) we keep the work entirely in + // XMM registers to avoid expensive general-purpose -> XMM moves. Otherwise + // we fall back to the traditional scalar path. + if numBlocks == 8 { + for i := range blocks { + blocks[i] = XMM() + } - MOVQ(ivlo, x) - PINSRQ(Imm(1), ivhi, x) - PSHUFB(bswap, x) - if i < numBlocks-1 { - ADDQ(Imm(1), ivlo) - ADCQ(Imm(0), ivhi) + base := XMM() + tmp := GP64() + addVec := XMM() + + MOVQ(ivlo, blocks[0]) + PINSRQ(Imm(1), ivhi, blocks[0]) + MOVAPS(blocks[0], base) + PSHUFB(bswap, blocks[0]) + + // Check whether any of these eight counters will overflow. + MOVQ(ivlo, tmp) + ADDQ(Imm(uint64(numBlocks-1)), tmp) + slowLabel := fmt.Sprintf("ctr%d_slow", numBlocks) + doneLabel := fmt.Sprintf("ctr%d_done", numBlocks) + JC(LabelRef(slowLabel)) + + // Fast branch: create an XMM increment vector containing the value 1. + // Adding it to the base counter yields each subsequent counter. + XORQ(tmp, tmp) + INCQ(tmp) + PXOR(addVec, addVec) + PINSRQ(Imm(0), tmp, addVec) + + for i := 1; i < numBlocks; i++ { + PADDQ(addVec, base) + MOVAPS(base, blocks[i]) + } + JMP(LabelRef(doneLabel)) + + Label(slowLabel) + ADDQ(Imm(1), ivlo) + ADCQ(Imm(0), ivhi) + for i := 1; i < numBlocks; i++ { + MOVQ(ivlo, blocks[i]) + PINSRQ(Imm(1), ivhi, blocks[i]) + if i < numBlocks-1 { + ADDQ(Imm(1), ivlo) + ADCQ(Imm(0), ivhi) + } + } + + Label(doneLabel) + + // Convert little-endian counters to big-endian after the branch since + // both paths share the same shuffle sequence. + for i := 1; i < numBlocks; i++ { + PSHUFB(bswap, blocks[i]) + } + } else { + // Lay out counter block plaintext. + for i := 0; i < numBlocks; i++ { + x := XMM() + blocks[i] = x + + MOVQ(ivlo, x) + PINSRQ(Imm(1), ivhi, x) + PSHUFB(bswap, x) + if i < numBlocks-1 { + ADDQ(Imm(1), ivlo) + ADCQ(Imm(0), ivhi) + } } } diff --git a/src/crypto/internal/fips140/aes/ctr_amd64.s b/src/crypto/internal/fips140/aes/ctr_amd64.s index e6710834dd2..deef3e7705a 100644 --- a/src/crypto/internal/fips140/aes/ctr_amd64.s +++ b/src/crypto/internal/fips140/aes/ctr_amd64.s @@ -286,41 +286,68 @@ TEXT ·ctrBlocks8Asm(SB), $0-48 MOVOU bswapMask<>+0(SB), X0 MOVQ SI, X1 PINSRQ $0x01, DI, X1 + MOVAPS X1, X8 PSHUFB X0, X1 + MOVQ SI, R8 + ADDQ $0x07, R8 + JC ctr8_slow + XORQ R8, R8 + INCQ R8 + PXOR X9, X9 + PINSRQ $0x00, R8, X9 + PADDQ X9, X8 + MOVAPS X8, X2 + PADDQ X9, X8 + MOVAPS X8, X3 + PADDQ X9, X8 + MOVAPS X8, X4 + PADDQ X9, X8 + MOVAPS X8, X5 + PADDQ X9, X8 + MOVAPS X8, X6 + PADDQ X9, X8 + MOVAPS X8, X7 + PADDQ X9, X8 + MOVAPS X8, X8 + JMP ctr8_done + +ctr8_slow: ADDQ $0x01, SI ADCQ $0x00, DI MOVQ SI, X2 PINSRQ $0x01, DI, X2 - PSHUFB X0, X2 ADDQ $0x01, SI ADCQ $0x00, DI MOVQ SI, X3 PINSRQ $0x01, DI, X3 - PSHUFB X0, X3 ADDQ $0x01, SI ADCQ $0x00, DI MOVQ SI, X4 PINSRQ $0x01, DI, X4 - PSHUFB X0, X4 ADDQ $0x01, SI ADCQ $0x00, DI MOVQ SI, X5 PINSRQ $0x01, DI, X5 - PSHUFB X0, X5 ADDQ $0x01, SI ADCQ $0x00, DI MOVQ SI, X6 PINSRQ $0x01, DI, X6 - PSHUFB X0, X6 ADDQ $0x01, SI ADCQ $0x00, DI MOVQ SI, X7 PINSRQ $0x01, DI, X7 - PSHUFB X0, X7 ADDQ $0x01, SI ADCQ $0x00, DI MOVQ SI, X8 PINSRQ $0x01, DI, X8 + +ctr8_done: + PSHUFB X0, X2 + PSHUFB X0, X3 + PSHUFB X0, X4 + PSHUFB X0, X5 + PSHUFB X0, X6 + PSHUFB X0, X7 PSHUFB X0, X8 MOVUPS (CX), X0 PXOR X0, X1 From 437d2362ce8ad3e10631aaf90cb4d8c8126d1bac Mon Sep 17 00:00:00 2001 From: qmuntal Date: Wed, 26 Nov 2025 10:25:16 +0100 Subject: [PATCH 060/140] os,internal/poll: don't call IsNonblock for consoles and Stdin windows.IsNonblock can block for synchronous handles that have an outstanding I/O operation. Console handles are always synchronous, so we should not call IsNonblock for them. Stdin is often a pipe, and almost always a synchronous handle, so we should not call IsNonblock for it either. This avoids potential deadlocks during os package initialization, which calls NewFile(syscall.Stdin). Fixes #75949 Updates #76391 Cq-Include-Trybots: luci.golang.try:gotip-windows-amd64-longtest,gotip-windows-amd64-race,gotip-windows-arm64 Change-Id: I1603932b0a99823019aa0cad960f94cee9996505 Reviewed-on: https://go-review.googlesource.com/c/go/+/724640 LUCI-TryBot-Result: Go LUCI Reviewed-by: Damien Neil Auto-Submit: Damien Neil Reviewed-by: Cherry Mui --- src/internal/poll/fd_windows.go | 4 +++ src/os/file_windows.go | 61 +++++++++++++++++++++++++++------ src/os/os_windows_test.go | 39 +++++++++++++++++++++ src/os/removeall_windows.go | 2 +- src/os/root_windows.go | 2 +- 5 files changed, 95 insertions(+), 13 deletions(-) diff --git a/src/internal/poll/fd_windows.go b/src/internal/poll/fd_windows.go index 6443f6eb30b..edad6563508 100644 --- a/src/internal/poll/fd_windows.go +++ b/src/internal/poll/fd_windows.go @@ -451,6 +451,10 @@ func (fd *FD) Init(net string, pollable bool) error { fd.isFile = fd.kind != kindNet fd.isBlocking = !pollable + if !pollable { + return nil + } + // It is safe to add overlapped handles that also perform I/O // outside of the runtime poller. The runtime poller will ignore // I/O completion notifications not initiated by us. diff --git a/src/os/file_windows.go b/src/os/file_windows.go index 83f5fde7f9d..8f0827a23de 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -44,20 +44,60 @@ func (file *File) fd() uintptr { return uintptr(file.pfd.Sysfd) } +// newFileKind describes the kind of file to newFile. +type newFileKind int + +const ( + // kindNewFile means that the descriptor was passed to us via NewFile. + kindNewFile newFileKind = iota + // kindOpenFile means that the descriptor was opened using + // Open, Create, or OpenFile. + kindOpenFile + // kindPipe means that the descriptor was opened using Pipe. + kindPipe + // kindSock means that the descriptor is a network file descriptor + // that was created from net package and was opened using net_newUnixFile. + kindSock + // kindConsole means that the descriptor is a console handle. + kindConsole +) + // newFile returns a new File with the given file handle and name. // Unlike NewFile, it does not check that h is syscall.InvalidHandle. // If nonBlocking is true, it tries to add the file to the runtime poller. -func newFile(h syscall.Handle, name string, kind string, nonBlocking bool) *File { - if kind == "file" { +func newFile(h syscall.Handle, name string, kind newFileKind, nonBlocking bool) *File { + typ := "file" + switch kind { + case kindNewFile, kindOpenFile: t, err := syscall.GetFileType(h) if err != nil || t == syscall.FILE_TYPE_CHAR { var m uint32 if syscall.GetConsoleMode(h, &m) == nil { - kind = "console" + typ = "console" + // Console handles are always blocking. + break } } else if t == syscall.FILE_TYPE_PIPE { - kind = "pipe" + typ = "pipe" } + // NewFile doesn't know if a handle is blocking or non-blocking, + // so we try to detect that here. This call may block/ if the handle + // is blocking and there is an outstanding I/O operation. + // + // Avoid doing this for Stdin, which is almost always blocking and might + // be in use by other process when the "os" package is initializing. + // See go.dev/issue/75949 and go.dev/issue/76391. + if kind == kindNewFile && h != syscall.Stdin { + nonBlocking, _ = windows.IsNonblock(h) + } + case kindPipe: + typ = "pipe" + case kindSock: + typ = "file+net" + case kindConsole: + typ = "console" + default: + panic("newFile with unknown kind") } f := &File{&file{ @@ -72,13 +112,13 @@ func newFile(h syscall.Handle, name string, kind string, nonBlocking bool) *File // Ignore initialization errors. // Assume any problems will show up in later I/O. - f.pfd.Init(kind, nonBlocking) + f.pfd.Init(typ, nonBlocking) return f } // newConsoleFile creates new File that will be used as console. func newConsoleFile(h syscall.Handle, name string) *File { - return newFile(h, name, "console", false) + return newFile(h, name, kindConsole, false) } // newFileFromNewFile is called by [NewFile]. @@ -87,8 +127,7 @@ func newFileFromNewFile(fd uintptr, name string) *File { if h == syscall.InvalidHandle { return nil } - nonBlocking, _ := windows.IsNonblock(syscall.Handle(fd)) - return newFile(h, name, "file", nonBlocking) + return newFile(h, name, kindNewFile, false) } // net_newWindowsFile is a hidden entry point called by net.conn.File. @@ -100,7 +139,7 @@ func net_newWindowsFile(h syscall.Handle, name string) *File { if h == syscall.InvalidHandle { panic("invalid FD") } - return newFile(h, name, "file+net", true) + return newFile(h, name, kindSock, true) } func epipecheck(file *File, e error) { @@ -121,7 +160,7 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) { return nil, &PathError{Op: "open", Path: name, Err: err} } nonblocking := flag&windows.O_FILE_FLAG_OVERLAPPED != 0 - return newFile(r, name, "file", nonblocking), nil + return newFile(r, name, kindOpenFile, nonblocking), nil } func openDirNolog(name string) (*File, error) { @@ -235,7 +274,7 @@ func Pipe() (r *File, w *File, err error) { return nil, nil, NewSyscallError("pipe", e) } // syscall.Pipe always returns a non-blocking handle. - return newFile(p[0], "|0", "pipe", false), newFile(p[1], "|1", "pipe", false), nil + return newFile(p[0], "|0", kindPipe, false), newFile(p[1], "|1", kindPipe, false), nil } var useGetTempPath2 = sync.OnceValue(func() bool { diff --git a/src/os/os_windows_test.go b/src/os/os_windows_test.go index 3e7bddc7911..d549608b482 100644 --- a/src/os/os_windows_test.go +++ b/src/os/os_windows_test.go @@ -2288,3 +2288,42 @@ func TestOpenFileTruncateNamedPipe(t *testing.T) { } f.Close() } + +func TestNewFileStdinBlocked(t *testing.T) { + // See https://go.dev/issue/75949. + t.Parallel() + + // Use a subprocess to test that os.NewFile on a blocked stdin works. + // Can't do it in the same process because os.NewFile would close + // stdin for the whole test process once the test ends. + if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { + // In the child process, just exit. + // If we get here, the os package successfully initialized. + os.Exit(0) + } + name := pipeName() + stdin := newBytePipe(t, name, false) + file := newFileOverlapped(t, name, false) + + var wg sync.WaitGroup + wg.Go(func() { + // Block stdin on a read. + if _, err := stdin.Read(make([]byte, 1)); err != nil { + t.Error(err) + } + }) + + time.Sleep(100 * time.Millisecond) // Give time for the read to start. + cmd := testenv.CommandContext(t, t.Context(), testenv.Executable(t), fmt.Sprintf("-test.run=^%s$", t.Name())) + cmd.Env = cmd.Environ() + cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1") + cmd.Stdin = stdin + if err := cmd.Run(); err != nil { + t.Fatal(err) + } + // Unblock the read to let the goroutine exit. + if _, err := file.Write(make([]byte, 1)); err != nil { + t.Fatal(err) + } + wg.Wait() // Don't leave goroutines behind. +} diff --git a/src/os/removeall_windows.go b/src/os/removeall_windows.go index 5cbc5fb0367..6a1be1cca7b 100644 --- a/src/os/removeall_windows.go +++ b/src/os/removeall_windows.go @@ -13,5 +13,5 @@ func isErrNoFollow(err error) bool { } func newDirFile(fd syscall.Handle, name string) (*File, error) { - return newFile(fd, name, "file", false), nil + return newFile(fd, name, kindOpenFile, false), nil } diff --git a/src/os/root_windows.go b/src/os/root_windows.go index 033a119862d..381e33fc0e5 100644 --- a/src/os/root_windows.go +++ b/src/os/root_windows.go @@ -135,7 +135,7 @@ func rootOpenFileNolog(root *Root, name string, flag int, perm FileMode) (*File, return nil, &PathError{Op: "openat", Path: name, Err: err} } // openat always returns a non-blocking handle. - return newFile(fd, joinPath(root.Name(), name), "file", false), nil + return newFile(fd, joinPath(root.Name(), name), kindOpenFile, false), nil } func openat(dirfd syscall.Handle, name string, flag uint64, perm FileMode) (syscall.Handle, error) { From 37ce4adcd45febab8b5da1fe5ce609f1f6673894 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Wed, 26 Nov 2025 09:49:50 +0100 Subject: [PATCH 061/140] cmd/compile: add tests bruteforcing limit complement Change-Id: I9d8bd78a06738a8a242b6965382e61568e93dea7 Reviewed-on: https://go-review.googlesource.com/c/go/+/724620 Auto-Submit: Jorropo Reviewed-by: Keith Randall LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui Reviewed-by: Keith Randall --- src/cmd/compile/internal/ssa/prove_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/cmd/compile/internal/ssa/prove_test.go b/src/cmd/compile/internal/ssa/prove_test.go index 0044b60a16f..6315049870a 100644 --- a/src/cmd/compile/internal/ssa/prove_test.go +++ b/src/cmd/compile/internal/ssa/prove_test.go @@ -67,3 +67,10 @@ func TestLimitNegSigned(t *testing.T) { func TestLimitNegUnsigned(t *testing.T) { testLimitUnaryOpUnsigned8(t, "neg", limit.neg, func(x uint8) uint8 { return -x }) } + +func TestLimitComSigned(t *testing.T) { + testLimitUnaryOpSigned8(t, "com", limit.com, func(x int8) int8 { return ^x }) +} +func TestLimitComUnsigned(t *testing.T) { + testLimitUnaryOpUnsigned8(t, "com", limit.com, func(x uint8) uint8 { return ^x }) +} From c0f02c11fff439cf3a99dfca34698b583bb3ce48 Mon Sep 17 00:00:00 2001 From: Xiaolin Zhao Date: Tue, 18 Nov 2025 16:00:35 +0800 Subject: [PATCH 062/140] cmd/internal/obj/loong64: add aliases to 32-bit arithmetic instructions Both the MULW and MUL instructions point to the mul.w instruction in the loong64 ISA. Previously, MULW was not encoded; now it is encoded and used as an alias for MUL. The same applies to the following instructions: ADD, SUB, DIV. For consistency, we have added additional aliases for DIVU, REM and REMU. Change-Id: Iba201a3c4c2893ff7d301ef877fad9c81e54291b Reviewed-on: https://go-review.googlesource.com/c/go/+/721523 Reviewed-by: Cherry Mui Auto-Submit: abner chenc Reviewed-by: abner chenc Reviewed-by: Meidan Li Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: Go LUCI --- .../asm/internal/asm/testdata/loong64enc1.s | 16 +++++++++++++ .../asm/internal/asm/testdata/loong64enc2.s | 4 ++++ .../asm/internal/asm/testdata/loong64enc3.s | 4 ++++ src/cmd/internal/obj/loong64/a.out.go | 3 +++ src/cmd/internal/obj/loong64/anames.go | 3 +++ src/cmd/internal/obj/loong64/asm.go | 23 ++++++++++++------- 6 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/cmd/asm/internal/asm/testdata/loong64enc1.s b/src/cmd/asm/internal/asm/testdata/loong64enc1.s index fc6e2774169..20fd0144340 100644 --- a/src/cmd/asm/internal/asm/testdata/loong64enc1.s +++ b/src/cmd/asm/internal/asm/testdata/loong64enc1.s @@ -33,13 +33,17 @@ lable2: MOVV R4, R5 // 85001500 MOVBU R4, R5 // 85fc4303 SUB R4, R5, R6 // a6101100 + SUBW R4, R5, R6 // a6101100 SUBV R4, R5, R6 // a6901100 ADD R4, R5, R6 // a6101000 + ADDW R4, R5, R6 // a6101000 ADDV R4, R5, R6 // a6901000 AND R4, R5, R6 // a6901400 SUB R4, R5 // a5101100 + SUBW R4, R5 // a5101100 SUBV R4, R5 // a5901100 ADD R4, R5 // a5101000 + ADDW R4, R5 // a5101000 ADDV R4, R5 // a5901000 AND R4, R5 // a5901400 NEGW R4, R5 // 05101100 @@ -115,6 +119,8 @@ lable2: MOVV $1, R4 // 04048003 ADD $-1, R4, R5 // 85fcbf02 ADD $-1, R4 // 84fcbf02 + ADDW $-1, R4, R5 // 85fcbf02 + ADDW $-1, R4 // 84fcbf02 ADDV $-1, R4, R5 // 85fcff02 ADDV $-1, R4 // 84fcff02 AND $1, R4, R5 // 85044003 @@ -165,6 +171,8 @@ lable2: // mul MUL R4, R5 // a5101c00 MUL R4, R5, R6 // a6101c00 + MULW R4, R5 // a5101c00 + MULW R4, R5, R6 // a6101c00 MULV R4, R5 // a5901d00 MULV R4, R5, R6 // a6901d00 MULVU R4, R5 // a5901d00 @@ -199,12 +207,20 @@ lable2: MULHU R4, R5, R6 // a6101d00 REM R4, R5 // a5902000 REM R4, R5, R6 // a6902000 + REMW R4, R5 // a5902000 + REMW R4, R5, R6 // a6902000 REMU R4, R5 // a5902100 REMU R4, R5, R6 // a6902100 + REMWU R4, R5 // a5902100 + REMWU R4, R5, R6 // a6902100 DIV R4, R5 // a5102000 DIV R4, R5, R6 // a6102000 + DIVW R4, R5 // a5102000 + DIVW R4, R5, R6 // a6102000 DIVU R4, R5 // a5102100 DIVU R4, R5, R6 // a6102100 + DIVWU R4, R5 // a5102100 + DIVWU R4, R5, R6 // a6102100 SRLV R4, R5 // a5101900 SRLV R4, R5, R6 // a6101900 SRLV $4, R4, R5 // 85104500 diff --git a/src/cmd/asm/internal/asm/testdata/loong64enc2.s b/src/cmd/asm/internal/asm/testdata/loong64enc2.s index 91aed4e2c70..0ac85f32252 100644 --- a/src/cmd/asm/internal/asm/testdata/loong64enc2.s +++ b/src/cmd/asm/internal/asm/testdata/loong64enc2.s @@ -21,6 +21,10 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$0 ADD $4096, R4, R5 // 3e00001485781000 ADD $65536, R4 // 1e02001484781000 ADD $4096, R4 // 3e00001484781000 + ADDW $65536, R4, R5 // 1e02001485781000 + ADDW $4096, R4, R5 // 3e00001485781000 + ADDW $65536, R4 // 1e02001484781000 + ADDW $4096, R4 // 3e00001484781000 ADDV $65536, R4, R5 // 1e02001485f81000 ADDV $4096, R4, R5 // 3e00001485f81000 ADDV $65536, R4 // 1e02001484f81000 diff --git a/src/cmd/asm/internal/asm/testdata/loong64enc3.s b/src/cmd/asm/internal/asm/testdata/loong64enc3.s index 2dc6529dcb0..c8fb1acb396 100644 --- a/src/cmd/asm/internal/asm/testdata/loong64enc3.s +++ b/src/cmd/asm/internal/asm/testdata/loong64enc3.s @@ -11,12 +11,16 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$0 MOVV $4096(R4), R5 // 3e000014de03800385f81000 ADD $74565, R4 // 5e020014de178d0384781000 ADD $4097, R4 // 3e000014de07800384781000 + ADDW $74565, R4 // 5e020014de178d0384781000 + ADDW $4097, R4 // 3e000014de07800384781000 ADDV $74565, R4 // 5e020014de178d0384f81000 ADDV $4097, R4 // 3e000014de07800384f81000 AND $74565, R4 // 5e020014de178d0384f81400 AND $4097, R4 // 3e000014de07800384f81400 ADD $74565, R4, R5 // 5e020014de178d0385781000 ADD $4097, R4, R5 // 3e000014de07800385781000 + ADDW $74565, R4, R5 // 5e020014de178d0385781000 + ADDW $4097, R4, R5 // 3e000014de07800385781000 ADDV $74565, R4, R5 // 5e020014de178d0385f81000 ADDV $4097, R4, R5 // 3e000014de07800385f81000 AND $74565, R4, R5 // 5e020014de178d0385f81400 diff --git a/src/cmd/internal/obj/loong64/a.out.go b/src/cmd/internal/obj/loong64/a.out.go index 96f0889199d..2458fb2e8e8 100644 --- a/src/cmd/internal/obj/loong64/a.out.go +++ b/src/cmd/internal/obj/loong64/a.out.go @@ -468,6 +468,7 @@ const ( ADIVF ADIVU ADIVW + ADIVWU ALL ALLV @@ -508,7 +509,9 @@ const ( ANOR AOR AREM + AREMW AREMU + AREMWU ARFE diff --git a/src/cmd/internal/obj/loong64/anames.go b/src/cmd/internal/obj/loong64/anames.go index 0ee911401f0..18f818cebaa 100644 --- a/src/cmd/internal/obj/loong64/anames.go +++ b/src/cmd/internal/obj/loong64/anames.go @@ -43,6 +43,7 @@ var Anames = []string{ "DIVF", "DIVU", "DIVW", + "DIVWU", "LL", "LLV", "LUI", @@ -74,7 +75,9 @@ var Anames = []string{ "NOR", "OR", "REM", + "REMW", "REMU", + "REMWU", "RFE", "SC", "SCV", diff --git a/src/cmd/internal/obj/loong64/asm.go b/src/cmd/internal/obj/loong64/asm.go index 9aff344931d..6a234600984 100644 --- a/src/cmd/internal/obj/loong64/asm.go +++ b/src/cmd/internal/obj/loong64/asm.go @@ -1428,6 +1428,7 @@ func buildop(ctxt *obj.Link) { opset(AFTINTRNEVD, r0) case AADD: + opset(AADDW, r0) opset(ASGT, r0) opset(ASGTU, r0) opset(AADDU, r0) @@ -1512,18 +1513,24 @@ func buildop(ctxt *obj.Link) { opset(ABSTRINSV, r0) case ASUB: + opset(ASUBW, r0) opset(ASUBU, r0) opset(ANOR, r0) opset(ASUBV, r0) opset(ASUBVU, r0) opset(AMUL, r0) + opset(AMULW, r0) opset(AMULU, r0) opset(AMULH, r0) opset(AMULHU, r0) opset(AREM, r0) + opset(AREMW, r0) opset(AREMU, r0) + opset(AREMWU, r0) opset(ADIV, r0) + opset(ADIVW, r0) opset(ADIVU, r0) + opset(ADIVWU, r0) opset(AMULV, r0) opset(AMULVU, r0) opset(AMULHV, r0) @@ -3244,7 +3251,7 @@ func (c *ctxt0) oprrrr(a obj.As) uint32 { func (c *ctxt0) oprrr(a obj.As) uint32 { switch a { - case AADD: + case AADD, AADDW: return 0x20 << 15 case AADDU: return 0x20 << 15 @@ -3266,7 +3273,7 @@ func (c *ctxt0) oprrr(a obj.As) uint32 { return 0x2c << 15 // orn case AANDN: return 0x2d << 15 // andn - case ASUB: + case ASUB, ASUBW: return 0x22 << 15 case ASUBU, ANEGW: return 0x22 << 15 @@ -3297,7 +3304,7 @@ func (c *ctxt0) oprrr(a obj.As) uint32 { case ASUBVU, ANEGV: return 0x23 << 15 - case AMUL: + case AMUL, AMULW: return 0x38 << 15 // mul.w case AMULU: return 0x38 << 15 // mul.w @@ -3317,17 +3324,17 @@ func (c *ctxt0) oprrr(a obj.As) uint32 { return 0x3e << 15 // mulw.d.w case AMULWVWU: return 0x3f << 15 // mulw.d.wu - case ADIV: + case ADIV, ADIVW: return 0x40 << 15 // div.w - case ADIVU: + case ADIVU, ADIVWU: return 0x42 << 15 // div.wu case ADIVV: return 0x44 << 15 // div.d case ADIVVU: return 0x46 << 15 // div.du - case AREM: + case AREM, AREMW: return 0x41 << 15 // mod.w - case AREMU: + case AREMU, AREMWU: return 0x43 << 15 // mod.wu case AREMV: return 0x45 << 15 // mod.d @@ -4485,7 +4492,7 @@ func (c *ctxt0) opir(a obj.As) uint32 { func (c *ctxt0) opirr(a obj.As) uint32 { switch a { - case AADD, AADDU: + case AADD, AADDW, AADDU: return 0x00a << 22 case ASGT: return 0x008 << 22 From e0a4dffb0c0eff51bb5b170d4ae0492a43de153d Mon Sep 17 00:00:00 2001 From: Guoqi Chen Date: Mon, 24 Nov 2025 20:19:06 +0800 Subject: [PATCH 063/140] cmd/internal/obj/loong64: add {,x}vmadd series instructions support Go asm syntax: VMADD{B, H, W, V} V1, V2, V3 VMSUB{B, H, W, V} V1, V2, V3 XVMADD{B, H, W, V} X1, X2, X3 XVMSUB{B, H, W, V} X1, X2, X3 VMADDWEV{HB, WH, VW,QV}{,U} V1, V2, V3 VMADDWOD{HB, WH, VW,QV}{,U} V1, V2, V3 XVMADDWEV{HB, WH, VW,QV}{,U} X1, X2, X3 XVMADDWOD{HB, WH, VW,QV}{,U} X1, X2, X3 VMADDWEV{HBUB, WHUH, VWUW, QVUV} V1, V2, V3 VMADDWOD{HBUB, WHUH, VWUW, QVUV} V1, V2, V3 XVMADDWEV{HBUB, WHUH, VWUW, QVUV} X1, X2, X3 XVMADDWOD{HBUB, WHUH, VWUW, QVUV} X1, X2, X3 Equivalent platform assembler syntax: vmadd.{b,h,w,d} v3, v2, v1 vmsub.{b,h,w,d} v3, v2, v1 xvmadd.{b,h,w,d} x3, x2, x1 xvmsub.{b,h,w,d} x3, x2, x1 vmaddwev.{h.b, w.h, d.w, q.d}{,u} v3, v2, v1 vmaddwod.{h.b, w.h, d.w, q.d}{,u} v3, v2, v1 xvmaddwev.{h.b, w.h, d.w, q.d}{,u} x3, x2, x1 xvmaddwod.{h.b, w.h, d.w, q.d}{,u} x3, x2, x1 vmaddwev.{h.bu.b, d.wu.w, d.wu.w, q.du.d} v3, v2, v1 vmaddwod.{h.bu.b, d.wu.w, d.wu.w, q.du.d} v3, v2, v1 xvmaddwev.{h.bu.b, d.wu.w, d.wu.w, q.du.d} x3, x2, x1 xvmaddwod.{h.bu.b, d.wu.w, d.wu.w, q.du.d} x3, x2, x1 Change-Id: I2f4aae51045e1596d4744e525a1589586065cf8e Reviewed-on: https://go-review.googlesource.com/c/go/+/724200 Reviewed-by: Cherry Mui Reviewed-by: Meidan Li Reviewed-by: Dmitri Shuralyov Reviewed-by: sophie zhao LUCI-TryBot-Result: Go LUCI Auto-Submit: abner chenc --- .../asm/internal/asm/testdata/loong64enc1.s | 72 +++++++ src/cmd/internal/obj/loong64/a.out.go | 72 +++++++ src/cmd/internal/obj/loong64/anames.go | 64 ++++++ src/cmd/internal/obj/loong64/asm.go | 192 ++++++++++++++++++ 4 files changed, 400 insertions(+) diff --git a/src/cmd/asm/internal/asm/testdata/loong64enc1.s b/src/cmd/asm/internal/asm/testdata/loong64enc1.s index 20fd0144340..42fa5058323 100644 --- a/src/cmd/asm/internal/asm/testdata/loong64enc1.s +++ b/src/cmd/asm/internal/asm/testdata/loong64enc1.s @@ -1163,6 +1163,78 @@ lable2: XVSUBWODVWU X1, X2, X3 // 43043574 XVSUBWODQVU X1, X2, X3 // 43843574 + // [X]VMADD.{B/H/W/D}, [X]VMSUB.{B/H/W/D} instructions + VMADDB V1, V2, V3 // 4304a870 + VMADDH V1, V2, V3 // 4384a870 + VMADDW V1, V2, V3 // 4304a970 + VMADDV V1, V2, V3 // 4384a970 + VMSUBB V1, V2, V3 // 4304aa70 + VMSUBH V1, V2, V3 // 4384aa70 + VMSUBW V1, V2, V3 // 4304ab70 + VMSUBV V1, V2, V3 // 4384ab70 + XVMADDB X1, X2, X3 // 4304a874 + XVMADDH X1, X2, X3 // 4384a874 + XVMADDW X1, X2, X3 // 4304a974 + XVMADDV X1, X2, X3 // 4384a974 + XVMSUBB X1, X2, X3 // 4304aa74 + XVMSUBH X1, X2, X3 // 4384aa74 + XVMSUBW X1, X2, X3 // 4304ab74 + XVMSUBV X1, X2, X3 // 4384ab74 + + // [X]VMADDW{EV/OD}.{H.B/W.H/D.W/Q.D} instructions + VMADDWEVHB V1, V2, V3 // 4304ac70 + VMADDWEVWH V1, V2, V3 // 4384ac70 + VMADDWEVVW V1, V2, V3 // 4304ad70 + VMADDWEVQV V1, V2, V3 // 4384ad70 + VMADDWODHB V1, V2, V3 // 4304ae70 + VMADDWODWH V1, V2, V3 // 4384ae70 + VMADDWODVW V1, V2, V3 // 4304af70 + VMADDWODQV V1, V2, V3 // 4384af70 + XVMADDWEVHB X1, X2, X3 // 4304ac74 + XVMADDWEVWH X1, X2, X3 // 4384ac74 + XVMADDWEVVW X1, X2, X3 // 4304ad74 + XVMADDWEVQV X1, X2, X3 // 4384ad74 + XVMADDWODHB X1, X2, X3 // 4304ae74 + XVMADDWODWH X1, X2, X3 // 4384ae74 + XVMADDWODVW X1, X2, X3 // 4304af74 + XVMADDWODQV X1, X2, X3 // 4384af74 + + // [X]VMADDW{EV/OD}.{H.B/W.H/D.W/Q.D}U instructions + VMADDWEVHBU V1, V2, V3 // 4304b470 + VMADDWEVWHU V1, V2, V3 // 4384b470 + VMADDWEVVWU V1, V2, V3 // 4304b570 + VMADDWEVQVU V1, V2, V3 // 4384b570 + VMADDWODHBU V1, V2, V3 // 4304b670 + VMADDWODWHU V1, V2, V3 // 4384b670 + VMADDWODVWU V1, V2, V3 // 4304b770 + VMADDWODQVU V1, V2, V3 // 4384b770 + XVMADDWEVHBU X1, X2, X3 // 4304b474 + XVMADDWEVWHU X1, X2, X3 // 4384b474 + XVMADDWEVVWU X1, X2, X3 // 4304b574 + XVMADDWEVQVU X1, X2, X3 // 4384b574 + XVMADDWODHBU X1, X2, X3 // 4304b674 + XVMADDWODWHU X1, X2, X3 // 4384b674 + XVMADDWODVWU X1, X2, X3 // 4304b774 + XVMADDWODQVU X1, X2, X3 // 4384b774 + + // [X]VMADDW{EV/OD}.{H.BU.B/W.HU.H/D.WU.W/Q.DU.D} instructions + VMADDWEVHBUB V1, V2, V3 // 4304bc70 + VMADDWEVWHUH V1, V2, V3 // 4384bc70 + VMADDWEVVWUW V1, V2, V3 // 4304bd70 + VMADDWEVQVUV V1, V2, V3 // 4384bd70 + VMADDWODHBUB V1, V2, V3 // 4304be70 + VMADDWODWHUH V1, V2, V3 // 4384be70 + VMADDWODVWUW V1, V2, V3 // 4304bf70 + VMADDWODQVUV V1, V2, V3 // 4384bf70 + XVMADDWEVHBUB X1, X2, X3 // 4304bc74 + XVMADDWEVWHUH X1, X2, X3 // 4384bc74 + XVMADDWEVVWUW X1, X2, X3 // 4304bd74 + XVMADDWEVQVUV X1, X2, X3 // 4384bd74 + XVMADDWODHBUB X1, X2, X3 // 4304be74 + XVMADDWODWHUH X1, X2, X3 // 4384be74 + XVMADDWODVWUW X1, X2, X3 // 4304bf74 + XVMADDWODQVUV X1, X2, X3 // 4384bf74 + // [X]VSHUF4I.{B/H/W/D} instructions VSHUF4IB $0, V2, V1 // 41009073 VSHUF4IB $16, V2, V1 // 41409073 diff --git a/src/cmd/internal/obj/loong64/a.out.go b/src/cmd/internal/obj/loong64/a.out.go index 2458fb2e8e8..38d4b749590 100644 --- a/src/cmd/internal/obj/loong64/a.out.go +++ b/src/cmd/internal/obj/loong64/a.out.go @@ -1227,6 +1227,78 @@ const ( AXVSUBWODVWU AXVSUBWODQVU + AVMADDB + AVMADDH + AVMADDW + AVMADDV + AVMSUBB + AVMSUBH + AVMSUBW + AVMSUBV + + AXVMADDB + AXVMADDH + AXVMADDW + AXVMADDV + AXVMSUBB + AXVMSUBH + AXVMSUBW + AXVMSUBV + + AVMADDWEVHB + AVMADDWEVWH + AVMADDWEVVW + AVMADDWEVQV + AVMADDWODHB + AVMADDWODWH + AVMADDWODVW + AVMADDWODQV + + AVMADDWEVHBU + AVMADDWEVWHU + AVMADDWEVVWU + AVMADDWEVQVU + AVMADDWODHBU + AVMADDWODWHU + AVMADDWODVWU + AVMADDWODQVU + + AVMADDWEVHBUB + AVMADDWEVWHUH + AVMADDWEVVWUW + AVMADDWEVQVUV + AVMADDWODHBUB + AVMADDWODWHUH + AVMADDWODVWUW + AVMADDWODQVUV + + AXVMADDWEVHB + AXVMADDWEVWH + AXVMADDWEVVW + AXVMADDWEVQV + AXVMADDWODHB + AXVMADDWODWH + AXVMADDWODVW + AXVMADDWODQV + + AXVMADDWEVHBU + AXVMADDWEVWHU + AXVMADDWEVVWU + AXVMADDWEVQVU + AXVMADDWODHBU + AXVMADDWODWHU + AXVMADDWODVWU + AXVMADDWODQVU + + AXVMADDWEVHBUB + AXVMADDWEVWHUH + AXVMADDWEVVWUW + AXVMADDWEVQVUV + AXVMADDWODHBUB + AXVMADDWODWHUH + AXVMADDWODVWUW + AXVMADDWODQVUV + AVSHUF4IB AVSHUF4IH AVSHUF4IW diff --git a/src/cmd/internal/obj/loong64/anames.go b/src/cmd/internal/obj/loong64/anames.go index 18f818cebaa..b1fcbce196d 100644 --- a/src/cmd/internal/obj/loong64/anames.go +++ b/src/cmd/internal/obj/loong64/anames.go @@ -695,6 +695,70 @@ var Anames = []string{ "XVSUBWODWHU", "XVSUBWODVWU", "XVSUBWODQVU", + "VMADDB", + "VMADDH", + "VMADDW", + "VMADDV", + "VMSUBB", + "VMSUBH", + "VMSUBW", + "VMSUBV", + "XVMADDB", + "XVMADDH", + "XVMADDW", + "XVMADDV", + "XVMSUBB", + "XVMSUBH", + "XVMSUBW", + "XVMSUBV", + "VMADDWEVHB", + "VMADDWEVWH", + "VMADDWEVVW", + "VMADDWEVQV", + "VMADDWODHB", + "VMADDWODWH", + "VMADDWODVW", + "VMADDWODQV", + "VMADDWEVHBU", + "VMADDWEVWHU", + "VMADDWEVVWU", + "VMADDWEVQVU", + "VMADDWODHBU", + "VMADDWODWHU", + "VMADDWODVWU", + "VMADDWODQVU", + "VMADDWEVHBUB", + "VMADDWEVWHUH", + "VMADDWEVVWUW", + "VMADDWEVQVUV", + "VMADDWODHBUB", + "VMADDWODWHUH", + "VMADDWODVWUW", + "VMADDWODQVUV", + "XVMADDWEVHB", + "XVMADDWEVWH", + "XVMADDWEVVW", + "XVMADDWEVQV", + "XVMADDWODHB", + "XVMADDWODWH", + "XVMADDWODVW", + "XVMADDWODQV", + "XVMADDWEVHBU", + "XVMADDWEVWHU", + "XVMADDWEVVWU", + "XVMADDWEVQVU", + "XVMADDWODHBU", + "XVMADDWODWHU", + "XVMADDWODVWU", + "XVMADDWODQVU", + "XVMADDWEVHBUB", + "XVMADDWEVWHUH", + "XVMADDWEVVWUW", + "XVMADDWEVQVUV", + "XVMADDWODHBUB", + "XVMADDWODWHUH", + "XVMADDWODVWUW", + "XVMADDWODQVUV", "VSHUF4IB", "VSHUF4IH", "VSHUF4IW", diff --git a/src/cmd/internal/obj/loong64/asm.go b/src/cmd/internal/obj/loong64/asm.go index 6a234600984..e5f2014e956 100644 --- a/src/cmd/internal/obj/loong64/asm.go +++ b/src/cmd/internal/obj/loong64/asm.go @@ -1830,6 +1830,38 @@ func buildop(ctxt *obj.Link) { opset(AVSUBWODWHU, r0) opset(AVSUBWODVWU, r0) opset(AVSUBWODQVU, r0) + opset(AVMADDB, r0) + opset(AVMADDH, r0) + opset(AVMADDW, r0) + opset(AVMADDV, r0) + opset(AVMSUBB, r0) + opset(AVMSUBH, r0) + opset(AVMSUBW, r0) + opset(AVMSUBV, r0) + opset(AVMADDWEVHB, r0) + opset(AVMADDWEVWH, r0) + opset(AVMADDWEVVW, r0) + opset(AVMADDWEVQV, r0) + opset(AVMADDWODHB, r0) + opset(AVMADDWODWH, r0) + opset(AVMADDWODVW, r0) + opset(AVMADDWODQV, r0) + opset(AVMADDWEVHBU, r0) + opset(AVMADDWEVWHU, r0) + opset(AVMADDWEVVWU, r0) + opset(AVMADDWEVQVU, r0) + opset(AVMADDWODHBU, r0) + opset(AVMADDWODWHU, r0) + opset(AVMADDWODVWU, r0) + opset(AVMADDWODQVU, r0) + opset(AVMADDWEVHBUB, r0) + opset(AVMADDWEVWHUH, r0) + opset(AVMADDWEVVWUW, r0) + opset(AVMADDWEVQVUV, r0) + opset(AVMADDWODHBUB, r0) + opset(AVMADDWODWHUH, r0) + opset(AVMADDWODVWUW, r0) + opset(AVMADDWODQVUV, r0) case AXVSLTB: opset(AXVSLTH, r0) @@ -1871,6 +1903,38 @@ func buildop(ctxt *obj.Link) { opset(AXVSUBWODWHU, r0) opset(AXVSUBWODVWU, r0) opset(AXVSUBWODQVU, r0) + opset(AXVMADDB, r0) + opset(AXVMADDH, r0) + opset(AXVMADDW, r0) + opset(AXVMADDV, r0) + opset(AXVMSUBB, r0) + opset(AXVMSUBH, r0) + opset(AXVMSUBW, r0) + opset(AXVMSUBV, r0) + opset(AXVMADDWEVHB, r0) + opset(AXVMADDWEVWH, r0) + opset(AXVMADDWEVVW, r0) + opset(AXVMADDWEVQV, r0) + opset(AXVMADDWODHB, r0) + opset(AXVMADDWODWH, r0) + opset(AXVMADDWODVW, r0) + opset(AXVMADDWODQV, r0) + opset(AXVMADDWEVHBU, r0) + opset(AXVMADDWEVWHU, r0) + opset(AXVMADDWEVVWU, r0) + opset(AXVMADDWEVQVU, r0) + opset(AXVMADDWODHBU, r0) + opset(AXVMADDWODWHU, r0) + opset(AXVMADDWODVWU, r0) + opset(AXVMADDWODQVU, r0) + opset(AXVMADDWEVHBUB, r0) + opset(AXVMADDWEVWHUH, r0) + opset(AXVMADDWEVVWUW, r0) + opset(AXVMADDWEVQVUV, r0) + opset(AXVMADDWODHBUB, r0) + opset(AXVMADDWODWHUH, r0) + opset(AXVMADDWODVWUW, r0) + opset(AXVMADDWODQVUV, r0) case AVANDB: opset(AVORB, r0) @@ -3811,6 +3875,134 @@ func (c *ctxt0) oprrr(a obj.As) uint32 { return 0x0E86A << 15 // xvsubwod.d.wu case AXVSUBWODQVU: return 0x0E86B << 15 // xvsubwod.q.du + case AVMADDB: + return 0x0E150 << 15 // vmadd.b + case AVMADDH: + return 0x0E151 << 15 // vmadd.h + case AVMADDW: + return 0x0E152 << 15 // vmadd.w + case AVMADDV: + return 0x0E153 << 15 // vmadd.d + case AVMSUBB: + return 0x0E154 << 15 // vmsub.b + case AVMSUBH: + return 0x0E155 << 15 // vmsub.h + case AVMSUBW: + return 0x0E156 << 15 // vmsub.w + case AVMSUBV: + return 0x0E157 << 15 // vmsub.d + case AXVMADDB: + return 0x0E950 << 15 // xvmadd.b + case AXVMADDH: + return 0x0E951 << 15 // xvmadd.h + case AXVMADDW: + return 0x0E952 << 15 // xvmadd.w + case AXVMADDV: + return 0x0E953 << 15 // xvmadd.d + case AXVMSUBB: + return 0x0E954 << 15 // xvmsub.b + case AXVMSUBH: + return 0x0E955 << 15 // xvmsub.h + case AXVMSUBW: + return 0x0E956 << 15 // xvmsub.w + case AXVMSUBV: + return 0x0E957 << 15 // xvmsub.d + case AVMADDWEVHB: + return 0x0E158 << 15 // vmaddwev.h.b + case AVMADDWEVWH: + return 0x0E159 << 15 // vmaddwev.w.h + case AVMADDWEVVW: + return 0x0E15A << 15 // vmaddwev.d.w + case AVMADDWEVQV: + return 0x0E15B << 15 // vmaddwev.q.d + case AVMADDWODHB: + return 0x0E15C << 15 // vmaddwov.h.b + case AVMADDWODWH: + return 0x0E15D << 15 // vmaddwod.w.h + case AVMADDWODVW: + return 0x0E15E << 15 // vmaddwod.d.w + case AVMADDWODQV: + return 0x0E15F << 15 // vmaddwod.q.d + case AVMADDWEVHBU: + return 0x0E168 << 15 // vmaddwev.h.bu + case AVMADDWEVWHU: + return 0x0E169 << 15 // vmaddwev.w.hu + case AVMADDWEVVWU: + return 0x0E16A << 15 // vmaddwev.d.wu + case AVMADDWEVQVU: + return 0x0E16B << 15 // vmaddwev.q.du + case AVMADDWODHBU: + return 0x0E16C << 15 // vmaddwov.h.bu + case AVMADDWODWHU: + return 0x0E16D << 15 // vmaddwod.w.hu + case AVMADDWODVWU: + return 0x0E16E << 15 // vmaddwod.d.wu + case AVMADDWODQVU: + return 0x0E16F << 15 // vmaddwod.q.du + case AVMADDWEVHBUB: + return 0x0E178 << 15 // vmaddwev.h.bu.b + case AVMADDWEVWHUH: + return 0x0E179 << 15 // vmaddwev.w.hu.h + case AVMADDWEVVWUW: + return 0x0E17A << 15 // vmaddwev.d.wu.w + case AVMADDWEVQVUV: + return 0x0E17B << 15 // vmaddwev.q.du.d + case AVMADDWODHBUB: + return 0x0E17C << 15 // vmaddwov.h.bu.b + case AVMADDWODWHUH: + return 0x0E17D << 15 // vmaddwod.w.hu.h + case AVMADDWODVWUW: + return 0x0E17E << 15 // vmaddwod.d.wu.w + case AVMADDWODQVUV: + return 0x0E17F << 15 // vmaddwod.q.du.d + case AXVMADDWEVHB: + return 0x0E958 << 15 // xvmaddwev.h.b + case AXVMADDWEVWH: + return 0x0E959 << 15 // xvmaddwev.w.h + case AXVMADDWEVVW: + return 0x0E95A << 15 // xvmaddwev.d.w + case AXVMADDWEVQV: + return 0x0E95B << 15 // xvmaddwev.q.d + case AXVMADDWODHB: + return 0x0E95C << 15 // xvmaddwov.h.b + case AXVMADDWODWH: + return 0x0E95D << 15 // xvmaddwod.w.h + case AXVMADDWODVW: + return 0x0E95E << 15 // xvmaddwod.d.w + case AXVMADDWODQV: + return 0x0E95F << 15 // xvmaddwod.q.d + case AXVMADDWEVHBU: + return 0x0E968 << 15 // xvmaddwev.h.bu + case AXVMADDWEVWHU: + return 0x0E969 << 15 // xvmaddwev.w.hu + case AXVMADDWEVVWU: + return 0x0E96A << 15 // xvmaddwev.d.wu + case AXVMADDWEVQVU: + return 0x0E96B << 15 // xvmaddwev.q.du + case AXVMADDWODHBU: + return 0x0E96C << 15 // xvmaddwov.h.bu + case AXVMADDWODWHU: + return 0x0E96D << 15 // xvmaddwod.w.hu + case AXVMADDWODVWU: + return 0x0E96E << 15 // xvmaddwod.d.wu + case AXVMADDWODQVU: + return 0x0E96F << 15 // xvmaddwod.q.du + case AXVMADDWEVHBUB: + return 0x0E978 << 15 // xvmaddwev.h.bu.b + case AXVMADDWEVWHUH: + return 0x0E979 << 15 // xvmaddwev.w.hu.h + case AXVMADDWEVVWUW: + return 0x0E97A << 15 // xvmaddwev.d.wu.w + case AXVMADDWEVQVUV: + return 0x0E97B << 15 // xvmaddwev.q.du.d + case AXVMADDWODHBUB: + return 0x0E97C << 15 // xvmaddwov.h.bu.b + case AXVMADDWODWHUH: + return 0x0E97D << 15 // xvmaddwod.w.hu.h + case AXVMADDWODVWUW: + return 0x0E97E << 15 // xvmaddwod.d.wu.w + case AXVMADDWODQVUV: + return 0x0E97F << 15 // xvmaddwod.q.du.d case AVSLLB: return 0xe1d0 << 15 // vsll.b case AVSLLH: From b194f5d24a71e34f147c90e4351d80ac75be55de Mon Sep 17 00:00:00 2001 From: qmuntal Date: Wed, 26 Nov 2025 12:11:50 +0100 Subject: [PATCH 064/140] os,internal/syscall/windows: support O_* flags in Root.OpenFile These file flags are supported by os.OpenFile since CL 699415. Closes #73676 Change-Id: Ib37102a565f538d394d2a94bd605d6c6004f3028 Reviewed-on: https://go-review.googlesource.com/c/go/+/724621 Auto-Submit: Damien Neil Reviewed-by: Damien Neil LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui --- src/internal/syscall/windows/at_windows.go | 49 ++++++++--- src/internal/syscall/windows/types_windows.go | 15 ++++ src/os/root_windows.go | 14 +-- src/os/root_windows_test.go | 87 +++++++++++++++++++ src/syscall/types_windows.go | 1 - 5 files changed, 148 insertions(+), 18 deletions(-) diff --git a/src/internal/syscall/windows/at_windows.go b/src/internal/syscall/windows/at_windows.go index b7ca8433c2a..de733c523cd 100644 --- a/src/internal/syscall/windows/at_windows.go +++ b/src/internal/syscall/windows/at_windows.go @@ -5,6 +5,7 @@ package windows import ( + "internal/oserror" "runtime" "structs" "syscall" @@ -26,7 +27,6 @@ const ( // to avoid overlap. const ( O_NOFOLLOW_ANY = 0x200000000 // disallow symlinks anywhere in the path - O_OPEN_REPARSE = 0x400000000 // FILE_OPEN_REPARSE_POINT, used by Lstat O_WRITE_ATTRS = 0x800000000 // FILE_WRITE_ATTRIBUTES, used by Chmod ) @@ -36,24 +36,54 @@ func Openat(dirfd syscall.Handle, name string, flag uint64, perm uint32) (_ sysc } var access, options uint32 + // Map Win32 file flags to NT create options. + fileFlags := uint32(flag) & FileFlagsMask + if fileFlags&^ValidFileFlagsMask != 0 { + return syscall.InvalidHandle, oserror.ErrInvalid + } + if fileFlags&O_FILE_FLAG_OVERLAPPED == 0 { + options |= FILE_SYNCHRONOUS_IO_NONALERT + } + if fileFlags&O_FILE_FLAG_DELETE_ON_CLOSE != 0 { + access |= DELETE + } + setOptionFlag := func(ntFlag, win32Flag uint32) { + if fileFlags&win32Flag != 0 { + options |= ntFlag + } + } + setOptionFlag(FILE_NO_INTERMEDIATE_BUFFERING, O_FILE_FLAG_NO_BUFFERING) + setOptionFlag(FILE_WRITE_THROUGH, O_FILE_FLAG_WRITE_THROUGH) + setOptionFlag(FILE_SEQUENTIAL_ONLY, O_FILE_FLAG_SEQUENTIAL_SCAN) + setOptionFlag(FILE_RANDOM_ACCESS, O_FILE_FLAG_RANDOM_ACCESS) + setOptionFlag(FILE_OPEN_FOR_BACKUP_INTENT, O_FILE_FLAG_BACKUP_SEMANTICS) + setOptionFlag(FILE_SESSION_AWARE, O_FILE_FLAG_SESSION_AWARE) + setOptionFlag(FILE_DELETE_ON_CLOSE, O_FILE_FLAG_DELETE_ON_CLOSE) + setOptionFlag(FILE_OPEN_NO_RECALL, O_FILE_FLAG_OPEN_NO_RECALL) + setOptionFlag(FILE_OPEN_REPARSE_POINT, O_FILE_FLAG_OPEN_REPARSE_POINT) + switch flag & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) { case syscall.O_RDONLY: // FILE_GENERIC_READ includes FILE_LIST_DIRECTORY. - access = FILE_GENERIC_READ + access |= FILE_GENERIC_READ case syscall.O_WRONLY: - access = FILE_GENERIC_WRITE + access |= FILE_GENERIC_WRITE options |= FILE_NON_DIRECTORY_FILE case syscall.O_RDWR: - access = FILE_GENERIC_READ | FILE_GENERIC_WRITE + access |= FILE_GENERIC_READ | FILE_GENERIC_WRITE options |= FILE_NON_DIRECTORY_FILE default: // Stat opens files without requesting read or write permissions, // but we still need to request SYNCHRONIZE. - access = SYNCHRONIZE + access |= SYNCHRONIZE } if flag&syscall.O_CREAT != 0 { access |= FILE_GENERIC_WRITE } + if fileFlags&O_FILE_FLAG_NO_BUFFERING != 0 { + // Disable buffering implies no implicit append access. + access &^= FILE_APPEND_DATA + } if flag&syscall.O_APPEND != 0 { access |= FILE_APPEND_DATA // Remove FILE_WRITE_DATA access unless O_TRUNC is set, @@ -82,14 +112,13 @@ func Openat(dirfd syscall.Handle, name string, flag uint64, perm uint32) (_ sysc if flag&syscall.O_CLOEXEC == 0 { objAttrs.Attributes |= OBJ_INHERIT } + if fileFlags&O_FILE_FLAG_POSIX_SEMANTICS == 0 { + objAttrs.Attributes |= OBJ_CASE_INSENSITIVE + } if err := objAttrs.init(dirfd, name); err != nil { return syscall.InvalidHandle, err } - if flag&O_OPEN_REPARSE != 0 { - options |= FILE_OPEN_REPARSE_POINT - } - // We don't use FILE_OVERWRITE/FILE_OVERWRITE_IF, because when opening // a file with FILE_ATTRIBUTE_READONLY these will replace an existing // file with a new, read-only one. @@ -121,7 +150,7 @@ func Openat(dirfd syscall.Handle, name string, flag uint64, perm uint32) (_ sysc fileAttrs, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, disposition, - FILE_SYNCHRONOUS_IO_NONALERT|FILE_OPEN_FOR_BACKUP_INTENT|options, + FILE_OPEN_FOR_BACKUP_INTENT|options, nil, 0, ) diff --git a/src/internal/syscall/windows/types_windows.go b/src/internal/syscall/windows/types_windows.go index 49daf9b31b1..568f94624c4 100644 --- a/src/internal/syscall/windows/types_windows.go +++ b/src/internal/syscall/windows/types_windows.go @@ -210,6 +210,7 @@ const ( FILE_NO_COMPRESSION = 0x00008000 FILE_OPEN_REQUIRING_OPLOCK = 0x00010000 FILE_DISALLOW_EXCLUSIVE = 0x00020000 + FILE_SESSION_AWARE = 0x00040000 FILE_RESERVE_OPFILTER = 0x00100000 FILE_OPEN_REPARSE_POINT = 0x00200000 FILE_OPEN_NO_RECALL = 0x00400000 @@ -286,3 +287,17 @@ const VER_NT_WORKSTATION = 0x0000001 type MemoryBasicInformation = windows.MemoryBasicInformation type Context = windows.Context + +const FileFlagsMask = 0xFFF00000 + +const ValidFileFlagsMask = O_FILE_FLAG_OPEN_REPARSE_POINT | + O_FILE_FLAG_BACKUP_SEMANTICS | + O_FILE_FLAG_OVERLAPPED | + O_FILE_FLAG_OPEN_NO_RECALL | + O_FILE_FLAG_SESSION_AWARE | + O_FILE_FLAG_POSIX_SEMANTICS | + O_FILE_FLAG_DELETE_ON_CLOSE | + O_FILE_FLAG_SEQUENTIAL_SCAN | + O_FILE_FLAG_NO_BUFFERING | + O_FILE_FLAG_RANDOM_ACCESS | + O_FILE_FLAG_WRITE_THROUGH diff --git a/src/os/root_windows.go b/src/os/root_windows.go index 381e33fc0e5..da995e6d7f5 100644 --- a/src/os/root_windows.go +++ b/src/os/root_windows.go @@ -134,8 +134,8 @@ func rootOpenFileNolog(root *Root, name string, flag int, perm FileMode) (*File, if err != nil { return nil, &PathError{Op: "openat", Path: name, Err: err} } - // openat always returns a non-blocking handle. - return newFile(fd, joinPath(root.Name(), name), kindOpenFile, false), nil + nonblocking := flag&windows.O_FILE_FLAG_OVERLAPPED != 0 + return newFile(fd, joinPath(root.Name(), name), kindOpenFile, nonblocking), nil } func openat(dirfd syscall.Handle, name string, flag uint64, perm FileMode) (syscall.Handle, error) { @@ -213,7 +213,7 @@ func rootStat(r *Root, name string, lstat bool) (FileInfo, error) { lstat = false } fi, err := doInRoot(r, name, nil, func(parent syscall.Handle, n string) (FileInfo, error) { - fd, err := openat(parent, n, windows.O_OPEN_REPARSE, 0) + fd, err := openat(parent, n, windows.O_FILE_FLAG_OPEN_REPARSE_POINT, 0) if err != nil { return nil, err } @@ -290,7 +290,7 @@ func chmodat(parent syscall.Handle, name string, mode FileMode) error { // This may or may not be the desired behavior: https://go.dev/issue/71492 // // For now, be consistent with os.Symlink. - // Passing O_OPEN_REPARSE causes us to open the named file itself, + // Passing O_FILE_FLAG_OPEN_REPARSE_POINT causes us to open the named file itself, // not any file that it links to. // // If we want to change this in the future, pass O_NOFOLLOW_ANY instead @@ -301,7 +301,7 @@ func chmodat(parent syscall.Handle, name string, mode FileMode) error { // return errSymlink(link) // } // } - h, err := windows.Openat(parent, name, syscall.O_CLOEXEC|windows.O_OPEN_REPARSE|windows.O_WRITE_ATTRS, 0) + h, err := windows.Openat(parent, name, syscall.O_CLOEXEC|windows.O_FILE_FLAG_OPEN_REPARSE_POINT|windows.O_WRITE_ATTRS, 0) if err != nil { return err } @@ -382,7 +382,7 @@ func linkat(oldfd syscall.Handle, oldname string, newfd syscall.Handle, newname } func readlinkat(dirfd syscall.Handle, name string) (string, error) { - fd, err := openat(dirfd, name, windows.O_OPEN_REPARSE, 0) + fd, err := openat(dirfd, name, windows.O_FILE_FLAG_OPEN_REPARSE_POINT, 0) if err != nil { return "", err } @@ -391,7 +391,7 @@ func readlinkat(dirfd syscall.Handle, name string) (string, error) { } func modeAt(parent syscall.Handle, name string) (FileMode, error) { - fd, err := openat(parent, name, windows.O_OPEN_REPARSE|windows.O_DIRECTORY, 0) + fd, err := openat(parent, name, windows.O_FILE_FLAG_OPEN_REPARSE_POINT|windows.O_DIRECTORY, 0) if err != nil { return 0, err } diff --git a/src/os/root_windows_test.go b/src/os/root_windows_test.go index 47643f98d10..31d54f25716 100644 --- a/src/os/root_windows_test.go +++ b/src/os/root_windows_test.go @@ -9,6 +9,7 @@ package os_test import ( "errors" "fmt" + "internal/strconv" "internal/syscall/windows" "os" "path/filepath" @@ -247,3 +248,89 @@ func TestRootOpenFileTruncateNamedPipe(t *testing.T) { } f.Close() } + +func TestRootOpenFileFlags(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + root, err := os.OpenRoot(dir) + if err != nil { + t.Fatal(err) + } + defer root.Close() + + // The only way to retrieve some of the flags passed in CreateFile + // is using NtQueryInformationFile, which returns the file flags + // NT equivalent. Note that FILE_SYNCHRONOUS_IO_NONALERT is always + // set when FILE_FLAG_OVERLAPPED is not passed. + // The flags that can't be retrieved using NtQueryInformationFile won't + // be tested in here, but we at least know that the logic to handle them is correct. + tests := []struct { + flag uint32 + wantMode uint32 + }{ + {0, windows.FILE_SYNCHRONOUS_IO_NONALERT}, + {windows.O_FILE_FLAG_OVERLAPPED, 0}, + {windows.O_FILE_FLAG_NO_BUFFERING, windows.FILE_NO_INTERMEDIATE_BUFFERING | windows.FILE_SYNCHRONOUS_IO_NONALERT}, + {windows.O_FILE_FLAG_NO_BUFFERING | windows.O_FILE_FLAG_OVERLAPPED, windows.FILE_NO_INTERMEDIATE_BUFFERING}, + {windows.O_FILE_FLAG_SEQUENTIAL_SCAN, windows.FILE_SEQUENTIAL_ONLY | windows.FILE_SYNCHRONOUS_IO_NONALERT}, + {windows.O_FILE_FLAG_WRITE_THROUGH, windows.FILE_WRITE_THROUGH | windows.FILE_SYNCHRONOUS_IO_NONALERT}, + } + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + f, err := root.OpenFile(strconv.Itoa(i)+".txt", syscall.O_RDWR|syscall.O_CREAT|int(tt.flag), 0666) + if err != nil { + t.Fatal(err) + } + defer f.Close() + var info windows.FILE_MODE_INFORMATION + if err := windows.NtQueryInformationFile(syscall.Handle(f.Fd()), &windows.IO_STATUS_BLOCK{}, + unsafe.Pointer(&info), uint32(unsafe.Sizeof(info)), windows.FileModeInformation); err != nil { + t.Fatal(err) + } + if info.Mode != tt.wantMode { + t.Errorf("file mode = 0x%x; want 0x%x", info.Mode, tt.wantMode) + } + }) + } +} + +func TestRootOpenFileDeleteOnClose(t *testing.T) { + t.Parallel() + dir := t.TempDir() + root, err := os.OpenRoot(dir) + if err != nil { + t.Fatal(err) + } + defer root.Close() + const name = "test.txt" + f, err := root.OpenFile(name, syscall.O_RDWR|syscall.O_CREAT|windows.O_FILE_FLAG_DELETE_ON_CLOSE, 0666) + if err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + // The file should be deleted after closing. + if _, err := os.Stat(filepath.Join(dir, name)); !errors.Is(err, os.ErrNotExist) { + t.Errorf("expected file to be deleted, got %v", err) + } +} + +func TestRootOpenFileFlagInvalid(t *testing.T) { + t.Parallel() + dir := t.TempDir() + root, err := os.OpenRoot(dir) + if err != nil { + t.Fatal(err) + } + defer root.Close() + // invalidFileFlag is the only value in the file flag range that is not supported, + // as it is not defined in the Windows API. + const invalidFileFlag = 0x00400000 + f, err := root.OpenFile("test.txt", syscall.O_RDWR|syscall.O_CREAT|invalidFileFlag, 0666) + if !errors.Is(err, os.ErrInvalid) { + t.Fatalf("expected os.ErrInvalid, got %v", err) + } + f.Close() +} diff --git a/src/syscall/types_windows.go b/src/syscall/types_windows.go index 3c6d18a8509..1bffb2769d2 100644 --- a/src/syscall/types_windows.go +++ b/src/syscall/types_windows.go @@ -54,7 +54,6 @@ const ( o_DIRECTORY = 0x04000 O_CLOEXEC = 0x80000 o_NOFOLLOW_ANY = 0x200000000 // used by internal/syscall/windows - o_OPEN_REPARSE = 0x400000000 // used by internal/syscall/windows o_WRITE_ATTRS = 0x800000000 // used by internal/syscall/windows ) From fb5156a0981c2b89a118695138194e1af162ac8e Mon Sep 17 00:00:00 2001 From: Junyang Shao Date: Wed, 26 Nov 2025 19:15:51 +0000 Subject: [PATCH 065/140] testing: fix bloop doc This CL deletes the compiler detail part from bloop documentation. Change-Id: I73933707a593d4958e2300416d15e7213f001c3a Reviewed-on: https://go-review.googlesource.com/c/go/+/724800 LUCI-TryBot-Result: Go LUCI Reviewed-by: David Chase --- src/testing/benchmark.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/testing/benchmark.go b/src/testing/benchmark.go index fbd82beb847..9352e738095 100644 --- a/src/testing/benchmark.go +++ b/src/testing/benchmark.go @@ -483,14 +483,12 @@ func (b *B) loopSlowPath() bool { // the timer so cleanup code is not measured. // // Within the body of a "for b.Loop() { ... }" loop, arguments to and -// results from function calls and assignment receivers within the loop are kept +// results from function calls and assigned variables within the loop are kept // alive, preventing the compiler from fully optimizing away the loop body. // Currently, this is implemented as a compiler transformation that wraps such -// variables with a runtime.KeepAlive intrinsic call. The compiler can recursively -// walk the body of for, if statments, the cases of switch, select statments -// and bracket-braced blocks. This applies only to statements syntactically between -// the curly braces of the loop, and the loop condition must be written exactly -// as "b.Loop()". +// variables with a runtime.KeepAlive intrinsic call. This applies only to +// statements syntactically between the curly braces of the loop, and the loop +// condition must be written exactly as "b.Loop()". // // After Loop returns false, b.N contains the total number of iterations that // ran, so the benchmark may use b.N to compute other average metrics. From ac3369242d3a6d6219fdf1d592effc5e51ddfeb8 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Thu, 23 Oct 2025 10:54:00 +0200 Subject: [PATCH 066/140] runtime: merge all the linux 32 and 64 bits files into one for each Change-Id: I57067c9724dad2fba518b900d6f6a049cc32099e Reviewed-on: https://go-review.googlesource.com/c/go/+/714081 LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Pratt Reviewed-by: Junyang Shao --- .../{os_linux_settime32.go => os_linux32.go} | 39 ++++++++++++++++-- .../{os_linux_futex64.go => os_linux64.go} | 3 ++ src/runtime/os_linux_futex32.go | 40 ------------------- src/runtime/os_linux_settime64.go | 10 ----- 4 files changed, 38 insertions(+), 54 deletions(-) rename src/runtime/{os_linux_settime32.go => os_linux32.go} (55%) rename src/runtime/{os_linux_futex64.go => os_linux64.go} (81%) delete mode 100644 src/runtime/os_linux_futex32.go delete mode 100644 src/runtime/os_linux_settime64.go diff --git a/src/runtime/os_linux_settime32.go b/src/runtime/os_linux32.go similarity index 55% rename from src/runtime/os_linux_settime32.go rename to src/runtime/os_linux32.go index e6c5d9f95c0..16de6fb350f 100644 --- a/src/runtime/os_linux_settime32.go +++ b/src/runtime/os_linux32.go @@ -6,9 +6,38 @@ package runtime -import "internal/runtime/atomic" +import ( + "internal/runtime/atomic" + "unsafe" +) -var timer32bitOnly atomic.Bool +//go:noescape +func futex_time32(addr unsafe.Pointer, op int32, val uint32, ts *timespec32, addr2 unsafe.Pointer, val3 uint32) int32 + +//go:noescape +func futex_time64(addr unsafe.Pointer, op int32, val uint32, ts *timespec, addr2 unsafe.Pointer, val3 uint32) int32 + +var isFutexTime32bitOnly atomic.Bool + +//go:nosplit +func futex(addr unsafe.Pointer, op int32, val uint32, ts *timespec, addr2 unsafe.Pointer, val3 uint32) int32 { + if !isFutexTime32bitOnly.Load() { + ret := futex_time64(addr, op, val, ts, addr2, val3) + // futex_time64 is only supported on Linux 5.0+ + if ret != -_ENOSYS { + return ret + } + isFutexTime32bitOnly.Store(true) + } + // Downgrade ts. + var ts32 timespec32 + var pts32 *timespec32 + if ts != nil { + ts32.setNsec(ts.tv_sec*1e9 + ts.tv_nsec) + pts32 = &ts32 + } + return futex_time32(addr, op, val, pts32, addr2, val3) +} //go:noescape func timer_settime32(timerid int32, flags int32, new, old *itimerspec32) int32 @@ -16,15 +45,17 @@ func timer_settime32(timerid int32, flags int32, new, old *itimerspec32) int32 //go:noescape func timer_settime64(timerid int32, flags int32, new, old *itimerspec) int32 +var isSetTime32bitOnly atomic.Bool + //go:nosplit func timer_settime(timerid int32, flags int32, new, old *itimerspec) int32 { - if !timer32bitOnly.Load() { + if !isSetTime32bitOnly.Load() { ret := timer_settime64(timerid, flags, new, old) // timer_settime64 is only supported on Linux 5.0+ if ret != -_ENOSYS { return ret } - timer32bitOnly.Store(true) + isSetTime32bitOnly.Store(true) } var newts, oldts itimerspec32 diff --git a/src/runtime/os_linux_futex64.go b/src/runtime/os_linux64.go similarity index 81% rename from src/runtime/os_linux_futex64.go rename to src/runtime/os_linux64.go index 2448e30cf1b..7b70d80fbe5 100644 --- a/src/runtime/os_linux_futex64.go +++ b/src/runtime/os_linux64.go @@ -12,3 +12,6 @@ import ( //go:noescape func futex(addr unsafe.Pointer, op int32, val uint32, ts *timespec, addr2 unsafe.Pointer, val3 uint32) int32 + +//go:noescape +func timer_settime(timerid int32, flags int32, new, old *itimerspec) int32 diff --git a/src/runtime/os_linux_futex32.go b/src/runtime/os_linux_futex32.go deleted file mode 100644 index c5cffa24d15..00000000000 --- a/src/runtime/os_linux_futex32.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2025 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 linux && (386 || arm || mips || mipsle || (gccgo && (ppc || s390))) - -package runtime - -import ( - "internal/runtime/atomic" - "unsafe" -) - -//go:noescape -func futex_time32(addr unsafe.Pointer, op int32, val uint32, ts *timespec32, addr2 unsafe.Pointer, val3 uint32) int32 - -//go:noescape -func futex_time64(addr unsafe.Pointer, op int32, val uint32, ts *timespec, addr2 unsafe.Pointer, val3 uint32) int32 - -var is32bitOnly atomic.Bool - -//go:nosplit -func futex(addr unsafe.Pointer, op int32, val uint32, ts *timespec, addr2 unsafe.Pointer, val3 uint32) int32 { - if !is32bitOnly.Load() { - ret := futex_time64(addr, op, val, ts, addr2, val3) - // futex_time64 is only supported on Linux 5.0+ - if ret != -_ENOSYS { - return ret - } - is32bitOnly.Store(true) - } - // Downgrade ts. - var ts32 timespec32 - var pts32 *timespec32 - if ts != nil { - ts32.setNsec(ts.tv_sec*1e9 + ts.tv_nsec) - pts32 = &ts32 - } - return futex_time32(addr, op, val, pts32, addr2, val3) -} diff --git a/src/runtime/os_linux_settime64.go b/src/runtime/os_linux_settime64.go deleted file mode 100644 index dfacf5612d8..00000000000 --- a/src/runtime/os_linux_settime64.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2025 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 linux && !(386 || arm || mips || mipsle || (gccgo && (ppc || s390))) - -package runtime - -//go:noescape -func timer_settime(timerid int32, flags int32, new, old *itimerspec) int32 From efe9ad501d94743d87e500a7ba0b1732a89e9afd Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 25 Nov 2025 16:26:25 -0800 Subject: [PATCH 067/140] go/types, types2: improve printing of []*operand lists (debugging support) Special-case an sprintf argument of []*operand type, similar to what we do for other lists. As a result a list of operands is printed as [a, b, c] rather than [a b c] (default formatting for slices). (We could factor out this code into a generic function, but this is a minimally intrusive change at this point.) Change-Id: Iea4fc6ea375dd9618316b7317a77b57b4e35544d Reviewed-on: https://go-review.googlesource.com/c/go/+/724500 Reviewed-by: Robert Griesemer LUCI-TryBot-Result: Go LUCI Reviewed-by: Mark Freeman Auto-Submit: Robert Griesemer --- src/cmd/compile/internal/types2/format.go | 11 +++++++++++ src/go/types/format.go | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/cmd/compile/internal/types2/format.go b/src/cmd/compile/internal/types2/format.go index b61dfda1c81..d7aa2399c7b 100644 --- a/src/cmd/compile/internal/types2/format.go +++ b/src/cmd/compile/internal/types2/format.go @@ -23,6 +23,17 @@ func sprintf(qf Qualifier, tpSubscripts bool, format string, args ...any) string panic("got operand instead of *operand") case *operand: arg = operandString(a, qf) + case []*operand: + var buf strings.Builder + buf.WriteByte('[') + for i, x := range a { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(operandString(x, qf)) + } + buf.WriteByte(']') + arg = buf.String() case syntax.Pos: arg = a.String() case syntax.Expr: diff --git a/src/go/types/format.go b/src/go/types/format.go index 550d22f5aeb..8df72f98e6f 100644 --- a/src/go/types/format.go +++ b/src/go/types/format.go @@ -24,6 +24,17 @@ func sprintf(fset *token.FileSet, qf Qualifier, tpSubscripts bool, format string panic("got operand instead of *operand") case *operand: arg = operandString(a, qf) + case []*operand: + var buf strings.Builder + buf.WriteByte('[') + for i, x := range a { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(operandString(x, qf)) + } + buf.WriteByte(']') + arg = buf.String() case token.Pos: if fset != nil { arg = fset.Position(a).String() From 3c6bf6fbf38062b24a7cf0390f1e617d733851b3 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Thu, 20 Nov 2025 09:42:16 -0800 Subject: [PATCH 068/140] cmd/compile: handle loops better during stack allocation of slices Don't use the move2heap optimization if the move2heap is inside a loop deeper than the declaration of the slice. We really only want to do the move2heap operation once. Change-Id: I4a68d01609c2c9d4e0abe4580839e70059393a81 Reviewed-on: https://go-review.googlesource.com/c/go/+/722440 Reviewed-by: David Chase Reviewed-by: Junyang Shao LUCI-TryBot-Result: Go LUCI --- src/cmd/compile/internal/slice/slice.go | 22 ++++++++++++++++- test/codegen/append.go | 32 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/cmd/compile/internal/slice/slice.go b/src/cmd/compile/internal/slice/slice.go index 7a32e7adbd2..17eba5b7726 100644 --- a/src/cmd/compile/internal/slice/slice.go +++ b/src/cmd/compile/internal/slice/slice.go @@ -152,6 +152,11 @@ func analyze(fn *ir.Func) { // least weight 2. (Note: appends in loops have weight >= 2.) appendWeight int + // Loop depth at declaration point. + // Use for heuristics only, it is not guaranteed to be correct + // in the presence of gotos. + declDepth int + // Whether we ever do cap(s), or other operations that use cap(s) // (possibly implicitly), like s[i:j]. capUsed bool @@ -209,6 +214,20 @@ func analyze(fn *ir.Func) { i.s.Opt = nil return } + if loopDepth > i.declDepth { + // Conservatively, we disable this optimization when the + // transition is inside a loop. This can result in adding + // overhead unnecessarily in cases like: + // func f(n int, p *[]byte) { + // var s []byte + // for i := range n { + // *p = s + // s = append(s, 0) + // } + // } + i.s.Opt = nil + return + } i.transition = loc } @@ -237,7 +256,7 @@ func analyze(fn *ir.Func) { // s = append(s, ...) is ok i.okUses += 2 i.appends = append(i.appends, y) - i.appendWeight += 1 + loopDepth + i.appendWeight += 1 + (loopDepth - i.declDepth) } // TODO: s = append(nil, ...)? } @@ -277,6 +296,7 @@ func analyze(fn *ir.Func) { n := n.(*ir.Decl) if i := tracking(n.X); i != nil { i.okUses++ + i.declDepth = loopDepth } case ir.OINDEX: n := n.(*ir.IndexExpr) diff --git a/test/codegen/append.go b/test/codegen/append.go index 0e58a48c458..e90fa87ed2c 100644 --- a/test/codegen/append.go +++ b/test/codegen/append.go @@ -185,6 +185,38 @@ func Append17(n int) []int { return r } +func Append18(n int, p *[]int) { + var r []int + for i := range n { + // amd64:-`.*moveSliceNoCapNoScan` + *p = r + // amd64:`.*growslice` + r = append(r, i) + } +} + +func Append19(n int, p [][]int) { + for j := range p { + var r []int + for i := range n { + // amd64:`.*growslice` + r = append(r, i) + } + // amd64:`.*moveSliceNoCapNoScan` + p[j] = r + } +} + +func Append20(n int, p [][]int) { + for j := range p { + var r []int + // amd64:`.*growslice` + r = append(r, 0) + // amd64:-`.*moveSliceNoCapNoScan` + p[j] = r + } +} + //go:noinline func useSlice(s []int) { } From 623ef2813579c9b52ba4a0335722df4d93566b74 Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Mon, 24 Nov 2025 18:33:30 -0500 Subject: [PATCH 069/140] cmd/go: limit total compile -c backend concurrency using a pool Previously we limited the value we passed in to compile -c (which set the number of SSA compile goroutines that run at one time) to 4. This CL allows the -c value to go up to GOMAXPROCS, while limiting the total number of backend SSA compile goroutines to still be less than the previous worst case of 4*GOMAXPROCS (actually four times the value of the -p flag, but the default is GOMAXPROCS). We do that by keeping a pool of tokens to represent the total number of SSA compile goroutines (with some buffer to allow us to run out of tokens and not exceed 4*GOMAXPROCS). Each time a compile requests a -c value, we'll hand out half of the remaining tokens (with the number handed otu capped at GOMAXPROCS) until we run out of tokens, in wich case we'll set -c to one. This leads to a speed up of 3-10% on the 16 core intel perf builder and 5-16% on the 88 core builder on the Sweet go-build benchmark. Change-Id: Ib1ec843fee57f0fb8d36a507162317276a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/724142 Reviewed-by: Alan Donovan LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Matloob --- src/cmd/go/internal/work/gc.go | 77 +++++++++++++++++++++++--------- src/cmd/go/internal/work/init.go | 1 + 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/src/cmd/go/internal/work/gc.go b/src/cmd/go/internal/work/gc.go index 71c36e80cba..bbb087b436b 100644 --- a/src/cmd/go/internal/work/gc.go +++ b/src/cmd/go/internal/work/gc.go @@ -15,6 +15,7 @@ import ( "path/filepath" "runtime" "strings" + "sync" "cmd/go/internal/base" "cmd/go/internal/cfg" @@ -127,7 +128,9 @@ func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg gcflags = append(gcflags, fuzzInstrumentFlags()...) } // Add -c=N to use concurrent backend compilation, if possible. - if c := gcBackendConcurrency(gcflags); c > 1 { + c, release := compilerConcurrency() + defer release() + if c > 1 { defaultGcFlags = append(defaultGcFlags, fmt.Sprintf("-c=%d", c)) } @@ -177,8 +180,9 @@ func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg return ofile, output, err } -// gcBackendConcurrency returns the backend compiler concurrency level for a package compilation. -func gcBackendConcurrency(gcflags []string) int { +// compilerConcurrency returns the compiler concurrency level for a package compilation. +// The returned function must be called after the compile finishes. +func compilerConcurrency() (int, func()) { // First, check whether we can use -c at all for this compilation. canDashC := concurrentGCBackendCompilationEnabledByDefault @@ -199,7 +203,7 @@ func gcBackendConcurrency(gcflags []string) int { } if !canDashC { - return 1 + return 1, func() {} } // Decide how many concurrent backend compilations to allow. @@ -212,29 +216,60 @@ func gcBackendConcurrency(gcflags []string) int { // of the overall compiler execution, so c==1 for much of the build. // So don't worry too much about that interaction for now. // - // However, in practice, setting c above 4 tends not to help very much. - // See the analysis in CL 41192. + // But to keep things reasonable, we maintain a cap on the total number of + // concurrent backend compiles. (If we gave each compile action the full GOMAXPROCS, we could + // potentially have GOMAXPROCS^2 running compile goroutines) In the past, we'd limit + // the number of concurrent backend compiles per process to 4, which would result in a worst-case number + // of backend compiles of 4*cfg.BuildP. Because some compile processes benefit from having + // a larger number of compiles, especially when the compile action is the only + // action running, we'll allow the max value to be larger, but ensure that the + // total number of backend compiles never exceeds that previous worst-case number. + // This is implemented using a pool of tokens that are given out. We'll set aside enough + // tokens to make sure we don't run out, and then give half of the remaining tokens (up to + // GOMAXPROCS) to each compile action that requests it. // - // TODO(josharian): attempt to detect whether this particular compilation - // is likely to be a bottleneck, e.g. when: - // - it has no successor packages to compile (usually package main) - // - all paths through the build graph pass through it - // - critical path scheduling says it is high priority - // and in such a case, set c to runtime.GOMAXPROCS(0). - // By default this is the same as runtime.NumCPU. - // We do this now when p==1. - // To limit parallelism, set GOMAXPROCS below numCPU; this may be useful + // As a user, to limit parallelism, set GOMAXPROCS below numCPU; this may be useful // on a low-memory builder, or if a deterministic build order is required. - c := runtime.GOMAXPROCS(0) if cfg.BuildP == 1 { // No process parallelism, do not cap compiler parallelism. - return c + return maxCompilerConcurrency, func() {} } - // Some process parallelism. Set c to min(4, maxprocs). - if c > 4 { - c = 4 + + // Cap compiler parallelism using the pool. + tokensMu.Lock() + defer tokensMu.Unlock() + concurrentProcesses++ + // Set aside tokens so that we don't run out if we were running cfg.BuildP concurrent compiles. + // We'll set aside one token for each of the action goroutines that aren't currently running a compile. + setAside := cfg.BuildP - concurrentProcesses + availableTokens := tokens - setAside + // Grab half the remaining tokens: but with a floor of at least 1 token, and + // a ceiling of the max backend concurrency. + c := max(min(availableTokens/2, maxCompilerConcurrency), 1) + tokens -= c + // Successfully grabbed the tokens. + return c, func() { + tokensMu.Lock() + defer tokensMu.Unlock() + tokens += c } - return c +} + +var maxCompilerConcurrency = runtime.GOMAXPROCS(0) // max value we will use for -c + +var ( + tokensMu sync.Mutex + tokens int // number of available tokens + concurrentProcesses int // number of currently running compiles +) + +// initCompilerConcurrencyPool sets the number of tokens in the pool. It needs +// to be run after init, so that it can use the value of cfg.BuildP. +func initCompilerConcurrencyPool() { + // Size the pool so that the worst case total number of compiles is not more + // than what it was when we capped the concurrency to 4. + oldConcurrencyCap := min(4, maxCompilerConcurrency) + tokens = oldConcurrencyCap * cfg.BuildP } // trimpath returns the -trimpath argument to use diff --git a/src/cmd/go/internal/work/init.go b/src/cmd/go/internal/work/init.go index a2954ab91ab..964d6e8363a 100644 --- a/src/cmd/go/internal/work/init.go +++ b/src/cmd/go/internal/work/init.go @@ -60,6 +60,7 @@ func BuildInit(loaderstate *modload.State) { modload.Init(loaderstate) instrumentInit() buildModeInit() + initCompilerConcurrencyPool() cfgChangedEnv = makeCfgChangedEnv() if err := fsys.Init(); err != nil { From e2cae9ecdf944a1cc5d8803ff8932180858b8ce6 Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Wed, 26 Nov 2025 09:21:13 -0800 Subject: [PATCH 070/140] crypto/x509: add ExtKeyUsage.OID method And OIDFromASN1OID for converting between asn1.ObjectIdentifier and OID. Fixes #75325 Change-Id: I3b84dce54346d88aab731ffe30d0fef07b014f04 Reviewed-on: https://go-review.googlesource.com/c/go/+/724761 Reviewed-by: Neal Patel Auto-Submit: Roland Shoemaker Commit-Queue: Neal Patel LUCI-TryBot-Result: Go LUCI --- api/next/75325.txt | 2 ++ .../6-stdlib/99-minor/crypto/x509/75325.md | 4 ++++ src/crypto/x509/oid.go | 12 ++++++++++ src/crypto/x509/oid_test.go | 24 +++++++++++++++++++ src/crypto/x509/x509.go | 13 ++++++++++ src/crypto/x509/x509_test.go | 9 +++++++ 6 files changed, 64 insertions(+) create mode 100644 api/next/75325.txt create mode 100644 doc/next/6-stdlib/99-minor/crypto/x509/75325.md diff --git a/api/next/75325.txt b/api/next/75325.txt new file mode 100644 index 00000000000..dc241efef2d --- /dev/null +++ b/api/next/75325.txt @@ -0,0 +1,2 @@ +pkg crypto/x509, func OIDFromASN1OID(asn1.ObjectIdentifier) (OID, error) #75325 +pkg crypto/x509, method (ExtKeyUsage) OID() OID #75325 diff --git a/doc/next/6-stdlib/99-minor/crypto/x509/75325.md b/doc/next/6-stdlib/99-minor/crypto/x509/75325.md new file mode 100644 index 00000000000..a133e66209b --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/x509/75325.md @@ -0,0 +1,4 @@ +The [ExtKeyUsage] type now has an OID method that returns the corresponding OID for the EKU. + +The new [OIDFromASN1OID] function allows converting an [encoding/asn1.ObjectIdentifier] into +an [OID]. \ No newline at end of file diff --git a/src/crypto/x509/oid.go b/src/crypto/x509/oid.go index c60daa7540c..8bf19a14333 100644 --- a/src/crypto/x509/oid.go +++ b/src/crypto/x509/oid.go @@ -393,3 +393,15 @@ func (oid OID) toASN1OID() (asn1.ObjectIdentifier, bool) { return out, true } + +// OIDFromASN1OID creates a new OID using asn1OID. +func OIDFromASN1OID(asn1OID asn1.ObjectIdentifier) (OID, error) { + uint64OID := make([]uint64, 0, len(asn1OID)) + for _, component := range asn1OID { + if component < 0 { + return OID{}, errors.New("x509: OID components must be non-negative") + } + uint64OID = append(uint64OID, uint64(component)) + } + return OIDFromInts(uint64OID) +} diff --git a/src/crypto/x509/oid_test.go b/src/crypto/x509/oid_test.go index ce3a0672a6a..efc71fc2dc7 100644 --- a/src/crypto/x509/oid_test.go +++ b/src/crypto/x509/oid_test.go @@ -343,3 +343,27 @@ func BenchmarkOIDMarshalUnmarshalText(b *testing.B) { } } } + +func TestOIDFromASN1OID(t *testing.T) { + negativeComponentOID := asn1.ObjectIdentifier{-1} + _, err := OIDFromASN1OID(negativeComponentOID) + if err == nil || err.Error() != "x509: OID components must be non-negative" { + t.Fatalf("OIDFromASN1OID() = %v; want = \"x509: OID components must be non-negative\"", err) + } + + shortOID := asn1.ObjectIdentifier{1} + _, err = OIDFromASN1OID(shortOID) + if err == nil || err != errInvalidOID { + t.Fatalf("OIDFromASN1OID() = %v; want = %q", err, errInvalidOID) + } + invalidOIDFirstComponent := asn1.ObjectIdentifier{255, 1} + _, err = OIDFromASN1OID(invalidOIDFirstComponent) + if err == nil || err != errInvalidOID { + t.Fatalf("OIDFromASN1OID() = %v; want = %q", err, errInvalidOID) + } + invalidOIDSecondComponent := asn1.ObjectIdentifier{1, 255} + _, err = OIDFromASN1OID(invalidOIDSecondComponent) + if err == nil || err != errInvalidOID { + t.Fatalf("OIDFromASN1OID() = %v; want = %q", err, errInvalidOID) + } +} diff --git a/src/crypto/x509/x509.go b/src/crypto/x509/x509.go index 85e8fceedcb..7953b615f50 100644 --- a/src/crypto/x509/x509.go +++ b/src/crypto/x509/x509.go @@ -687,6 +687,19 @@ func oidFromExtKeyUsage(eku ExtKeyUsage) (oid asn1.ObjectIdentifier, ok bool) { return } +// OID returns the ASN.1 object identifier of the EKU. +func (eku ExtKeyUsage) OID() OID { + asn1OID, ok := oidFromExtKeyUsage(eku) + if !ok { + panic("x509: internal error: known ExtKeyUsage has no OID") + } + oid, err := OIDFromASN1OID(asn1OID) + if err != nil { + panic("x509: internal error: known ExtKeyUsage has invalid OID") + } + return oid +} + // A Certificate represents an X.509 certificate. type Certificate struct { Raw []byte // Complete ASN.1 DER content (certificate, signature algorithm and signature). diff --git a/src/crypto/x509/x509_test.go b/src/crypto/x509/x509_test.go index 98f3f7941c8..183ee303fae 100644 --- a/src/crypto/x509/x509_test.go +++ b/src/crypto/x509/x509_test.go @@ -4263,3 +4263,12 @@ func TestCreateCertificateNegativeMaxPathLength(t *testing.T) { t.Fatalf(`CreateCertificate() = %v; want = "x509: invalid MaxPathLen, must be greater or equal to -1"`, err) } } + +func TestEKUOIDS(t *testing.T) { + for _, eku := range extKeyUsageOIDs { + oid := eku.extKeyUsage.OID() + if !oid.EqualASN1OID(eku.oid) { + t.Errorf("extKeyUsage %v: expected OID %v, got %v", eku.extKeyUsage, eku.oid, oid) + } + } +} From 86bbea0cfa72041fb4315eb22099b0bc83caa314 Mon Sep 17 00:00:00 2001 From: Daniel Morsing Date: Mon, 24 Nov 2025 13:08:10 +0000 Subject: [PATCH 071/140] crypto/fips140: add WithoutEnforcement WithoutEnforcement lets programs running under GODEBUG=fips140=only selectively opt out of strict enforcement. This is especially helpful for non-critical uses of cryptography routines like SHA-1 for content addressable storage backends (E.g. git). Fixes #74630 Change-Id: Iabba1f5eb63498db98047aca45e09c5dccf2fbdf Reviewed-on: https://go-review.googlesource.com/c/go/+/723720 Reviewed-by: Dmitri Shuralyov Reviewed-by: Filippo Valsorda Auto-Submit: Filippo Valsorda LUCI-TryBot-Result: Go LUCI Reviewed-by: Roland Shoemaker --- api/next/74630.txt | 2 + .../6-stdlib/99-minor/crypto/fips140/74630.md | 2 + src/crypto/cipher/cbc.go | 4 +- src/crypto/cipher/cfb.go | 4 +- src/crypto/cipher/ctr.go | 2 +- src/crypto/cipher/gcm.go | 8 +-- src/crypto/cipher/ofb.go | 2 +- src/crypto/des/cipher.go | 4 +- src/crypto/dsa/dsa.go | 8 +-- src/crypto/ecdh/nist.go | 2 +- src/crypto/ecdh/x25519.go | 6 +- src/crypto/ecdsa/ecdsa.go | 6 +- src/crypto/ecdsa/ecdsa_legacy.go | 6 +- src/crypto/ed25519/ed25519.go | 4 +- src/crypto/fips140/enforcement.go | 47 ++++++++++++++ src/crypto/fips140/enforcement_test.go | 46 +++++++++++++ .../fips140/testdata/enforcement_test.go | 65 +++++++++++++++++++ src/crypto/hkdf/hkdf.go | 2 +- src/crypto/hmac/hmac.go | 2 +- .../internal/fips140only/fips140only.go | 8 ++- src/crypto/md5/md5.go | 4 +- src/crypto/pbkdf2/pbkdf2.go | 2 +- src/crypto/rand/util.go | 2 +- src/crypto/rc4/rc4.go | 2 +- src/crypto/rsa/fips.go | 24 +++---- src/crypto/rsa/pkcs1v15.go | 4 +- src/crypto/rsa/rsa.go | 8 +-- src/crypto/sha1/sha1.go | 8 +-- src/go/build/deps_test.go | 2 +- src/runtime/fipsbypass.go | 22 +++++++ src/runtime/proc.go | 4 ++ src/runtime/runtime2.go | 1 + 32 files changed, 252 insertions(+), 61 deletions(-) create mode 100644 api/next/74630.txt create mode 100644 doc/next/6-stdlib/99-minor/crypto/fips140/74630.md create mode 100644 src/crypto/fips140/enforcement.go create mode 100644 src/crypto/fips140/enforcement_test.go create mode 100644 src/crypto/fips140/testdata/enforcement_test.go create mode 100644 src/runtime/fipsbypass.go diff --git a/api/next/74630.txt b/api/next/74630.txt new file mode 100644 index 00000000000..2a2a82fc43b --- /dev/null +++ b/api/next/74630.txt @@ -0,0 +1,2 @@ +pkg crypto/fips140, func Enforced() bool #74630 +pkg crypto/fips140, func WithoutEnforcement(func()) #74630 diff --git a/doc/next/6-stdlib/99-minor/crypto/fips140/74630.md b/doc/next/6-stdlib/99-minor/crypto/fips140/74630.md new file mode 100644 index 00000000000..6a56aad3d15 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/fips140/74630.md @@ -0,0 +1,2 @@ +The new [WithoutEnforcement] and [Enforced] functions now allow running +in `GODEBUG=fips140=only` mode while selectively disabling the strict FIPS 140-3 checks. diff --git a/src/crypto/cipher/cbc.go b/src/crypto/cipher/cbc.go index 8e614062969..87bafee08ad 100644 --- a/src/crypto/cipher/cbc.go +++ b/src/crypto/cipher/cbc.go @@ -54,7 +54,7 @@ func NewCBCEncrypter(b Block, iv []byte) BlockMode { if b, ok := b.(*aes.Block); ok { return aes.NewCBCEncrypter(b, [16]byte(iv)) } - if fips140only.Enabled { + if fips140only.Enforced() { panic("crypto/cipher: use of CBC with non-AES ciphers is not allowed in FIPS 140-only mode") } if cbc, ok := b.(cbcEncAble); ok { @@ -133,7 +133,7 @@ func NewCBCDecrypter(b Block, iv []byte) BlockMode { if b, ok := b.(*aes.Block); ok { return aes.NewCBCDecrypter(b, [16]byte(iv)) } - if fips140only.Enabled { + if fips140only.Enforced() { panic("crypto/cipher: use of CBC with non-AES ciphers is not allowed in FIPS 140-only mode") } if cbc, ok := b.(cbcDecAble); ok { diff --git a/src/crypto/cipher/cfb.go b/src/crypto/cipher/cfb.go index 7de682fac36..8ede955b917 100644 --- a/src/crypto/cipher/cfb.go +++ b/src/crypto/cipher/cfb.go @@ -61,7 +61,7 @@ func (x *cfb) XORKeyStream(dst, src []byte) { // CFB is also unoptimized and not validated as part of the FIPS 140-3 module. // If an unauthenticated [Stream] mode is required, use [NewCTR] instead. func NewCFBEncrypter(block Block, iv []byte) Stream { - if fips140only.Enabled { + if fips140only.Enforced() { panic("crypto/cipher: use of CFB is not allowed in FIPS 140-only mode") } return newCFB(block, iv, false) @@ -77,7 +77,7 @@ func NewCFBEncrypter(block Block, iv []byte) Stream { // CFB is also unoptimized and not validated as part of the FIPS 140-3 module. // If an unauthenticated [Stream] mode is required, use [NewCTR] instead. func NewCFBDecrypter(block Block, iv []byte) Stream { - if fips140only.Enabled { + if fips140only.Enforced() { panic("crypto/cipher: use of CFB is not allowed in FIPS 140-only mode") } return newCFB(block, iv, true) diff --git a/src/crypto/cipher/ctr.go b/src/crypto/cipher/ctr.go index 49512ca5dd8..8e63ed7e668 100644 --- a/src/crypto/cipher/ctr.go +++ b/src/crypto/cipher/ctr.go @@ -42,7 +42,7 @@ func NewCTR(block Block, iv []byte) Stream { if block, ok := block.(*aes.Block); ok { return aesCtrWrapper{aes.NewCTR(block, iv)} } - if fips140only.Enabled { + if fips140only.Enforced() { panic("crypto/cipher: use of CTR with non-AES ciphers is not allowed in FIPS 140-only mode") } if ctr, ok := block.(ctrAble); ok { diff --git a/src/crypto/cipher/gcm.go b/src/crypto/cipher/gcm.go index 73493f6cd23..88892e15555 100644 --- a/src/crypto/cipher/gcm.go +++ b/src/crypto/cipher/gcm.go @@ -28,7 +28,7 @@ const ( // An exception is when the underlying [Block] was created by aes.NewCipher // on systems with hardware support for AES. See the [crypto/aes] package documentation for details. func NewGCM(cipher Block) (AEAD, error) { - if fips140only.Enabled { + if fips140only.Enforced() { return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce") } return newGCM(cipher, gcmStandardNonceSize, gcmTagSize) @@ -42,7 +42,7 @@ func NewGCM(cipher Block) (AEAD, error) { // cryptosystem that uses non-standard nonce lengths. All other users should use // [NewGCM], which is faster and more resistant to misuse. func NewGCMWithNonceSize(cipher Block, size int) (AEAD, error) { - if fips140only.Enabled { + if fips140only.Enforced() { return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce") } return newGCM(cipher, size, gcmTagSize) @@ -57,7 +57,7 @@ func NewGCMWithNonceSize(cipher Block, size int) (AEAD, error) { // cryptosystem that uses non-standard tag lengths. All other users should use // [NewGCM], which is more resistant to misuse. func NewGCMWithTagSize(cipher Block, tagSize int) (AEAD, error) { - if fips140only.Enabled { + if fips140only.Enforced() { return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce") } return newGCM(cipher, gcmStandardNonceSize, tagSize) @@ -66,7 +66,7 @@ func NewGCMWithTagSize(cipher Block, tagSize int) (AEAD, error) { func newGCM(cipher Block, nonceSize, tagSize int) (AEAD, error) { c, ok := cipher.(*aes.Block) if !ok { - if fips140only.Enabled { + if fips140only.Enforced() { return nil, errors.New("crypto/cipher: use of GCM with non-AES ciphers is not allowed in FIPS 140-only mode") } return newGCMFallback(cipher, nonceSize, tagSize) diff --git a/src/crypto/cipher/ofb.go b/src/crypto/cipher/ofb.go index 8db5659f7a2..ee5dfaf5c0d 100644 --- a/src/crypto/cipher/ofb.go +++ b/src/crypto/cipher/ofb.go @@ -29,7 +29,7 @@ type ofb struct { // OFB is also unoptimized and not validated as part of the FIPS 140-3 module. // If an unauthenticated [Stream] mode is required, use [NewCTR] instead. func NewOFB(b Block, iv []byte) Stream { - if fips140only.Enabled { + if fips140only.Enforced() { panic("crypto/cipher: use of OFB is not allowed in FIPS 140-only mode") } diff --git a/src/crypto/des/cipher.go b/src/crypto/des/cipher.go index 21303b384cf..81ba766b252 100644 --- a/src/crypto/des/cipher.go +++ b/src/crypto/des/cipher.go @@ -29,7 +29,7 @@ type desCipher struct { // NewCipher creates and returns a new [cipher.Block]. func NewCipher(key []byte) (cipher.Block, error) { - if fips140only.Enabled { + if fips140only.Enforced() { return nil, errors.New("crypto/des: use of DES is not allowed in FIPS 140-only mode") } @@ -77,7 +77,7 @@ type tripleDESCipher struct { // NewTripleDESCipher creates and returns a new [cipher.Block]. func NewTripleDESCipher(key []byte) (cipher.Block, error) { - if fips140only.Enabled { + if fips140only.Enforced() { return nil, errors.New("crypto/des: use of TripleDES is not allowed in FIPS 140-only mode") } diff --git a/src/crypto/dsa/dsa.go b/src/crypto/dsa/dsa.go index 000becc82df..ecc4c82bb52 100644 --- a/src/crypto/dsa/dsa.go +++ b/src/crypto/dsa/dsa.go @@ -64,7 +64,7 @@ const numMRTests = 64 // GenerateParameters puts a random, valid set of DSA parameters into params. // This function can take many seconds, even on fast machines. func GenerateParameters(params *Parameters, rand io.Reader, sizes ParameterSizes) error { - if fips140only.Enabled { + if fips140only.Enforced() { return errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode") } @@ -162,7 +162,7 @@ GeneratePrimes: // GenerateKey generates a public&private key pair. The Parameters of the // [PrivateKey] must already be valid (see [GenerateParameters]). func GenerateKey(priv *PrivateKey, rand io.Reader) error { - if fips140only.Enabled { + if fips140only.Enforced() { return errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode") } @@ -212,7 +212,7 @@ func fermatInverse(k, P *big.Int) *big.Int { // Be aware that calling Sign with an attacker-controlled [PrivateKey] may // require an arbitrary amount of CPU. func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) { - if fips140only.Enabled { + if fips140only.Enforced() { return nil, nil, errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode") } @@ -284,7 +284,7 @@ func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err err // to the byte-length of the subgroup. This function does not perform that // truncation itself. func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool { - if fips140only.Enabled { + if fips140only.Enforced() { panic("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode") } diff --git a/src/crypto/ecdh/nist.go b/src/crypto/ecdh/nist.go index acef8298943..0d58196842a 100644 --- a/src/crypto/ecdh/nist.go +++ b/src/crypto/ecdh/nist.go @@ -44,7 +44,7 @@ func (c *nistCurve) GenerateKey(rand io.Reader) (*PrivateKey, error) { return k, nil } - if fips140only.Enabled && !fips140only.ApprovedRandomReader(rand) { + if fips140only.Enforced() && !fips140only.ApprovedRandomReader(rand) { return nil, errors.New("crypto/ecdh: only crypto/rand.Reader is allowed in FIPS 140-only mode") } diff --git a/src/crypto/ecdh/x25519.go b/src/crypto/ecdh/x25519.go index 81dca5d3a42..3ad13f3e73a 100644 --- a/src/crypto/ecdh/x25519.go +++ b/src/crypto/ecdh/x25519.go @@ -35,7 +35,7 @@ func (c *x25519Curve) String() string { } func (c *x25519Curve) GenerateKey(rand io.Reader) (*PrivateKey, error) { - if fips140only.Enabled { + if fips140only.Enforced() { return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode") } key := make([]byte, x25519PrivateKeySize) @@ -47,7 +47,7 @@ func (c *x25519Curve) GenerateKey(rand io.Reader) (*PrivateKey, error) { } func (c *x25519Curve) NewPrivateKey(key []byte) (*PrivateKey, error) { - if fips140only.Enabled { + if fips140only.Enforced() { return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode") } if len(key) != x25519PrivateKeySize { @@ -67,7 +67,7 @@ func (c *x25519Curve) NewPrivateKey(key []byte) (*PrivateKey, error) { } func (c *x25519Curve) NewPublicKey(key []byte) (*PublicKey, error) { - if fips140only.Enabled { + if fips140only.Enforced() { return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode") } if len(key) != x25519PublicKeySize { diff --git a/src/crypto/ecdsa/ecdsa.go b/src/crypto/ecdsa/ecdsa.go index 38f0b564ac9..9d965c4e7b8 100644 --- a/src/crypto/ecdsa/ecdsa.go +++ b/src/crypto/ecdsa/ecdsa.go @@ -358,7 +358,7 @@ func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) { } func generateFIPS[P ecdsa.Point[P]](curve elliptic.Curve, c *ecdsa.Curve[P], rand io.Reader) (*PrivateKey, error) { - if fips140only.Enabled && !fips140only.ApprovedRandomReader(rand) { + if fips140only.Enforced() && !fips140only.ApprovedRandomReader(rand) { return nil, errors.New("crypto/ecdsa: only crypto/rand.Reader is allowed in FIPS 140-only mode") } privateKey, err := ecdsa.GenerateKey(c, rand) @@ -403,7 +403,7 @@ func SignASN1(rand io.Reader, priv *PrivateKey, hash []byte) ([]byte, error) { } func signFIPS[P ecdsa.Point[P]](c *ecdsa.Curve[P], priv *PrivateKey, rand io.Reader, hash []byte) ([]byte, error) { - if fips140only.Enabled && !fips140only.ApprovedRandomReader(rand) { + if fips140only.Enforced() && !fips140only.ApprovedRandomReader(rand) { return nil, errors.New("crypto/ecdsa: only crypto/rand.Reader is allowed in FIPS 140-only mode") } k, err := privateKeyToFIPS(c, priv) @@ -448,7 +448,7 @@ func signFIPSDeterministic[P ecdsa.Point[P]](c *ecdsa.Curve[P], hashFunc crypto. return nil, err } h := fips140hash.UnwrapNew(hashFunc.New) - if fips140only.Enabled && !fips140only.ApprovedHash(h()) { + if fips140only.Enforced() && !fips140only.ApprovedHash(h()) { return nil, errors.New("crypto/ecdsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") } sig, err := ecdsa.SignDeterministic(c, h, k, hash) diff --git a/src/crypto/ecdsa/ecdsa_legacy.go b/src/crypto/ecdsa/ecdsa_legacy.go index 74f27b74885..f6b4401bd75 100644 --- a/src/crypto/ecdsa/ecdsa_legacy.go +++ b/src/crypto/ecdsa/ecdsa_legacy.go @@ -20,7 +20,7 @@ import ( // deprecated custom curves. func generateLegacy(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) { - if fips140only.Enabled { + if fips140only.Enforced() { return nil, errors.New("crypto/ecdsa: use of custom curves is not allowed in FIPS 140-only mode") } @@ -81,7 +81,7 @@ func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err err } func signLegacy(priv *PrivateKey, csprng io.Reader, hash []byte) (sig []byte, err error) { - if fips140only.Enabled { + if fips140only.Enforced() { return nil, errors.New("crypto/ecdsa: use of custom curves is not allowed in FIPS 140-only mode") } @@ -153,7 +153,7 @@ func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool { } func verifyLegacy(pub *PublicKey, hash []byte, sig []byte) bool { - if fips140only.Enabled { + if fips140only.Enforced() { panic("crypto/ecdsa: use of custom curves is not allowed in FIPS 140-only mode") } diff --git a/src/crypto/ed25519/ed25519.go b/src/crypto/ed25519/ed25519.go index 0e268139587..26b4882b132 100644 --- a/src/crypto/ed25519/ed25519.go +++ b/src/crypto/ed25519/ed25519.go @@ -110,7 +110,7 @@ func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOp case hash == crypto.SHA512: // Ed25519ph return ed25519.SignPH(k, message, context) case hash == crypto.Hash(0) && context != "": // Ed25519ctx - if fips140only.Enabled { + if fips140only.Enforced() { return nil, errors.New("crypto/ed25519: use of Ed25519ctx is not allowed in FIPS 140-only mode") } return ed25519.SignCtx(k, message, context) @@ -230,7 +230,7 @@ func VerifyWithOptions(publicKey PublicKey, message, sig []byte, opts *Options) case opts.Hash == crypto.SHA512: // Ed25519ph return ed25519.VerifyPH(k, message, sig, opts.Context) case opts.Hash == crypto.Hash(0) && opts.Context != "": // Ed25519ctx - if fips140only.Enabled { + if fips140only.Enforced() { return errors.New("crypto/ed25519: use of Ed25519ctx is not allowed in FIPS 140-only mode") } return ed25519.VerifyCtx(k, message, sig, opts.Context) diff --git a/src/crypto/fips140/enforcement.go b/src/crypto/fips140/enforcement.go new file mode 100644 index 00000000000..5dae32a3a79 --- /dev/null +++ b/src/crypto/fips140/enforcement.go @@ -0,0 +1,47 @@ +// Copyright 2025 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 fips140 + +import ( + "internal/godebug" + _ "unsafe" // for linkname +) + +// WithoutEnforcement disables strict FIPS 140-3 enforcement while executing f. +// Calling WithoutEnforcement without strict enforcement enabled +// (GODEBUG=fips140=only is not set or already inside of a call to +// WithoutEnforcement) is a no-op. +// +// WithoutEnforcement is inherited by any goroutines spawned while executing f. +// +// As this disables enforcement, it should be applied carefully to tightly +// scoped functions. +func WithoutEnforcement(f func()) { + if !Enabled() || !Enforced() { + f() + return + } + setBypass() + defer unsetBypass() + f() +} + +var enabled = godebug.New("fips140").Value() == "only" + +// Enforced indicates if strict FIPS 140-3 enforcement is enabled. Strict +// enforcement is enabled when a program is run with GODEBUG=fips140=only and +// enforcement has not been disabled by a call to [WithoutEnforcement]. +func Enforced() bool { + return enabled && !isBypassed() +} + +//go:linkname setBypass +func setBypass() + +//go:linkname isBypassed +func isBypassed() bool + +//go:linkname unsetBypass +func unsetBypass() diff --git a/src/crypto/fips140/enforcement_test.go b/src/crypto/fips140/enforcement_test.go new file mode 100644 index 00000000000..708c7f7a15e --- /dev/null +++ b/src/crypto/fips140/enforcement_test.go @@ -0,0 +1,46 @@ +// Copyright 2025 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 fips140_test + +import ( + "crypto/internal/cryptotest" + "internal/testenv" + "path/filepath" + "strings" + "testing" +) + +func TestWithoutEnforcement(t *testing.T) { + testenv.MustHaveExec(t) + testenv.MustHaveGoBuild(t) + cryptotest.MustSupportFIPS140(t) + + tool, _ := testenv.GoTool() + tmpdir := t.TempDir() + binFile := filepath.Join(tmpdir, "fips140.test") + cmd := testenv.Command(t, tool, "test", "-c", "-o", binFile, "./testdata") + out, err := cmd.CombinedOutput() + if err != nil { + t.Log(string(out)) + t.Errorf("Could not build enforcement tests") + } + cmd = testenv.Command(t, binFile, "-test.list", ".") + list, err := cmd.CombinedOutput() + if err != nil { + t.Log(string(out)) + t.Errorf("Could not get enforcement test list") + } + for test := range strings.Lines(string(list)) { + test = strings.TrimSpace(test) + t.Run(test, func(t *testing.T) { + cmd = testenv.Command(t, binFile, "-test.run", "^"+test+"$") + cmd.Env = append(cmd.Env, "GODEBUG=fips140=only") + out, err := cmd.CombinedOutput() + if err != nil { + t.Error(string(out)) + } + }) + } +} diff --git a/src/crypto/fips140/testdata/enforcement_test.go b/src/crypto/fips140/testdata/enforcement_test.go new file mode 100644 index 00000000000..a8fde3b5bc6 --- /dev/null +++ b/src/crypto/fips140/testdata/enforcement_test.go @@ -0,0 +1,65 @@ +// Copyright 2025 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 fips140_test + +import ( + "crypto/des" + "crypto/fips140" + "testing" +) + +func expectAllowed(t *testing.T, why string, expected bool) { + t.Helper() + result := isAllowed() + if result != expected { + t.Fatalf("%v: expected: %v, got: %v", why, expected, result) + } +} + +func isAllowed() bool { + _, err := des.NewCipher(make([]byte, 8)) + return err == nil +} + +func TestDisabled(t *testing.T) { + expectAllowed(t, "before enforcement disabled", false) + fips140.WithoutEnforcement(func() { + expectAllowed(t, "inside WithoutEnforcement", true) + }) + // make sure that bypass doesn't live on after returning + expectAllowed(t, "after WithoutEnforcement", false) +} + +func TestNested(t *testing.T) { + expectAllowed(t, "before enforcement bypass", false) + fips140.WithoutEnforcement(func() { + fips140.WithoutEnforcement(func() { + expectAllowed(t, "inside nested WithoutEnforcement", true) + }) + expectAllowed(t, "inside nested WithoutEnforcement", true) + }) + expectAllowed(t, "after enforcement bypass", false) +} + +func TestGoroutineInherit(t *testing.T) { + ch := make(chan bool, 2) + expectAllowed(t, "before enforcement bypass", false) + fips140.WithoutEnforcement(func() { + go func() { + ch <- isAllowed() + }() + }) + allowed := <-ch + if !allowed { + t.Fatal("goroutine didn't inherit enforcement bypass") + } + go func() { + ch <- isAllowed() + }() + allowed = <-ch + if allowed { + t.Fatal("goroutine inherited bypass after WithoutEnforcement return") + } +} diff --git a/src/crypto/hkdf/hkdf.go b/src/crypto/hkdf/hkdf.go index 6b02522866d..88439922a50 100644 --- a/src/crypto/hkdf/hkdf.go +++ b/src/crypto/hkdf/hkdf.go @@ -71,7 +71,7 @@ func Key[Hash hash.Hash](h func() Hash, secret, salt []byte, info string, keyLen } func checkFIPS140Only[Hash hash.Hash](h func() Hash, key []byte) error { - if !fips140only.Enabled { + if !fips140only.Enforced() { return nil } if len(key) < 112/8 { diff --git a/src/crypto/hmac/hmac.go b/src/crypto/hmac/hmac.go index 554c8c9b789..e7976e25193 100644 --- a/src/crypto/hmac/hmac.go +++ b/src/crypto/hmac/hmac.go @@ -45,7 +45,7 @@ func New(h func() hash.Hash, key []byte) hash.Hash { // BoringCrypto did not recognize h, so fall through to standard Go code. } h = fips140hash.UnwrapNew(h) - if fips140only.Enabled { + if fips140only.Enforced() { if len(key) < 112/8 { panic("crypto/hmac: use of keys shorter than 112 bits is not allowed in FIPS 140-only mode") } diff --git a/src/crypto/internal/fips140only/fips140only.go b/src/crypto/internal/fips140only/fips140only.go index 147877a34fc..1b0a4be6ba5 100644 --- a/src/crypto/internal/fips140only/fips140only.go +++ b/src/crypto/internal/fips140only/fips140only.go @@ -5,18 +5,20 @@ package fips140only import ( + "crypto/fips140" "crypto/internal/fips140/drbg" "crypto/internal/fips140/sha256" "crypto/internal/fips140/sha3" "crypto/internal/fips140/sha512" "hash" - "internal/godebug" "io" ) -// Enabled reports whether FIPS 140-only mode is enabled, in which non-approved +// Enforced reports whether FIPS 140-only mode is enabled and enforced, in which non-approved // cryptography returns an error or panics. -var Enabled = godebug.New("fips140").Value() == "only" +func Enforced() bool { + return fips140.Enforced() +} func ApprovedHash(h hash.Hash) bool { switch h.(type) { diff --git a/src/crypto/md5/md5.go b/src/crypto/md5/md5.go index 9274f89d3e2..f1287887ff5 100644 --- a/src/crypto/md5/md5.go +++ b/src/crypto/md5/md5.go @@ -124,7 +124,7 @@ func (d *digest) Size() int { return Size } func (d *digest) BlockSize() int { return BlockSize } func (d *digest) Write(p []byte) (nn int, err error) { - if fips140only.Enabled { + if fips140only.Enforced() { return 0, errors.New("crypto/md5: use of MD5 is not allowed in FIPS 140-only mode") } // Note that we currently call block or blockGeneric @@ -173,7 +173,7 @@ func (d *digest) Sum(in []byte) []byte { } func (d *digest) checkSum() [Size]byte { - if fips140only.Enabled { + if fips140only.Enforced() { panic("crypto/md5: use of MD5 is not allowed in FIPS 140-only mode") } diff --git a/src/crypto/pbkdf2/pbkdf2.go b/src/crypto/pbkdf2/pbkdf2.go index 01fd12e40e3..0bc14be888d 100644 --- a/src/crypto/pbkdf2/pbkdf2.go +++ b/src/crypto/pbkdf2/pbkdf2.go @@ -39,7 +39,7 @@ import ( // Setting keyLength to a value outside of this range will result in an error. func Key[Hash hash.Hash](h func() Hash, password string, salt []byte, iter, keyLength int) ([]byte, error) { fh := fips140hash.UnwrapNew(h) - if fips140only.Enabled { + if fips140only.Enforced() { if keyLength < 112/8 { return nil, errors.New("crypto/pbkdf2: use of keys shorter than 112 bits is not allowed in FIPS 140-only mode") } diff --git a/src/crypto/rand/util.go b/src/crypto/rand/util.go index 10c2284a9b5..8c928519751 100644 --- a/src/crypto/rand/util.go +++ b/src/crypto/rand/util.go @@ -15,7 +15,7 @@ import ( // Prime returns a number of the given bit length that is prime with high probability. // Prime will return error for any error returned by rand.Read or if bits < 2. func Prime(rand io.Reader, bits int) (*big.Int, error) { - if fips140only.Enabled { + if fips140only.Enforced() { return nil, errors.New("crypto/rand: use of Prime is not allowed in FIPS 140-only mode") } if bits < 2 { diff --git a/src/crypto/rc4/rc4.go b/src/crypto/rc4/rc4.go index eebc1c04cbf..c4d2b0d382e 100644 --- a/src/crypto/rc4/rc4.go +++ b/src/crypto/rc4/rc4.go @@ -31,7 +31,7 @@ func (k KeySizeError) Error() string { // NewCipher creates and returns a new [Cipher]. The key argument should be the // RC4 key, at least 1 byte and at most 256 bytes. func NewCipher(key []byte) (*Cipher, error) { - if fips140only.Enabled { + if fips140only.Enforced() { return nil, errors.New("crypto/rc4: use of RC4 is not allowed in FIPS 140-only mode") } k := len(key) diff --git a/src/crypto/rsa/fips.go b/src/crypto/rsa/fips.go index ba92659193f..75aa3d3d725 100644 --- a/src/crypto/rsa/fips.go +++ b/src/crypto/rsa/fips.go @@ -84,10 +84,10 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte, if err := checkFIPS140OnlyPrivateKey(priv); err != nil { return nil, err } - if fips140only.Enabled && !fips140only.ApprovedHash(h) { + if fips140only.Enforced() && !fips140only.ApprovedHash(h) { return nil, errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") } - if fips140only.Enabled && !fips140only.ApprovedRandomReader(rand) { + if fips140only.Enforced() && !fips140only.ApprovedRandomReader(rand) { return nil, errors.New("crypto/rsa: only crypto/rand.Reader is allowed in FIPS 140-only mode") } @@ -97,7 +97,7 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte, } saltLength := opts.saltLength() - if fips140only.Enabled && saltLength > h.Size() { + if fips140only.Enforced() && saltLength > h.Size() { return nil, errors.New("crypto/rsa: use of PSS salt longer than the hash is not allowed in FIPS 140-only mode") } switch saltLength { @@ -149,7 +149,7 @@ func VerifyPSS(pub *PublicKey, hash crypto.Hash, digest []byte, sig []byte, opts if err := checkFIPS140OnlyPublicKey(pub); err != nil { return err } - if fips140only.Enabled && !fips140only.ApprovedHash(h) { + if fips140only.Enforced() && !fips140only.ApprovedHash(h) { return errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") } @@ -159,7 +159,7 @@ func VerifyPSS(pub *PublicKey, hash crypto.Hash, digest []byte, sig []byte, opts } saltLength := opts.saltLength() - if fips140only.Enabled && saltLength > h.Size() { + if fips140only.Enforced() && saltLength > h.Size() { return errors.New("crypto/rsa: use of PSS salt longer than the hash is not allowed in FIPS 140-only mode") } switch saltLength { @@ -234,10 +234,10 @@ func encryptOAEP(hash hash.Hash, mgfHash hash.Hash, random io.Reader, pub *Publi if err := checkFIPS140OnlyPublicKey(pub); err != nil { return nil, err } - if fips140only.Enabled && !fips140only.ApprovedHash(hash) { + if fips140only.Enforced() && !fips140only.ApprovedHash(hash) { return nil, errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") } - if fips140only.Enabled && !fips140only.ApprovedRandomReader(random) { + if fips140only.Enforced() && !fips140only.ApprovedRandomReader(random) { return nil, errors.New("crypto/rsa: only crypto/rand.Reader is allowed in FIPS 140-only mode") } @@ -291,7 +291,7 @@ func decryptOAEP(hash, mgfHash hash.Hash, priv *PrivateKey, ciphertext []byte, l if err := checkFIPS140OnlyPrivateKey(priv); err != nil { return nil, err } - if fips140only.Enabled { + if fips140only.Enforced() { if !fips140only.ApprovedHash(hash) || !fips140only.ApprovedHash(mgfHash) { return nil, errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") } @@ -341,7 +341,7 @@ func SignPKCS1v15(random io.Reader, priv *PrivateKey, hash crypto.Hash, hashed [ if err := checkFIPS140OnlyPrivateKey(priv); err != nil { return nil, err } - if fips140only.Enabled && !fips140only.ApprovedHash(fips140hash.Unwrap(hash.New())) { + if fips140only.Enforced() && !fips140only.ApprovedHash(fips140hash.Unwrap(hash.New())) { return nil, errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") } @@ -387,7 +387,7 @@ func VerifyPKCS1v15(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte) if err := checkFIPS140OnlyPublicKey(pub); err != nil { return err } - if fips140only.Enabled && !fips140only.ApprovedHash(fips140hash.Unwrap(hash.New())) { + if fips140only.Enforced() && !fips140only.ApprovedHash(fips140hash.Unwrap(hash.New())) { return errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") } @@ -415,7 +415,7 @@ func fipsError2[T any](x T, err error) (T, error) { } func checkFIPS140OnlyPublicKey(pub *PublicKey) error { - if !fips140only.Enabled { + if !fips140only.Enforced() { return nil } if pub.N == nil { @@ -437,7 +437,7 @@ func checkFIPS140OnlyPublicKey(pub *PublicKey) error { } func checkFIPS140OnlyPrivateKey(priv *PrivateKey) error { - if !fips140only.Enabled { + if !fips140only.Enforced() { return nil } if err := checkFIPS140OnlyPublicKey(&priv.PublicKey); err != nil { diff --git a/src/crypto/rsa/pkcs1v15.go b/src/crypto/rsa/pkcs1v15.go index 76853a94453..caf68957e2d 100644 --- a/src/crypto/rsa/pkcs1v15.go +++ b/src/crypto/rsa/pkcs1v15.go @@ -49,7 +49,7 @@ type PKCS1v15DecryptOptions struct { // // [draft-irtf-cfrg-rsa-guidance-05]: https://www.ietf.org/archive/id/draft-irtf-cfrg-rsa-guidance-05.html#name-rationale func EncryptPKCS1v15(random io.Reader, pub *PublicKey, msg []byte) ([]byte, error) { - if fips140only.Enabled { + if fips140only.Enforced() { return nil, errors.New("crypto/rsa: use of PKCS#1 v1.5 encryption is not allowed in FIPS 140-only mode") } @@ -212,7 +212,7 @@ func DecryptPKCS1v15SessionKey(random io.Reader, priv *PrivateKey, ciphertext [] // access patterns. If the plaintext was valid then index contains the index of // the original message in em, to allow constant time padding removal. func decryptPKCS1v15(priv *PrivateKey, ciphertext []byte) (valid int, em []byte, index int, err error) { - if fips140only.Enabled { + if fips140only.Enforced() { return 0, nil, 0, errors.New("crypto/rsa: use of PKCS#1 v1.5 encryption is not allowed in FIPS 140-only mode") } diff --git a/src/crypto/rsa/rsa.go b/src/crypto/rsa/rsa.go index 7e1bf3d7a56..5680d8b5415 100644 --- a/src/crypto/rsa/rsa.go +++ b/src/crypto/rsa/rsa.go @@ -350,13 +350,13 @@ func GenerateKey(random io.Reader, bits int) (*PrivateKey, error) { return key, nil } - if fips140only.Enabled && bits < 2048 { + if fips140only.Enforced() && bits < 2048 { return nil, errors.New("crypto/rsa: use of keys smaller than 2048 bits is not allowed in FIPS 140-only mode") } - if fips140only.Enabled && bits%2 == 1 { + if fips140only.Enforced() && bits%2 == 1 { return nil, errors.New("crypto/rsa: use of keys with odd size is not allowed in FIPS 140-only mode") } - if fips140only.Enabled && !fips140only.ApprovedRandomReader(random) { + if fips140only.Enforced() && !fips140only.ApprovedRandomReader(random) { return nil, errors.New("crypto/rsa: only crypto/rand.Reader is allowed in FIPS 140-only mode") } @@ -424,7 +424,7 @@ func GenerateMultiPrimeKey(random io.Reader, nprimes int, bits int) (*PrivateKey if nprimes == 2 { return GenerateKey(random, bits) } - if fips140only.Enabled { + if fips140only.Enforced() { return nil, errors.New("crypto/rsa: multi-prime RSA is not allowed in FIPS 140-only mode") } diff --git a/src/crypto/sha1/sha1.go b/src/crypto/sha1/sha1.go index 3acc5b11fb7..46e47df1d32 100644 --- a/src/crypto/sha1/sha1.go +++ b/src/crypto/sha1/sha1.go @@ -126,7 +126,7 @@ func (d *digest) Size() int { return Size } func (d *digest) BlockSize() int { return BlockSize } func (d *digest) Write(p []byte) (nn int, err error) { - if fips140only.Enabled { + if fips140only.Enforced() { return 0, errors.New("crypto/sha1: use of SHA-1 is not allowed in FIPS 140-only mode") } boring.Unreachable() @@ -161,7 +161,7 @@ func (d *digest) Sum(in []byte) []byte { } func (d *digest) checkSum() [Size]byte { - if fips140only.Enabled { + if fips140only.Enforced() { panic("crypto/sha1: use of SHA-1 is not allowed in FIPS 140-only mode") } @@ -205,7 +205,7 @@ func (d *digest) ConstantTimeSum(in []byte) []byte { } func (d *digest) constSum() [Size]byte { - if fips140only.Enabled { + if fips140only.Enforced() { panic("crypto/sha1: use of SHA-1 is not allowed in FIPS 140-only mode") } @@ -274,7 +274,7 @@ func Sum(data []byte) [Size]byte { if boring.Enabled { return boring.SHA1(data) } - if fips140only.Enabled { + if fips140only.Enforced() { panic("crypto/sha1: use of SHA-1 is not allowed in FIPS 140-only mode") } var d digest diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 295c69425ee..5466f025e1b 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -537,7 +537,7 @@ var depsRules = ` < crypto/internal/fips140/edwards25519 < crypto/internal/fips140/ed25519 < crypto/internal/fips140/rsa - < FIPS < crypto/fips140; + < crypto/fips140 < FIPS; crypto !< FIPS; diff --git a/src/runtime/fipsbypass.go b/src/runtime/fipsbypass.go new file mode 100644 index 00000000000..12df9c6b6a2 --- /dev/null +++ b/src/runtime/fipsbypass.go @@ -0,0 +1,22 @@ +// Copyright 2025 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 runtime + +import _ "unsafe" + +//go:linkname fips140_setBypass crypto/fips140.setBypass +func fips140_setBypass() { + getg().fipsOnlyBypass = true +} + +//go:linkname fips140_unsetBypass crypto/fips140.unsetBypass +func fips140_unsetBypass() { + getg().fipsOnlyBypass = false +} + +//go:linkname fips140_isBypassed crypto/fips140.isBypassed +func fips140_isBypassed() bool { + return getg().fipsOnlyBypass +} diff --git a/src/runtime/proc.go b/src/runtime/proc.go index 58fb4bd6810..3b98be10748 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -4481,6 +4481,7 @@ func gdestroy(gp *g) { gp.labels = nil gp.timer = nil gp.bubble = nil + gp.fipsOnlyBypass = false if gcBlackenEnabled != 0 && gp.gcAssistBytes > 0 { // Flush assist credit to the global pool. This gives @@ -5325,6 +5326,9 @@ func newproc1(fn *funcval, callergp *g, callerpc uintptr, parked bool, waitreaso traceRelease(trace) } + // fips140 bubble + newg.fipsOnlyBypass = callergp.fipsOnlyBypass + // Set up race context. if raceenabled { newg.racectx = racegostart(callerpc) diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go index 3175ee55f54..58eaf802372 100644 --- a/src/runtime/runtime2.go +++ b/src/runtime/runtime2.go @@ -545,6 +545,7 @@ type g struct { runnableTime int64 // the amount of time spent runnable, cleared when running, only used when tracking lockedm muintptr fipsIndicator uint8 + fipsOnlyBypass bool syncSafePoint bool // set if g is stopped at a synchronous safe point. runningCleanups atomic.Bool sig uint32 From de392823326ab3b572a8a493b813123381c15be9 Mon Sep 17 00:00:00 2001 From: Cherry Mui Date: Wed, 26 Nov 2025 10:56:15 -0500 Subject: [PATCH 072/140] cmd/compile, runtime: guard X15 zeroing with GOEXPERIMENT=simd If simd experiment is not enabled, the compiler doesn't use the AVX part of the register. So only zero it with the SSE instruction. Change-Id: Ia3bdf34a9ed273128db2ee0f4f5db6f7cc76a975 Reviewed-on: https://go-review.googlesource.com/c/go/+/724720 Reviewed-by: Junyang Shao LUCI-TryBot-Result: Go LUCI Reviewed-by: David Chase --- src/cmd/compile/internal/amd64/ssa.go | 4 ++++ src/runtime/asm_amd64.s | 4 ++++ src/runtime/race_amd64.s | 2 ++ src/runtime/sys_darwin_amd64.s | 2 ++ src/runtime/sys_dragonfly_amd64.s | 2 ++ src/runtime/sys_freebsd_amd64.s | 4 ++++ src/runtime/sys_linux_amd64.s | 4 ++++ src/runtime/sys_netbsd_amd64.s | 2 ++ src/runtime/sys_openbsd_amd64.s | 2 ++ src/runtime/sys_windows_amd64.s | 2 ++ 10 files changed, 28 insertions(+) diff --git a/src/cmd/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go index a4676cd0a9b..9a0fa27470a 100644 --- a/src/cmd/compile/internal/amd64/ssa.go +++ b/src/cmd/compile/internal/amd64/ssa.go @@ -1871,6 +1871,10 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { // zeroX15 zeroes the X15 register. func zeroX15(s *ssagen.State) { + if !buildcfg.Experiment.SIMD { + opregreg(s, x86.AXORPS, x86.REG_X15, x86.REG_X15) + return + } vxorps := func(s *ssagen.State) { p := s.Prog(x86.AVXORPS) p.From.Type = obj.TYPE_REG diff --git a/src/runtime/asm_amd64.s b/src/runtime/asm_amd64.s index 7c746803a87..ed46ad4a284 100644 --- a/src/runtime/asm_amd64.s +++ b/src/runtime/asm_amd64.s @@ -1049,9 +1049,11 @@ needm: // there's no need to handle that. Clear R14 so that there's // a bad value in there, in case needm tries to use it. XORPS X15, X15 +#ifdef GOEXPERIMENT_simd CMPB internal∕cpu·X86+const_offsetX86HasAVX(SB), $1 JNE 2(PC) VXORPS X15, X15, X15 +#endif XORQ R14, R14 MOVQ $runtime·needAndBindM(SB), AX CALL AX @@ -1749,9 +1751,11 @@ TEXT ·sigpanic0(SB),NOSPLIT,$0-0 get_tls(R14) MOVQ g(R14), R14 XORPS X15, X15 +#ifdef GOEXPERIMENT_simd CMPB internal∕cpu·X86+const_offsetX86HasAVX(SB), $1 JNE 2(PC) VXORPS X15, X15, X15 +#endif JMP ·sigpanic(SB) // gcWriteBarrier informs the GC about heap pointer writes. diff --git a/src/runtime/race_amd64.s b/src/runtime/race_amd64.s index 23f2e59e3d4..ade29bc5f1f 100644 --- a/src/runtime/race_amd64.s +++ b/src/runtime/race_amd64.s @@ -456,9 +456,11 @@ call: // Back to Go world, set special registers. // The g register (R14) is preserved in C. XORPS X15, X15 +#ifdef GOEXPERIMENT_simd CMPB internal∕cpu·X86+const_offsetX86HasAVX(SB), $1 JNE 2(PC) VXORPS X15, X15, X15 +#endif RET // C->Go callback thunk that allows to call runtime·racesymbolize from C code. diff --git a/src/runtime/sys_darwin_amd64.s b/src/runtime/sys_darwin_amd64.s index e4e1216d569..e033e8b7021 100644 --- a/src/runtime/sys_darwin_amd64.s +++ b/src/runtime/sys_darwin_amd64.s @@ -177,9 +177,11 @@ TEXT runtime·sigtramp(SB),NOSPLIT|TOPFRAME|NOFRAME,$0 get_tls(R12) MOVQ g(R12), R14 PXOR X15, X15 +#ifdef GOEXPERIMENT_simd CMPB internal∕cpu·X86+const_offsetX86HasAVX(SB), $1 JNE 2(PC) VXORPS X15, X15, X15 +#endif // Reserve space for spill slots. NOP SP // disable vet stack checking diff --git a/src/runtime/sys_dragonfly_amd64.s b/src/runtime/sys_dragonfly_amd64.s index 84bf326aad3..e417d4b8a81 100644 --- a/src/runtime/sys_dragonfly_amd64.s +++ b/src/runtime/sys_dragonfly_amd64.s @@ -228,9 +228,11 @@ TEXT runtime·sigtramp(SB),NOSPLIT|TOPFRAME|NOFRAME,$0 get_tls(R12) MOVQ g(R12), R14 PXOR X15, X15 +#ifdef GOEXPERIMENT_simd CMPB internal∕cpu·X86+const_offsetX86HasAVX(SB), $1 JNE 2(PC) VXORPS X15, X15, X15 +#endif // Reserve space for spill slots. NOP SP // disable vet stack checking diff --git a/src/runtime/sys_freebsd_amd64.s b/src/runtime/sys_freebsd_amd64.s index a1fa3a6fa29..bab275cc726 100644 --- a/src/runtime/sys_freebsd_amd64.s +++ b/src/runtime/sys_freebsd_amd64.s @@ -265,9 +265,11 @@ TEXT runtime·sigtramp(SB),NOSPLIT|TOPFRAME|NOFRAME,$0 get_tls(R12) MOVQ g(R12), R14 PXOR X15, X15 +#ifdef GOEXPERIMENT_simd CMPB internal∕cpu·X86+const_offsetX86HasAVX(SB), $1 JNE 2(PC) VXORPS X15, X15, X15 +#endif // Reserve space for spill slots. NOP SP // disable vet stack checking @@ -293,9 +295,11 @@ TEXT runtime·sigprofNonGoWrapper<>(SB),NOSPLIT|NOFRAME,$0 get_tls(R12) MOVQ g(R12), R14 PXOR X15, X15 +#ifdef GOEXPERIMENT_simd CMPB internal∕cpu·X86+const_offsetX86HasAVX(SB), $1 JNE 2(PC) VXORPS X15, X15, X15 +#endif // Reserve space for spill slots. NOP SP // disable vet stack checking diff --git a/src/runtime/sys_linux_amd64.s b/src/runtime/sys_linux_amd64.s index 02505c2fb0a..e252a4b9147 100644 --- a/src/runtime/sys_linux_amd64.s +++ b/src/runtime/sys_linux_amd64.s @@ -340,9 +340,11 @@ TEXT runtime·sigtramp(SB),NOSPLIT|TOPFRAME|NOFRAME,$0 get_tls(R12) MOVQ g(R12), R14 PXOR X15, X15 +#ifdef GOEXPERIMENT_simd CMPB internal∕cpu·X86+const_offsetX86HasAVX(SB), $1 JNE 2(PC) VXORPS X15, X15, X15 +#endif // Reserve space for spill slots. NOP SP // disable vet stack checking @@ -368,9 +370,11 @@ TEXT runtime·sigprofNonGoWrapper<>(SB),NOSPLIT|NOFRAME,$0 get_tls(R12) MOVQ g(R12), R14 PXOR X15, X15 +#ifdef GOEXPERIMENT_simd CMPB internal∕cpu·X86+const_offsetX86HasAVX(SB), $1 JNE 2(PC) VXORPS X15, X15, X15 +#endif // Reserve space for spill slots. NOP SP // disable vet stack checking diff --git a/src/runtime/sys_netbsd_amd64.s b/src/runtime/sys_netbsd_amd64.s index edc7f3d6ee0..946b1fbe22c 100644 --- a/src/runtime/sys_netbsd_amd64.s +++ b/src/runtime/sys_netbsd_amd64.s @@ -310,9 +310,11 @@ TEXT runtime·sigtramp(SB),NOSPLIT|TOPFRAME|NOFRAME,$0 get_tls(R12) MOVQ g(R12), R14 PXOR X15, X15 +#ifdef GOEXPERIMENT_simd CMPB internal∕cpu·X86+const_offsetX86HasAVX(SB), $1 JNE 2(PC) VXORPS X15, X15, X15 +#endif // Reserve space for spill slots. NOP SP // disable vet stack checking diff --git a/src/runtime/sys_openbsd_amd64.s b/src/runtime/sys_openbsd_amd64.s index 734dfe6478e..7766fa5194e 100644 --- a/src/runtime/sys_openbsd_amd64.s +++ b/src/runtime/sys_openbsd_amd64.s @@ -64,9 +64,11 @@ TEXT runtime·sigtramp(SB),NOSPLIT|TOPFRAME|NOFRAME,$0 get_tls(R12) MOVQ g(R12), R14 PXOR X15, X15 +#ifdef GOEXPERIMENT_simd CMPB internal∕cpu·X86+const_offsetX86HasAVX(SB), $1 JNE 2(PC) VXORPS X15, X15, X15 +#endif // Reserve space for spill slots. NOP SP // disable vet stack checking diff --git a/src/runtime/sys_windows_amd64.s b/src/runtime/sys_windows_amd64.s index b0b4d3cce65..52a21ba89bb 100644 --- a/src/runtime/sys_windows_amd64.s +++ b/src/runtime/sys_windows_amd64.s @@ -32,9 +32,11 @@ TEXT sigtramp<>(SB),NOSPLIT,$0-0 // R14 is cleared in case there's a non-zero value in there // if called from a non-go thread. XORPS X15, X15 +#ifdef GOEXPERIMENT_simd CMPB internal∕cpu·X86+const_offsetX86HasAVX(SB), $1 JNE 2(PC) VXORPS X15, X15, X15 +#endif XORQ R14, R14 get_tls(AX) From 301d9f9b52b9b5dbc57151f680a64d1bf85e6d43 Mon Sep 17 00:00:00 2001 From: Dmitri Shuralyov Date: Wed, 26 Nov 2025 16:12:53 -0500 Subject: [PATCH 073/140] doc/next: document broken freebsd/riscv64 port Also update comment in cmd/dist's broken map to point to the top-level umbrella issue. For #76475. For #75005. Change-Id: I43b8384af4264dc5d72ceea8d05730b9db81123a Reviewed-on: https://go-review.googlesource.com/c/go/+/724860 Reviewed-by: Cherry Mui Reviewed-by: Dmitri Shuralyov Auto-Submit: Dmitri Shuralyov TryBot-Bypass: Dmitri Shuralyov --- doc/next/7-ports.md | 7 +++++++ src/cmd/dist/build.go | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/next/7-ports.md b/doc/next/7-ports.md index 04e1285f03e..bc245c22d41 100644 --- a/doc/next/7-ports.md +++ b/doc/next/7-ports.md @@ -6,6 +6,13 @@ Go 1.26 is the last release that will run on macOS 12 Monterey. Go 1.27 will require macOS 13 Ventura or later. +### FreeBSD + + + +The freebsd/riscv64 port (`GOOS=freebsd GOARCH=riscv64`) has been marked broken. +See [issue 76475](/issue/76475) for details. + ### Windows diff --git a/src/cmd/dist/build.go b/src/cmd/dist/build.go index 2b382a1c025..e4250e12de8 100644 --- a/src/cmd/dist/build.go +++ b/src/cmd/dist/build.go @@ -1822,7 +1822,7 @@ var cgoEnabled = map[string]bool{ // get filtered out of cgoEnabled for 'dist list'. // See go.dev/issue/56679. var broken = map[string]bool{ - "freebsd/riscv64": true, // Broken: go.dev/issue/73568. + "freebsd/riscv64": true, // Broken: go.dev/issue/76475. "linux/sparc64": true, // An incomplete port. See CL 132155. "openbsd/mips64": true, // Broken: go.dev/issue/58110. } From 3353c100bb97954edc11c1bc07fd07db9d4bc567 Mon Sep 17 00:00:00 2001 From: matloob Date: Wed, 26 Nov 2025 10:30:34 -0500 Subject: [PATCH 074/140] cmd/go: remove experiment checks for compile -c There's a comment that we should test that compile -c is compatible with the fieldtrack and preemptibleloops experiments and then remove the check disabling -c when those experiments are enabled. I tested this and the tests pass with fieldtrack (with the exception of one go command test that makes the assumption that fieldtrack is off), and the preemptibleloops experiment is already broken without this experiment. Also remove the check for the value of the GO19CONCURRENTCOMPILATION environment variable. The compiler concurrency can be limited by setting GOMAXPROCS. Change-Id: I0c02745de463ea572673648061185cd76a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/724680 Reviewed-by: Alan Donovan Reviewed-by: Cherry Mui Auto-Submit: Michael Matloob LUCI-TryBot-Result: Go LUCI --- src/cmd/go/internal/work/build.go | 2 -- src/cmd/go/internal/work/gc.go | 24 ------------------------ 2 files changed, 26 deletions(-) diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go index c483c19c65b..75d05d65de2 100644 --- a/src/cmd/go/internal/work/build.go +++ b/src/cmd/go/internal/work/build.go @@ -238,8 +238,6 @@ See also: go install, go get, go clean. `, } -const concurrentGCBackendCompilationEnabledByDefault = true - func init() { // break init cycle CmdBuild.Run = runBuild diff --git a/src/cmd/go/internal/work/gc.go b/src/cmd/go/internal/work/gc.go index bbb087b436b..9a5e6c924c3 100644 --- a/src/cmd/go/internal/work/gc.go +++ b/src/cmd/go/internal/work/gc.go @@ -10,7 +10,6 @@ import ( "internal/buildcfg" "internal/platform" "io" - "log" "os" "path/filepath" "runtime" @@ -183,29 +182,6 @@ func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg // compilerConcurrency returns the compiler concurrency level for a package compilation. // The returned function must be called after the compile finishes. func compilerConcurrency() (int, func()) { - // First, check whether we can use -c at all for this compilation. - canDashC := concurrentGCBackendCompilationEnabledByDefault - - switch e := os.Getenv("GO19CONCURRENTCOMPILATION"); e { - case "0": - canDashC = false - case "1": - canDashC = true - case "": - // Not set. Use default. - default: - log.Fatalf("GO19CONCURRENTCOMPILATION must be 0, 1, or unset, got %q", e) - } - - // TODO: Test and delete these conditions. - if cfg.ExperimentErr != nil || cfg.Experiment.FieldTrack || cfg.Experiment.PreemptibleLoops { - canDashC = false - } - - if !canDashC { - return 1, func() {} - } - // Decide how many concurrent backend compilations to allow. // // If we allow too many, in theory we might end up with p concurrent processes, From 3fd9cb1895d37682096cde4229e45bea1428dfbe Mon Sep 17 00:00:00 2001 From: Junyang Shao Date: Tue, 25 Nov 2025 01:37:13 +0000 Subject: [PATCH 075/140] cmd/compile: fix bloop get name logic This CL change getNameFrom impl to pattern match addressible patterns. Change-Id: If1faa22a3a012d501e911d8468a5702b348abf16 Reviewed-on: https://go-review.googlesource.com/c/go/+/724180 LUCI-TryBot-Result: Go LUCI Reviewed-by: David Chase --- src/cmd/compile/internal/bloop/bloop.go | 127 ++++++++++++++---------- test/bloop.go | 6 +- 2 files changed, 81 insertions(+), 52 deletions(-) diff --git a/src/cmd/compile/internal/bloop/bloop.go b/src/cmd/compile/internal/bloop/bloop.go index 1e7f915daaa..e4a86d89142 100644 --- a/src/cmd/compile/internal/bloop/bloop.go +++ b/src/cmd/compile/internal/bloop/bloop.go @@ -42,40 +42,42 @@ import ( "cmd/compile/internal/reflectdata" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" - "fmt" + "cmd/internal/src" ) // getNameFromNode tries to iteratively peel down the node to // get the name. func getNameFromNode(n ir.Node) *ir.Name { - var ret *ir.Name - if n.Op() == ir.ONAME { - ret = n.(*ir.Name) - } else { - // avoid infinite recursion on circular referencing nodes. - seen := map[ir.Node]bool{n: true} - var findName func(ir.Node) bool - findName = func(a ir.Node) bool { - if a.Op() == ir.ONAME { - ret = a.(*ir.Name) - return true - } - if !seen[a] { - seen[a] = true - return ir.DoChildren(a, findName) - } - return false + // Tries to iteratively peel down the node to get the names. + for n != nil { + switch n.Op() { + case ir.ONAME: + // Found the name, stop the loop. + return n.(*ir.Name) + case ir.OSLICE, ir.OSLICE3: + n = n.(*ir.SliceExpr).X + case ir.ODOT: + n = n.(*ir.SelectorExpr).X + case ir.OCONV, ir.OCONVIFACE, ir.OCONVNOP: + n = n.(*ir.ConvExpr).X + case ir.OADDR: + n = n.(*ir.AddrExpr).X + case ir.ODOTPTR: + n = n.(*ir.SelectorExpr).X + case ir.OINDEX, ir.OINDEXMAP: + n = n.(*ir.IndexExpr).X + default: + n = nil } - ir.DoChildren(n, findName) } - return ret + return nil } // keepAliveAt returns a statement that is either curNode, or a // block containing curNode followed by a call to runtime.keepAlive for each -// ONAME in ns. These calls ensure that names in ns will be live until +// node in ns. These calls ensure that nodes in ns will be live until // after curNode's execution. -func keepAliveAt(ns []*ir.Name, curNode ir.Node) ir.Node { +func keepAliveAt(ns []ir.Node, curNode ir.Node) ir.Node { if len(ns) == 0 { return curNode } @@ -109,12 +111,12 @@ func keepAliveAt(ns []*ir.Name, curNode ir.Node) ir.Node { return ir.NewBlockStmt(pos, calls) } -func debugName(name *ir.Name, line string) { - if base.Flag.LowerM > 0 { +func debugName(name *ir.Name, pos src.XPos) { + if base.Flag.LowerM > 1 { if name.Linksym() != nil { - fmt.Printf("%v: %s will be kept alive\n", line, name.Linksym().Name) + base.WarnfAt(pos, "%s will be kept alive", name.Linksym().Name) } else { - fmt.Printf("%v: expr will be kept alive\n", line) + base.WarnfAt(pos, "expr will be kept alive") } } } @@ -129,29 +131,50 @@ func preserveStmt(curFn *ir.Func, stmt ir.Node) (ret ir.Node) { // Peel down struct and slice indexing to get the names name := getNameFromNode(n.X) if name != nil { - debugName(name, ir.Line(stmt)) - ret = keepAliveAt([]*ir.Name{name}, n) + debugName(name, n.Pos()) + ret = keepAliveAt([]ir.Node{name}, n) + } else if deref := n.X.(*ir.StarExpr); deref != nil { + ret = keepAliveAt([]ir.Node{deref}, n) + if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "dereference will be kept alive") + } + } else if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "expr is unknown to bloop pass") } case *ir.AssignListStmt: - names := []*ir.Name{} + ns := []ir.Node{} for _, lhs := range n.Lhs { name := getNameFromNode(lhs) if name != nil { - debugName(name, ir.Line(stmt)) - names = append(names, name) + debugName(name, n.Pos()) + ns = append(ns, name) + } else if deref := lhs.(*ir.StarExpr); deref != nil { + ns = append(ns, deref) + if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "dereference will be kept alive") + } + } else if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "expr is unknown to bloop pass") } } - ret = keepAliveAt(names, n) + ret = keepAliveAt(ns, n) case *ir.AssignOpStmt: name := getNameFromNode(n.X) if name != nil { - debugName(name, ir.Line(stmt)) - ret = keepAliveAt([]*ir.Name{name}, n) + debugName(name, n.Pos()) + ret = keepAliveAt([]ir.Node{name}, n) + } else if deref := n.X.(*ir.StarExpr); deref != nil { + ret = keepAliveAt([]ir.Node{deref}, n) + if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "dereference will be kept alive") + } + } else if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "expr is unknown to bloop pass") } case *ir.CallExpr: - names := []*ir.Name{} curNode := stmt if n.Fun != nil && n.Fun.Type() != nil && n.Fun.Type().NumResults() != 0 { + ns := []ir.Node{} // This function's results are not assigned, assign them to // auto tmps and then keepAliveAt these autos. // Note: markStmt assumes the context that it's called - this CallExpr is @@ -161,7 +184,7 @@ func preserveStmt(curFn *ir.Func, stmt ir.Node) (ret ir.Node) { for i, res := range results { tmp := typecheck.TempAt(n.Pos(), curFn, res.Type) lhs[i] = tmp - names = append(names, tmp) + ns = append(ns, tmp) } // Create an assignment statement. @@ -174,33 +197,35 @@ func preserveStmt(curFn *ir.Func, stmt ir.Node) (ret ir.Node) { if len(results) > 1 { plural = "s" } - if base.Flag.LowerM > 0 { - fmt.Printf("%v: function result%s will be kept alive\n", ir.Line(stmt), plural) + if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "function result%s will be kept alive", plural) } + ret = keepAliveAt(ns, curNode) } else { // This function probably doesn't return anything, keep its args alive. argTmps := []ir.Node{} + names := []ir.Node{} for i, a := range n.Args { if name := getNameFromNode(a); name != nil { // If they are name, keep them alive directly. - debugName(name, ir.Line(stmt)) + debugName(name, n.Pos()) names = append(names, name) } else if a.Op() == ir.OSLICELIT { // variadic args are encoded as slice literal. s := a.(*ir.CompLitExpr) - ns := []*ir.Name{} - for i, n := range s.List { - if name := getNameFromNode(n); name != nil { - debugName(name, ir.Line(a)) + ns := []ir.Node{} + for i, elem := range s.List { + if name := getNameFromNode(elem); name != nil { + debugName(name, n.Pos()) ns = append(ns, name) } else { // We need a temporary to save this arg. - tmp := typecheck.TempAt(n.Pos(), curFn, n.Type()) - argTmps = append(argTmps, typecheck.AssignExpr(ir.NewAssignStmt(n.Pos(), tmp, n))) + tmp := typecheck.TempAt(elem.Pos(), curFn, elem.Type()) + argTmps = append(argTmps, typecheck.AssignExpr(ir.NewAssignStmt(elem.Pos(), tmp, elem))) names = append(names, tmp) s.List[i] = tmp - if base.Flag.LowerM > 0 { - fmt.Printf("%v: function arg will be kept alive\n", ir.Line(n)) + if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "function arg will be kept alive") } } } @@ -212,8 +237,8 @@ func preserveStmt(curFn *ir.Func, stmt ir.Node) (ret ir.Node) { argTmps = append(argTmps, typecheck.AssignExpr(ir.NewAssignStmt(n.Pos(), tmp, a))) names = append(names, tmp) n.Args[i] = tmp - if base.Flag.LowerM > 0 { - fmt.Printf("%v: function arg will be kept alive\n", ir.Line(stmt)) + if base.Flag.LowerM > 1 { + base.WarnfAt(n.Pos(), "function arg will be kept alive") } } } @@ -221,8 +246,8 @@ func preserveStmt(curFn *ir.Func, stmt ir.Node) (ret ir.Node) { argTmps = append(argTmps, n) curNode = ir.NewBlockStmt(n.Pos(), argTmps) } + ret = keepAliveAt(names, curNode) } - ret = keepAliveAt(names, curNode) } return } @@ -282,6 +307,8 @@ func (e editor) edit(n ir.Node) ir.Node { preserveStmts(e.curFn, n.Body) case *ir.CommClause: preserveStmts(e.curFn, n.Body) + case *ir.RangeStmt: + preserveStmts(e.curFn, n.Body) } } return n diff --git a/test/bloop.go b/test/bloop.go index 0d2dcba0ac6..a19d8345b00 100644 --- a/test/bloop.go +++ b/test/bloop.go @@ -1,4 +1,4 @@ -// errorcheck -0 -m +// errorcheck -0 -m=2 // Copyright 2025 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style @@ -25,10 +25,11 @@ func caninlineVariadic(x ...int) { // ERROR "can inline caninlineVariadic" "x do something = x[0] } -func test(b *testing.B, localsink, cond int) { // ERROR "leaking param: b" +func test(b *testing.B, localsink, cond int) { // ERROR ".*" for i := 0; i < b.N; i++ { caninline(1) // ERROR "inlining call to caninline" } + somethingptr := &something for b.Loop() { // ERROR "inlining call to testing\.\(\*B\)\.Loop" caninline(1) // ERROR "inlining call to caninline" "function result will be kept alive" ".* does not escape" caninlineNoRet(1) // ERROR "inlining call to caninlineNoRet" "function arg will be kept alive" ".* does not escape" @@ -37,6 +38,7 @@ func test(b *testing.B, localsink, cond int) { // ERROR "leaking param: b" localsink = caninline(1) // ERROR "inlining call to caninline" "localsink will be kept alive" ".* does not escape" localsink += 5 // ERROR "localsink will be kept alive" ".* does not escape" localsink, cond = 1, 2 // ERROR "localsink will be kept alive" "cond will be kept alive" ".* does not escape" + *somethingptr = 1 // ERROR "dereference will be kept alive" if cond > 0 { caninline(1) // ERROR "inlining call to caninline" "function result will be kept alive" ".* does not escape" } From 992ad55e3dcea4bd017d618d759cb9cd3529f288 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Wed, 26 Nov 2025 21:11:35 +0100 Subject: [PATCH 076/140] crypto/tls: support crypto.MessageSigner private keys Fixes #75656 Change-Id: I6bc71c80973765ef995d17b1450ea2026a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/724820 Auto-Submit: Filippo Valsorda Reviewed-by: Nicholas Husin Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI Reviewed-by: Nicholas Husin --- .../6-stdlib/99-minor/crypto/tls/75656.md | 2 + src/crypto/tls/auth.go | 58 +++++++--- src/crypto/tls/common.go | 7 +- src/crypto/tls/handshake_client.go | 36 +++--- src/crypto/tls/handshake_client_tls13.go | 6 +- src/crypto/tls/handshake_server.go | 18 ++- src/crypto/tls/handshake_server_tls13.go | 6 +- src/crypto/tls/key_agreement.go | 107 +++++++++--------- src/crypto/tls/prf.go | 20 +--- src/crypto/tls/tls_test.go | 71 ++++++++++++ 10 files changed, 214 insertions(+), 117 deletions(-) create mode 100644 doc/next/6-stdlib/99-minor/crypto/tls/75656.md diff --git a/doc/next/6-stdlib/99-minor/crypto/tls/75656.md b/doc/next/6-stdlib/99-minor/crypto/tls/75656.md new file mode 100644 index 00000000000..a2b8d9bf9c6 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/tls/75656.md @@ -0,0 +1,2 @@ +If [Certificate.PrivateKey] implements [crypto.MessageSigner], its SignMessage +method is used instead of Sign in TLS 1.2 and later. diff --git a/src/crypto/tls/auth.go b/src/crypto/tls/auth.go index 7169e471056..1b26dd50ef8 100644 --- a/src/crypto/tls/auth.go +++ b/src/crypto/tls/auth.go @@ -18,9 +18,13 @@ import ( "slices" ) -// verifyHandshakeSignature verifies a signature against pre-hashed -// (if required) handshake contents. +// verifyHandshakeSignature verifies a signature against unhashed handshake contents. func verifyHandshakeSignature(sigType uint8, pubkey crypto.PublicKey, hashFunc crypto.Hash, signed, sig []byte) error { + if hashFunc != directSigning { + h := hashFunc.New() + h.Write(signed) + signed = h.Sum(nil) + } switch sigType { case signatureECDSA: pubKey, ok := pubkey.(*ecdsa.PublicKey) @@ -61,6 +65,32 @@ func verifyHandshakeSignature(sigType uint8, pubkey crypto.PublicKey, hashFunc c return nil } +// verifyLegacyHandshakeSignature verifies a TLS 1.0 and 1.1 signature against +// pre-hashed handshake contents. +func verifyLegacyHandshakeSignature(sigType uint8, pubkey crypto.PublicKey, hashFunc crypto.Hash, hashed, sig []byte) error { + switch sigType { + case signatureECDSA: + pubKey, ok := pubkey.(*ecdsa.PublicKey) + if !ok { + return fmt.Errorf("expected an ECDSA public key, got %T", pubkey) + } + if !ecdsa.VerifyASN1(pubKey, hashed, sig) { + return errors.New("ECDSA verification failure") + } + case signaturePKCS1v15: + pubKey, ok := pubkey.(*rsa.PublicKey) + if !ok { + return fmt.Errorf("expected an RSA public key, got %T", pubkey) + } + if err := rsa.VerifyPKCS1v15(pubKey, hashFunc, hashed, sig); err != nil { + return err + } + default: + return errors.New("internal error: unknown signature type") + } + return nil +} + const ( serverSignatureContext = "TLS 1.3, server CertificateVerify\x00" clientSignatureContext = "TLS 1.3, client CertificateVerify\x00" @@ -77,21 +107,15 @@ var signaturePadding = []byte{ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, } -// signedMessage returns the pre-hashed (if necessary) message to be signed by -// certificate keys in TLS 1.3. See RFC 8446, Section 4.4.3. -func signedMessage(sigHash crypto.Hash, context string, transcript hash.Hash) []byte { - if sigHash == directSigning { - b := &bytes.Buffer{} - b.Write(signaturePadding) - io.WriteString(b, context) - b.Write(transcript.Sum(nil)) - return b.Bytes() - } - h := sigHash.New() - h.Write(signaturePadding) - io.WriteString(h, context) - h.Write(transcript.Sum(nil)) - return h.Sum(nil) +// signedMessage returns the (unhashed) message to be signed by certificate keys +// in TLS 1.3. See RFC 8446, Section 4.4.3. +func signedMessage(context string, transcript hash.Hash) []byte { + const maxSize = 64 /* signaturePadding */ + len(serverSignatureContext) + 512/8 /* SHA-512 */ + b := bytes.NewBuffer(make([]byte, 0, maxSize)) + b.Write(signaturePadding) + io.WriteString(b, context) + b.Write(transcript.Sum(nil)) + return b.Bytes() } // typeAndHashFromSignatureScheme returns the corresponding signature type and diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index 993cfaf7c06..099a11ca63d 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -1597,9 +1597,14 @@ var writerMutex sync.Mutex type Certificate struct { Certificate [][]byte // PrivateKey contains the private key corresponding to the public key in - // Leaf. This must implement crypto.Signer with an RSA, ECDSA or Ed25519 PublicKey. + // Leaf. This must implement [crypto.Signer] with an RSA, ECDSA or Ed25519 + // PublicKey. + // // For a server up to TLS 1.2, it can also implement crypto.Decrypter with // an RSA PublicKey. + // + // If it implements [crypto.MessageSigner], SignMessage will be used instead + // of Sign for TLS 1.2 and later. PrivateKey crypto.PrivateKey // SupportedSignatureAlgorithms is an optional list restricting what // signature algorithms the PrivateKey can be used for. diff --git a/src/crypto/tls/handshake_client.go b/src/crypto/tls/handshake_client.go index e1ddcb3f106..c2b1b7037a4 100644 --- a/src/crypto/tls/handshake_client.go +++ b/src/crypto/tls/handshake_client.go @@ -781,15 +781,13 @@ func (hs *clientHandshakeState) doFullHandshake() error { return fmt.Errorf("tls: client certificate private key of type %T does not implement crypto.Signer", chainToSend.PrivateKey) } - var sigType uint8 - var sigHash crypto.Hash if c.vers >= VersionTLS12 { signatureAlgorithm, err := selectSignatureScheme(c.vers, chainToSend, certReq.supportedSignatureAlgorithms) if err != nil { c.sendAlert(alertHandshakeFailure) return err } - sigType, sigHash, err = typeAndHashFromSignatureScheme(signatureAlgorithm) + sigType, sigHash, err := typeAndHashFromSignatureScheme(signatureAlgorithm) if err != nil { return c.sendAlert(alertInternalError) } @@ -799,23 +797,31 @@ func (hs *clientHandshakeState) doFullHandshake() error { tlssha1.Value() // ensure godebug is initialized tlssha1.IncNonDefault() } + if hs.finishedHash.buffer == nil { + c.sendAlert(alertInternalError) + return errors.New("tls: internal error: did not keep handshake transcript for TLS 1.2") + } + signOpts := crypto.SignerOpts(sigHash) + if sigType == signatureRSAPSS { + signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash} + } + certVerify.signature, err = crypto.SignMessage(key, c.config.rand(), hs.finishedHash.buffer, signOpts) + if err != nil { + c.sendAlert(alertInternalError) + return err + } } else { - sigType, sigHash, err = legacyTypeAndHashFromPublicKey(key.Public()) + sigType, sigHash, err := legacyTypeAndHashFromPublicKey(key.Public()) if err != nil { c.sendAlert(alertIllegalParameter) return err } - } - - signed := hs.finishedHash.hashForClientCertificate(sigType, sigHash) - signOpts := crypto.SignerOpts(sigHash) - if sigType == signatureRSAPSS { - signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash} - } - certVerify.signature, err = key.Sign(c.config.rand(), signed, signOpts) - if err != nil { - c.sendAlert(alertInternalError) - return err + signed := hs.finishedHash.hashForClientCertificate(sigType) + certVerify.signature, err = key.Sign(c.config.rand(), signed, sigHash) + if err != nil { + c.sendAlert(alertInternalError) + return err + } } if _, err := hs.c.writeHandshakeRecord(certVerify, &hs.finishedHash); err != nil { diff --git a/src/crypto/tls/handshake_client_tls13.go b/src/crypto/tls/handshake_client_tls13.go index 2912d97f75e..e696bd3a13d 100644 --- a/src/crypto/tls/handshake_client_tls13.go +++ b/src/crypto/tls/handshake_client_tls13.go @@ -664,7 +664,7 @@ func (hs *clientHandshakeStateTLS13) readServerCertificate() error { if sigType == signaturePKCS1v15 || sigHash == crypto.SHA1 { return c.sendAlert(alertInternalError) } - signed := signedMessage(sigHash, serverSignatureContext, hs.transcript) + signed := signedMessage(serverSignatureContext, hs.transcript) if err := verifyHandshakeSignature(sigType, c.peerCertificates[0].PublicKey, sigHash, signed, certVerify.signature); err != nil { c.sendAlert(alertDecryptError) @@ -783,12 +783,12 @@ func (hs *clientHandshakeStateTLS13) sendClientCertificate() error { return c.sendAlert(alertInternalError) } - signed := signedMessage(sigHash, clientSignatureContext, hs.transcript) + signed := signedMessage(clientSignatureContext, hs.transcript) signOpts := crypto.SignerOpts(sigHash) if sigType == signatureRSAPSS { signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash} } - sig, err := cert.PrivateKey.(crypto.Signer).Sign(c.config.rand(), signed, signOpts) + sig, err := crypto.SignMessage(cert.PrivateKey.(crypto.Signer), c.config.rand(), signed, signOpts) if err != nil { c.sendAlert(alertInternalError) return errors.New("tls: failed to sign handshake: " + err.Error()) diff --git a/src/crypto/tls/handshake_server.go b/src/crypto/tls/handshake_server.go index 2d3efffcf05..efdaeae6f7e 100644 --- a/src/crypto/tls/handshake_server.go +++ b/src/crypto/tls/handshake_server.go @@ -780,19 +780,27 @@ func (hs *serverHandshakeState) doFullHandshake() error { tlssha1.Value() // ensure godebug is initialized tlssha1.IncNonDefault() } + if hs.finishedHash.buffer == nil { + c.sendAlert(alertInternalError) + return errors.New("tls: internal error: did not keep handshake transcript for TLS 1.2") + } + if err := verifyHandshakeSignature(sigType, pub, sigHash, hs.finishedHash.buffer, certVerify.signature); err != nil { + c.sendAlert(alertDecryptError) + return errors.New("tls: invalid signature by the client certificate: " + err.Error()) + } } else { sigType, sigHash, err = legacyTypeAndHashFromPublicKey(pub) if err != nil { c.sendAlert(alertIllegalParameter) return err } + signed := hs.finishedHash.hashForClientCertificate(sigType) + if err := verifyLegacyHandshakeSignature(sigType, pub, sigHash, signed, certVerify.signature); err != nil { + c.sendAlert(alertDecryptError) + return errors.New("tls: invalid signature by the client certificate: " + err.Error()) + } } - signed := hs.finishedHash.hashForClientCertificate(sigType, sigHash) - if err := verifyHandshakeSignature(sigType, pub, sigHash, signed, certVerify.signature); err != nil { - c.sendAlert(alertDecryptError) - return errors.New("tls: invalid signature by the client certificate: " + err.Error()) - } c.peerSigAlg = certVerify.signatureAlgorithm if err := transcriptMsg(certVerify, &hs.finishedHash); err != nil { diff --git a/src/crypto/tls/handshake_server_tls13.go b/src/crypto/tls/handshake_server_tls13.go index 1182307936c..3bed1359a3c 100644 --- a/src/crypto/tls/handshake_server_tls13.go +++ b/src/crypto/tls/handshake_server_tls13.go @@ -845,12 +845,12 @@ func (hs *serverHandshakeStateTLS13) sendServerCertificate() error { return c.sendAlert(alertInternalError) } - signed := signedMessage(sigHash, serverSignatureContext, hs.transcript) + signed := signedMessage(serverSignatureContext, hs.transcript) signOpts := crypto.SignerOpts(sigHash) if sigType == signatureRSAPSS { signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash} } - sig, err := hs.cert.PrivateKey.(crypto.Signer).Sign(c.config.rand(), signed, signOpts) + sig, err := crypto.SignMessage(hs.cert.PrivateKey.(crypto.Signer), c.config.rand(), signed, signOpts) if err != nil { public := hs.cert.PrivateKey.(crypto.Signer).Public() if rsaKey, ok := public.(*rsa.PublicKey); ok && sigType == signatureRSAPSS && @@ -1081,7 +1081,7 @@ func (hs *serverHandshakeStateTLS13) readClientCertificate() error { if sigType == signaturePKCS1v15 || sigHash == crypto.SHA1 { return c.sendAlert(alertInternalError) } - signed := signedMessage(sigHash, clientSignatureContext, hs.transcript) + signed := signedMessage(clientSignatureContext, hs.transcript) if err := verifyHandshakeSignature(sigType, c.peerCertificates[0].PublicKey, sigHash, signed, certVerify.signature); err != nil { c.sendAlert(alertDecryptError) diff --git a/src/crypto/tls/key_agreement.go b/src/crypto/tls/key_agreement.go index 26f7bd2c520..ad2be5ddf9b 100644 --- a/src/crypto/tls/key_agreement.go +++ b/src/crypto/tls/key_agreement.go @@ -127,25 +127,8 @@ func md5SHA1Hash(slices [][]byte) []byte { } // hashForServerKeyExchange hashes the given slices and returns their digest -// using the given hash function (for TLS 1.2) or using a default based on -// the sigType (for earlier TLS versions). For Ed25519 signatures, which don't -// do pre-hashing, it returns the concatenation of the slices. -func hashForServerKeyExchange(sigType uint8, hashFunc crypto.Hash, version uint16, slices ...[]byte) []byte { - if sigType == signatureEd25519 { - var signed []byte - for _, slice := range slices { - signed = append(signed, slice...) - } - return signed - } - if version >= VersionTLS12 { - h := hashFunc.New() - for _, slice := range slices { - h.Write(slice) - } - digest := h.Sum(nil) - return digest - } +// using a hash based on the sigType. It can only be used for TLS 1.0 and 1.1. +func hashForServerKeyExchange(sigType uint8, slices ...[]byte) []byte { if sigType == signatureECDSA { return sha1Hash(slices) } @@ -207,14 +190,13 @@ func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Cer return nil, fmt.Errorf("tls: certificate private key of type %T does not implement crypto.Signer", cert.PrivateKey) } - var sigType uint8 - var sigHash crypto.Hash + var sig []byte if ka.version >= VersionTLS12 { ka.signatureAlgorithm, err = selectSignatureScheme(ka.version, cert, clientHello.supportedSignatureAlgorithms) if err != nil { return nil, err } - sigType, sigHash, err = typeAndHashFromSignatureScheme(ka.signatureAlgorithm) + sigType, sigHash, err := typeAndHashFromSignatureScheme(ka.signatureAlgorithm) if err != nil { return nil, err } @@ -222,25 +204,31 @@ func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Cer tlssha1.Value() // ensure godebug is initialized tlssha1.IncNonDefault() } + signed := slices.Concat(clientHello.random, hello.random, serverECDHEParams) + if (sigType == signaturePKCS1v15 || sigType == signatureRSAPSS) != ka.isRSA { + return nil, errors.New("tls: certificate cannot be used with the selected cipher suite") + } + signOpts := crypto.SignerOpts(sigHash) + if sigType == signatureRSAPSS { + signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash} + } + sig, err = crypto.SignMessage(priv, config.rand(), signed, signOpts) + if err != nil { + return nil, errors.New("tls: failed to sign ECDHE parameters: " + err.Error()) + } } else { - sigType, sigHash, err = legacyTypeAndHashFromPublicKey(priv.Public()) + sigType, sigHash, err := legacyTypeAndHashFromPublicKey(priv.Public()) if err != nil { return nil, err } - } - if (sigType == signaturePKCS1v15 || sigType == signatureRSAPSS) != ka.isRSA { - return nil, errors.New("tls: certificate cannot be used with the selected cipher suite") - } - - signed := hashForServerKeyExchange(sigType, sigHash, ka.version, clientHello.random, hello.random, serverECDHEParams) - - signOpts := crypto.SignerOpts(sigHash) - if sigType == signatureRSAPSS { - signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash} - } - sig, err := priv.Sign(config.rand(), signed, signOpts) - if err != nil { - return nil, errors.New("tls: failed to sign ECDHE parameters: " + err.Error()) + signed := hashForServerKeyExchange(sigType, clientHello.random, hello.random, serverECDHEParams) + if (sigType == signaturePKCS1v15) != ka.isRSA { + return nil, errors.New("tls: certificate cannot be used with the selected cipher suite") + } + sig, err = priv.Sign(config.rand(), signed, sigHash) + if err != nil { + return nil, errors.New("tls: failed to sign ECDHE parameters: " + err.Error()) + } } skx := new(serverKeyExchangeMsg) @@ -300,6 +288,18 @@ func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHell if len(sig) < 2 { return errServerKeyExchange } + if ka.version >= VersionTLS12 { + ka.signatureAlgorithm = SignatureScheme(sig[0])<<8 | SignatureScheme(sig[1]) + sig = sig[2:] + if len(sig) < 2 { + return errServerKeyExchange + } + } + sigLen := int(sig[0])<<8 | int(sig[1]) + if sigLen+2 != len(sig) { + return errServerKeyExchange + } + sig = sig[2:] if !slices.Contains(clientHello.supportedCurves, ka.curveID) { return errors.New("tls: server selected unoffered curve") @@ -333,12 +333,6 @@ func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHell var sigType uint8 var sigHash crypto.Hash if ka.version >= VersionTLS12 { - ka.signatureAlgorithm = SignatureScheme(sig[0])<<8 | SignatureScheme(sig[1]) - sig = sig[2:] - if len(sig) < 2 { - return errServerKeyExchange - } - if !isSupportedSignatureAlgorithm(ka.signatureAlgorithm, clientHello.supportedSignatureAlgorithms) { return errors.New("tls: certificate used with invalid signature algorithm") } @@ -350,26 +344,27 @@ func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHell tlssha1.Value() // ensure godebug is initialized tlssha1.IncNonDefault() } + if (sigType == signaturePKCS1v15 || sigType == signatureRSAPSS) != ka.isRSA { + return errServerKeyExchange + } + signed := slices.Concat(clientHello.random, serverHello.random, serverECDHEParams) + if err := verifyHandshakeSignature(sigType, cert.PublicKey, sigHash, signed, sig); err != nil { + return errors.New("tls: invalid signature by the server certificate: " + err.Error()) + } } else { sigType, sigHash, err = legacyTypeAndHashFromPublicKey(cert.PublicKey) if err != nil { return err } - } - if (sigType == signaturePKCS1v15 || sigType == signatureRSAPSS) != ka.isRSA { - return errServerKeyExchange + if (sigType == signaturePKCS1v15) != ka.isRSA { + return errServerKeyExchange + } + signed := hashForServerKeyExchange(sigType, clientHello.random, serverHello.random, serverECDHEParams) + if err := verifyLegacyHandshakeSignature(sigType, cert.PublicKey, sigHash, signed, sig); err != nil { + return errors.New("tls: invalid signature by the server certificate: " + err.Error()) + } } - sigLen := int(sig[0])<<8 | int(sig[1]) - if sigLen+2 != len(sig) { - return errServerKeyExchange - } - sig = sig[2:] - - signed := hashForServerKeyExchange(sigType, sigHash, ka.version, clientHello.random, serverHello.random, serverECDHEParams) - if err := verifyHandshakeSignature(sigType, cert.PublicKey, sigHash, signed, sig); err != nil { - return errors.New("tls: invalid signature by the server certificate: " + err.Error()) - } return nil } diff --git a/src/crypto/tls/prf.go b/src/crypto/tls/prf.go index e7369542a73..19f80ac2c91 100644 --- a/src/crypto/tls/prf.go +++ b/src/crypto/tls/prf.go @@ -221,23 +221,9 @@ func (h finishedHash) serverSum(masterSecret []byte) []byte { return h.prf(masterSecret, serverFinishedLabel, h.Sum(), finishedVerifyLength) } -// hashForClientCertificate returns the handshake messages so far, pre-hashed if -// necessary, suitable for signing by a TLS client certificate. -func (h finishedHash) hashForClientCertificate(sigType uint8, hashAlg crypto.Hash) []byte { - if (h.version >= VersionTLS12 || sigType == signatureEd25519) && h.buffer == nil { - panic("tls: handshake hash for a client certificate requested after discarding the handshake buffer") - } - - if sigType == signatureEd25519 { - return h.buffer - } - - if h.version >= VersionTLS12 { - hash := hashAlg.New() - hash.Write(h.buffer) - return hash.Sum(nil) - } - +// hashForClientCertificate returns the handshake messages so far, pre-hashed, +// suitable for signing by a TLS 1.0 and 1.1 client certificate. +func (h finishedHash) hashForClientCertificate(sigType uint8) []byte { if sigType == signatureECDSA { return h.server.Sum(nil) } diff --git a/src/crypto/tls/tls_test.go b/src/crypto/tls/tls_test.go index af2828fd8da..39ebb9d2f1e 100644 --- a/src/crypto/tls/tls_test.go +++ b/src/crypto/tls/tls_test.go @@ -2390,3 +2390,74 @@ func TestECH(t *testing.T) { check() } + +func TestMessageSigner(t *testing.T) { + t.Run("TLSv10", func(t *testing.T) { testMessageSigner(t, VersionTLS10) }) + t.Run("TLSv12", func(t *testing.T) { testMessageSigner(t, VersionTLS12) }) + t.Run("TLSv13", func(t *testing.T) { testMessageSigner(t, VersionTLS13) }) +} + +func testMessageSigner(t *testing.T, version uint16) { + clientConfig, serverConfig := testConfig.Clone(), testConfig.Clone() + serverConfig.ClientAuth = RequireAnyClientCert + clientConfig.MinVersion = version + clientConfig.MaxVersion = version + serverConfig.MinVersion = version + serverConfig.MaxVersion = version + clientConfig.Certificates = []Certificate{{ + Certificate: [][]byte{testRSACertificate}, + PrivateKey: messageOnlySigner{testRSAPrivateKey}, + }} + serverConfig.Certificates = []Certificate{{ + Certificate: [][]byte{testRSACertificate}, + PrivateKey: messageOnlySigner{testRSAPrivateKey}, + }} + + _, _, err := testHandshake(t, clientConfig, serverConfig) + if version < VersionTLS12 { + if err == nil { + t.Fatal("expected failure for TLS 1.0/1.1") + } + } else { + if err != nil { + t.Fatalf("unexpected failure: %s", err) + } + } + + clientConfig.Certificates = []Certificate{{ + Certificate: [][]byte{testECDSACertificate}, + PrivateKey: messageOnlySigner{testECDSAPrivateKey}, + }} + serverConfig.Certificates = []Certificate{{ + Certificate: [][]byte{testECDSACertificate}, + PrivateKey: messageOnlySigner{testECDSAPrivateKey}, + }} + + _, _, err = testHandshake(t, clientConfig, serverConfig) + if version < VersionTLS12 { + if err == nil { + t.Fatal("expected failure for TLS 1.0/1.1") + } + } else { + if err != nil { + t.Fatalf("unexpected failure: %s", err) + } + } +} + +type messageOnlySigner struct{ crypto.Signer } + +func (s messageOnlySigner) Public() crypto.PublicKey { + return s.Signer.Public() +} + +func (s messageOnlySigner) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) { + return nil, errors.New("messageOnlySigner: Sign called") +} + +func (s messageOnlySigner) SignMessage(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) { + h := opts.HashFunc().New() + h.Write(msg) + digest := h.Sum(nil) + return s.Signer.Sign(rand, digest, opts) +} From 0f6397384b583a18bae90421a71b6abad39a437f Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Mon, 21 Jul 2025 14:35:08 -0400 Subject: [PATCH 077/140] go/types: relax NewSignatureType for append(slice, str...) CL 688815 contained a partial fix for the reported bug, but NewSignatureType continued to panic. This change relaxes it to permit construction of the type "func([]byte, B) []byte" where "type B []byte". We must do so because a client may instantiate the type "func([]byte, T...)" where [T ~string|~[]byte] at T=B, and may have no way to know that they are dealing with this very special edge case of append. Added a regression test of NewSignatureType, which I should have done in the earlier CL. Also, make typestring less pedantic and fragile. Fixes #73871 Change-Id: I3d8f8609582149f9c9f8402a04ad516c2c63bbc6 Reviewed-on: https://go-review.googlesource.com/c/go/+/689277 TryBot-Bypass: Alan Donovan Reviewed-by: Robert Findley Auto-Submit: Alan Donovan Reviewed-by: Mark Freeman --- src/cmd/compile/internal/types2/signature.go | 38 +++++++++++++++---- src/cmd/compile/internal/types2/typestring.go | 23 ++++++----- src/go/types/api_test.go | 19 +++++++--- src/go/types/signature.go | 38 +++++++++++++++---- src/go/types/typestring.go | 23 ++++++----- 5 files changed, 101 insertions(+), 40 deletions(-) diff --git a/src/cmd/compile/internal/types2/signature.go b/src/cmd/compile/internal/types2/signature.go index ea60254fa68..d569ba80139 100644 --- a/src/cmd/compile/internal/types2/signature.go +++ b/src/cmd/compile/internal/types2/signature.go @@ -28,17 +28,28 @@ type Signature struct { recv *Var // nil if not a method params *Tuple // (incoming) parameters from left to right; or nil results *Tuple // (outgoing) results from left to right; or nil - variadic bool // true if the last parameter's type is of the form ...T (or string, for append built-in only) + variadic bool // true if the last parameter's type is of the form ...T + + // If variadic, the last element of params ordinarily has an + // unnamed Slice type. As a special case, in a call to append, + // it may be string, or a TypeParam T whose typeset ⊇ {string, []byte}. + // It may even be a named []byte type if a client instantiates + // T at such a type. } // NewSignatureType creates a new function type for the given receiver, // receiver type parameters, type parameters, parameters, and results. +// // If variadic is set, params must hold at least one parameter and the // last parameter must be an unnamed slice or a type parameter whose // type set has an unnamed slice as common underlying type. -// As a special case, for variadic signatures the last parameter may -// also be a string type, or a type parameter containing a mix of byte -// slices and string types in its type set. +// +// As a special case, to support append([]byte, str...), for variadic +// signatures the last parameter may also be a string type, or a type +// parameter containing a mix of byte slices and string types in its +// type set. It may even be a named []byte slice type resulting from +// substitution of such a type parameter. +// // If recv is non-nil, typeParams must be empty. If recvTypeParams is // non-empty, recv must be non-nil. func NewSignatureType(recv *Var, recvTypeParams, typeParams []*TypeParam, params, results *Tuple, variadic bool) *Signature { @@ -54,17 +65,29 @@ func NewSignatureType(recv *Var, recvTypeParams, typeParams []*TypeParam, params if isString(t) { s = NewSlice(universeByte) } else { - s, _ = Unalias(t).(*Slice) // don't accept a named slice type + // Variadic Go functions have a last parameter of type []T, + // suggesting we should reject a named slice type B here. + // + // However, a call to built-in append(slice, x...) + // where x has a TypeParam type [T ~string | ~[]byte], + // has the type func([]byte, T). Since a client may + // instantiate this type at T=B, we must permit + // named slice types, even when this results in a + // signature func([]byte, B) where type B []byte. + // + // (The caller of NewSignatureType may have no way to + // know that it is dealing with the append special case.) + s, _ = t.Underlying().(*Slice) } if S == nil { S = s - } else if !Identical(S, s) { + } else if s == nil || !Identical(S, s) { S = nil break } } if S == nil { - panic(fmt.Sprintf("got %s, want variadic parameter of unnamed slice or string type", last)) + panic(fmt.Sprintf("got %s, want variadic parameter of slice or string type", last)) } } sig := &Signature{recv: recv, params: params, results: results, variadic: variadic} @@ -98,6 +121,7 @@ func (s *Signature) TypeParams() *TypeParamList { return s.tparams } func (s *Signature) RecvTypeParams() *TypeParamList { return s.rparams } // Params returns the parameters of signature s, or nil. +// See [NewSignatureType] for details of variadic functions. func (s *Signature) Params() *Tuple { return s.params } // Results returns the results of signature s, or nil. diff --git a/src/cmd/compile/internal/types2/typestring.go b/src/cmd/compile/internal/types2/typestring.go index b1f0d0929ba..e68f6a28865 100644 --- a/src/cmd/compile/internal/types2/typestring.go +++ b/src/cmd/compile/internal/types2/typestring.go @@ -449,22 +449,25 @@ func (w *typeWriter) tuple(tup *Tuple, variadic bool) { } typ := v.typ if variadic && i == len(tup.vars)-1 { - if s, ok := typ.(*Slice); ok { + if slice, ok := typ.(*Slice); ok { w.string("...") - typ = s.elem + w.typ(slice.elem) } else { - // special case: - // append(s, "foo"...) leads to signature func([]byte, string...) - if t, _ := typ.Underlying().(*Basic); t == nil || t.kind != String { - w.error("expected string type") - continue - } + // append(slice, str...) entails various special + // cases, especially in conjunction with generics. + // str may be: + // - a string, + // - a TypeParam whose typeset includes string, or + // - a named []byte slice type B resulting from + // a client instantiating append([]byte, T) at T=B. + // For such cases we use the irregular notation + // func([]byte, T...), with the dots after the type. w.typ(typ) w.string("...") - continue } + } else { + w.typ(typ) } - w.typ(typ) } } w.byte(')') diff --git a/src/go/types/api_test.go b/src/go/types/api_test.go index f31b9d30c58..b5f9cbbed6a 100644 --- a/src/go/types/api_test.go +++ b/src/go/types/api_test.go @@ -3180,7 +3180,6 @@ func TestIssue73871(t *testing.T) { func f[T ~[]byte](y T) []byte { return append([]byte(nil), y...) } -// for illustration only: type B []byte var _ = f[B] ` @@ -3196,17 +3195,25 @@ var _ = f[B] // Check type inferred for 'append'. // - // Before the fix, the inferred type of append's y parameter + // Before CL 688815, the inferred type of append's y parameter // was T. When a client such as x/tools/go/ssa instantiated T=B, // it would result in the Signature "func([]byte, B)" with the // variadic flag set, an invalid combination that caused // NewSignatureType to panic. append := f.Decls[0].(*ast.FuncDecl).Body.List[0].(*ast.ReturnStmt).Results[0].(*ast.CallExpr).Fun tAppend := info.TypeOf(append).(*Signature) - want := "func([]byte, ...byte) []byte" - if got := fmt.Sprint(tAppend); got != want { - // Before the fix, tAppend was func([]byte, T) []byte, + if got, want := fmt.Sprint(tAppend), "func([]byte, ...byte) []byte"; got != want { + // Before CL 688815, tAppend was func([]byte, T) []byte, // where T prints as "". - t.Errorf("for append, inferred type %s, want %s", tAppend, want) + t.Errorf("append: got type %s, want %s", got, want) + } + + // Instantiate the type inferred for append(...) at T=B. + // Before the fix in CL 689277, NewSignatureType would panic. + params := slices.Collect(tAppend.Params().Variables()) + params[1] = NewVar(token.NoPos, pkg, "", pkg.Scope().Lookup("B").Type()) + sig := NewSignatureType(nil, nil, nil, NewTuple(params...), tAppend.Results(), true) + if got, want := fmt.Sprint(sig), "func([]byte, p.B...) []byte"; got != want { + t.Errorf("instantiated: got type %s, want %s", got, want) } } diff --git a/src/go/types/signature.go b/src/go/types/signature.go index 2f8be54e171..13f60c07722 100644 --- a/src/go/types/signature.go +++ b/src/go/types/signature.go @@ -29,7 +29,13 @@ type Signature struct { recv *Var // nil if not a method params *Tuple // (incoming) parameters from left to right; or nil results *Tuple // (outgoing) results from left to right; or nil - variadic bool // true if the last parameter's type is of the form ...T (or string, for append built-in only) + variadic bool // true if the last parameter's type is of the form ...T + + // If variadic, the last element of params ordinarily has an + // unnamed Slice type. As a special case, in a call to append, + // it may be string, or a TypeParam T whose typeset ⊇ {string, []byte}. + // It may even be a named []byte type if a client instantiates + // T at such a type. } // NewSignature returns a new function type for the given receiver, parameters, @@ -46,12 +52,17 @@ func NewSignature(recv *Var, params, results *Tuple, variadic bool) *Signature { // NewSignatureType creates a new function type for the given receiver, // receiver type parameters, type parameters, parameters, and results. +// // If variadic is set, params must hold at least one parameter and the // last parameter must be an unnamed slice or a type parameter whose // type set has an unnamed slice as common underlying type. -// As a special case, for variadic signatures the last parameter may -// also be a string type, or a type parameter containing a mix of byte -// slices and string types in its type set. +// +// As a special case, to support append([]byte, str...), for variadic +// signatures the last parameter may also be a string type, or a type +// parameter containing a mix of byte slices and string types in its +// type set. It may even be a named []byte slice type resulting from +// instantiation of such a type parameter. +// // If recv is non-nil, typeParams must be empty. If recvTypeParams is // non-empty, recv must be non-nil. func NewSignatureType(recv *Var, recvTypeParams, typeParams []*TypeParam, params, results *Tuple, variadic bool) *Signature { @@ -67,17 +78,29 @@ func NewSignatureType(recv *Var, recvTypeParams, typeParams []*TypeParam, params if isString(t) { s = NewSlice(universeByte) } else { - s, _ = Unalias(t).(*Slice) // don't accept a named slice type + // Variadic Go functions have a last parameter of type []T, + // suggesting we should reject a named slice type B here. + // + // However, a call to built-in append(slice, x...) + // where x has a TypeParam type [T ~string | ~[]byte], + // has the type func([]byte, T). Since a client may + // instantiate this type at T=B, we must permit + // named slice types, even when this results in a + // signature func([]byte, B) where type B []byte. + // + // (The caller of NewSignatureType may have no way to + // know that it is dealing with the append special case.) + s, _ = t.Underlying().(*Slice) } if S == nil { S = s - } else if !Identical(S, s) { + } else if s == nil || !Identical(S, s) { S = nil break } } if S == nil { - panic(fmt.Sprintf("got %s, want variadic parameter of unnamed slice or string type", last)) + panic(fmt.Sprintf("got %s, want variadic parameter of slice or string type", last)) } } sig := &Signature{recv: recv, params: params, results: results, variadic: variadic} @@ -111,6 +134,7 @@ func (s *Signature) TypeParams() *TypeParamList { return s.tparams } func (s *Signature) RecvTypeParams() *TypeParamList { return s.rparams } // Params returns the parameters of signature s, or nil. +// See [NewSignatureType] for details of variadic functions. func (s *Signature) Params() *Tuple { return s.params } // Results returns the results of signature s, or nil. diff --git a/src/go/types/typestring.go b/src/go/types/typestring.go index bd13459832d..766d2b29602 100644 --- a/src/go/types/typestring.go +++ b/src/go/types/typestring.go @@ -452,22 +452,25 @@ func (w *typeWriter) tuple(tup *Tuple, variadic bool) { } typ := v.typ if variadic && i == len(tup.vars)-1 { - if s, ok := typ.(*Slice); ok { + if slice, ok := typ.(*Slice); ok { w.string("...") - typ = s.elem + w.typ(slice.elem) } else { - // special case: - // append(s, "foo"...) leads to signature func([]byte, string...) - if t, _ := typ.Underlying().(*Basic); t == nil || t.kind != String { - w.error("expected string type") - continue - } + // append(slice, str...) entails various special + // cases, especially in conjunction with generics. + // str may be: + // - a string, + // - a TypeParam whose typeset includes string, or + // - a named []byte slice type B resulting from + // a client instantiating append([]byte, T) at T=B. + // For such cases we use the irregular notation + // func([]byte, T...), with the dots after the type. w.typ(typ) w.string("...") - continue } + } else { + w.typ(typ) } - w.typ(typ) } } w.byte(')') From 0c747b7aa757da0a0a0ac7b2b5834dca84c7c019 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Wed, 26 Nov 2025 17:53:04 -0500 Subject: [PATCH 078/140] go/build/constraint: use strings.Builder instead of for { str+=str } (This works around a bug in the stringsbuilder modernizer.) For #76476 Change-Id: I1cb8715fd79c0363cb9c159686eaeb3482c93228 Reviewed-on: https://go-review.googlesource.com/c/go/+/724721 Reviewed-by: Dmitri Shuralyov TryBot-Bypass: Alan Donovan Reviewed-by: Dmitri Shuralyov --- src/go/build/constraint/expr.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/go/build/constraint/expr.go b/src/go/build/constraint/expr.go index 1f39bb20110..943c8f3444b 100644 --- a/src/go/build/constraint/expr.go +++ b/src/go/build/constraint/expr.go @@ -515,18 +515,18 @@ func PlusBuildLines(x Expr) ([]string, error) { // Prepare the +build lines. var lines []string for _, or := range split { - line := "// +build" + var line strings.Builder + line.WriteString("// +build") for _, and := range or { - clause := "" + line.WriteString(" ") for i, lit := range and { if i > 0 { - clause += "," + line.WriteString(",") } - clause += lit.String() + line.WriteString(lit.String()) } - line += " " + clause } - lines = append(lines, line) + lines = append(lines, line.String()) } return lines, nil From a3fb92a7100f3f2824d483ee0cbcf1264584b3e4 Mon Sep 17 00:00:00 2001 From: Daniel Morsing Date: Thu, 25 Sep 2025 17:26:03 +0100 Subject: [PATCH 079/140] runtime/secret: implement new secret package Implement secret.Do. - When secret.Do returns: - Clear stack that is used by the argument function. - Clear all the registers that might contain secrets. - On stack growth in secret mode, clear the old stack. - When objects are allocated in secret mode, mark them and then zero the marked objects immediately when they are freed. - If the argument function panics, raise that panic as if it originated from secret.Do. This removes anything about the secret function from tracebacks. For now, this is only implemented on linux for arm64 and amd64. This is a rebased version of Keith Randalls initial implementation at CL 600635. I have added arm64 support, signal handling, preemption handling and dealt with vDSOs spilling into system stacks. Fixes #21865 Change-Id: I6fbd5a233beeaceb160785e0c0199a5c94d8e520 Co-authored-by: Keith Randall Reviewed-on: https://go-review.googlesource.com/c/go/+/704615 Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI Auto-Submit: Filippo Valsorda Reviewed-by: Cherry Mui --- doc/next/6-stdlib/1-secret.md | 20 + src/cmd/dist/test.go | 9 + src/go/build/deps_test.go | 1 + .../goexperiment/exp_runtimesecret_off.go | 8 + .../goexperiment/exp_runtimesecret_on.go | 8 + src/internal/goexperiment/flags.go | 3 + src/runtime/_mkmalloc/mkmalloc.go | 3 + src/runtime/asm_amd64.s | 54 +- src/runtime/asm_arm64.s | 55 +- src/runtime/malloc.go | 13 +- src/runtime/malloc_generated.go | 993 ++++++++++++++++++ src/runtime/malloc_stubs.go | 19 + src/runtime/mgc.go | 27 + src/runtime/mheap.go | 10 + src/runtime/preempt.go | 17 + src/runtime/proc.go | 13 + src/runtime/runtime2.go | 18 +- src/runtime/secret.go | 118 +++ src/runtime/secret/asm_amd64.s | 213 ++++ src/runtime/secret/asm_arm64.s | 167 +++ src/runtime/secret/crash_test.go | 427 ++++++++ src/runtime/secret/export.go | 16 + src/runtime/secret/secret.go | 128 +++ src/runtime/secret/secret_test.go | 293 ++++++ src/runtime/secret/stubs.go | 32 + src/runtime/secret/stubs_noasm.go | 13 + src/runtime/secret/testdata/crash.go | 142 +++ src/runtime/secret_amd64.s | 107 ++ src/runtime/secret_arm64.s | 90 ++ src/runtime/secret_asm.go | 9 + src/runtime/secret_noasm.go | 11 + src/runtime/secret_nosecret.go | 32 + src/runtime/signal_linux_amd64.go | 28 + src/runtime/signal_linux_arm64.go | 19 + src/runtime/signal_unix.go | 6 + src/runtime/sizeof_test.go | 2 +- src/runtime/stack.go | 19 + src/runtime/sys_linux_amd64.s | 12 + src/runtime/sys_linux_arm64.s | 14 + src/runtime/time_linux_amd64.s | 10 + src/runtime/vgetrandom_linux.go | 8 + src/syscall/asm_linux_amd64.s | 4 + 42 files changed, 3170 insertions(+), 21 deletions(-) create mode 100644 doc/next/6-stdlib/1-secret.md create mode 100644 src/internal/goexperiment/exp_runtimesecret_off.go create mode 100644 src/internal/goexperiment/exp_runtimesecret_on.go create mode 100644 src/runtime/secret.go create mode 100644 src/runtime/secret/asm_amd64.s create mode 100644 src/runtime/secret/asm_arm64.s create mode 100644 src/runtime/secret/crash_test.go create mode 100644 src/runtime/secret/export.go create mode 100644 src/runtime/secret/secret.go create mode 100644 src/runtime/secret/secret_test.go create mode 100644 src/runtime/secret/stubs.go create mode 100644 src/runtime/secret/stubs_noasm.go create mode 100644 src/runtime/secret/testdata/crash.go create mode 100644 src/runtime/secret_amd64.s create mode 100644 src/runtime/secret_arm64.s create mode 100644 src/runtime/secret_asm.go create mode 100644 src/runtime/secret_noasm.go create mode 100644 src/runtime/secret_nosecret.go diff --git a/doc/next/6-stdlib/1-secret.md b/doc/next/6-stdlib/1-secret.md new file mode 100644 index 00000000000..738d02f54a2 --- /dev/null +++ b/doc/next/6-stdlib/1-secret.md @@ -0,0 +1,20 @@ +### New secret package + + + +The new [secret](/pkg/runtime/secret) package is available as an experiment. +It provides a facility for securely erasing temporaries used in +code that manipulates secret information, typically cryptographic in nature. +Users can access it by passing `GOEXPERIMENT=runtimesecret` at build time. + + + +The secret.Do function runs its function argument and then erases all +temporary storage (registers, stack, new heap allocations) used by +that function argument. Heap storage is not erased until that storage +is deemed unreachable by the garbage collector, which might take some +time after secret.Do completes. + +This package is intended to make it easier to ensure [forward +secrecy](https://en.wikipedia.org/wiki/Forward_secrecy). diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go index 73ea5c4015a..f8d19ac34c5 100644 --- a/src/cmd/dist/test.go +++ b/src/cmd/dist/test.go @@ -753,6 +753,15 @@ func (t *tester) registerTests() { }) } + // Test GOEXPERIMENT=runtimesecret. + if !strings.Contains(goexperiment, "runtimesecret") { + t.registerTest("GOEXPERIMENT=runtimesecret go test runtime/secret/...", &goTest{ + variant: "runtimesecret", + env: []string{"GOEXPERIMENT=runtimesecret"}, + pkg: "runtime/secret/...", + }) + } + // Test ios/amd64 for the iOS simulator. if goos == "darwin" && goarch == "amd64" && t.cgoEnabled { t.registerTest("GOOS=ios on darwin/amd64", diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 5466f025e1b..e329c8a1727 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -108,6 +108,7 @@ var depsRules = ` < internal/runtime/cgroup < internal/runtime/gc/scan < runtime + < runtime/secret < sync/atomic < internal/sync < weak diff --git a/src/internal/goexperiment/exp_runtimesecret_off.go b/src/internal/goexperiment/exp_runtimesecret_off.go new file mode 100644 index 00000000000..d203589249c --- /dev/null +++ b/src/internal/goexperiment/exp_runtimesecret_off.go @@ -0,0 +1,8 @@ +// Code generated by mkconsts.go. DO NOT EDIT. + +//go:build !goexperiment.runtimesecret + +package goexperiment + +const RuntimeSecret = false +const RuntimeSecretInt = 0 diff --git a/src/internal/goexperiment/exp_runtimesecret_on.go b/src/internal/goexperiment/exp_runtimesecret_on.go new file mode 100644 index 00000000000..3788953db8b --- /dev/null +++ b/src/internal/goexperiment/exp_runtimesecret_on.go @@ -0,0 +1,8 @@ +// Code generated by mkconsts.go. DO NOT EDIT. + +//go:build goexperiment.runtimesecret + +package goexperiment + +const RuntimeSecret = true +const RuntimeSecretInt = 1 diff --git a/src/internal/goexperiment/flags.go b/src/internal/goexperiment/flags.go index 2e14d4298a6..2cfb71578b4 100644 --- a/src/internal/goexperiment/flags.go +++ b/src/internal/goexperiment/flags.go @@ -125,4 +125,7 @@ type Flags struct { // SIMD enables the simd package and the compiler's handling // of SIMD intrinsics. SIMD bool + + // RuntimeSecret enables the runtime/secret package. + RuntimeSecret bool } diff --git a/src/runtime/_mkmalloc/mkmalloc.go b/src/runtime/_mkmalloc/mkmalloc.go index 1f040c88610..46c50d66611 100644 --- a/src/runtime/_mkmalloc/mkmalloc.go +++ b/src/runtime/_mkmalloc/mkmalloc.go @@ -171,6 +171,7 @@ func specializedMallocConfig(classes []class, sizeToSizeClass []uint8) generator {subBasicLit, "elemsize_", str(elemsize)}, {subBasicLit, "sizeclass_", str(sc)}, {subBasicLit, "noscanint_", str(noscan)}, + {subBasicLit, "isTiny_", str(0)}, }, }) } @@ -198,6 +199,7 @@ func specializedMallocConfig(classes []class, sizeToSizeClass []uint8) generator {subBasicLit, "sizeclass_", str(tinySizeClass)}, {subBasicLit, "size_", str(s)}, {subBasicLit, "noscanint_", str(noscan)}, + {subBasicLit, "isTiny_", str(1)}, }, }) } @@ -215,6 +217,7 @@ func specializedMallocConfig(classes []class, sizeToSizeClass []uint8) generator {subBasicLit, "elemsize_", str(elemsize)}, {subBasicLit, "sizeclass_", str(sc)}, {subBasicLit, "noscanint_", str(noscan)}, + {subBasicLit, "isTiny_", str(0)}, }, }) } diff --git a/src/runtime/asm_amd64.s b/src/runtime/asm_amd64.s index ed46ad4a284..bf208a4d291 100644 --- a/src/runtime/asm_amd64.s +++ b/src/runtime/asm_amd64.s @@ -456,6 +456,13 @@ TEXT gogo<>(SB), NOSPLIT, $0 // Fn must never return. It should gogo(&g->sched) // to keep running g. TEXT runtime·mcall(SB), NOSPLIT, $0-8 +#ifdef GOEXPERIMENT_runtimesecret + CMPL g_secret(R14), $0 + JEQ nosecret + CALL ·secretEraseRegistersMcall(SB) +nosecret: +#endif + MOVQ AX, DX // DX = fn // Save state in g->sched. The caller's SP and PC are restored by gogo to @@ -511,6 +518,17 @@ TEXT runtime·systemstack_switch(SB), NOSPLIT, $0-0 // func systemstack(fn func()) TEXT runtime·systemstack(SB), NOSPLIT, $0-8 +#ifdef GOEXPERIMENT_runtimesecret + // If in secret mode, erase registers on transition + // from G stack to M stack, + get_tls(CX) + MOVQ g(CX), AX + CMPL g_secret(AX), $0 + JEQ nosecret + CALL ·secretEraseRegisters(SB) +nosecret: +#endif + MOVQ fn+0(FP), DI // DI = fn get_tls(CX) MOVQ g(CX), AX // AX = g @@ -643,6 +661,18 @@ TEXT runtime·morestack(SB),NOSPLIT|NOFRAME,$0-0 MOVQ AX, (m_morebuf+gobuf_sp)(BX) MOVQ DI, (m_morebuf+gobuf_g)(BX) + // If in secret mode, erase registers on transition + // from G stack to M stack, +#ifdef GOEXPERIMENT_runtimesecret + CMPL g_secret(DI), $0 + JEQ nosecret + CALL ·secretEraseRegisters(SB) + get_tls(CX) + MOVQ g(CX), DI // DI = g + MOVQ g_m(DI), BX // BX = m +nosecret: +#endif + // Call newstack on m->g0's stack. MOVQ m_g0(BX), BX MOVQ BX, g(CX) @@ -917,11 +947,6 @@ TEXT ·asmcgocall_landingpad(SB),NOSPLIT,$0-0 // aligned appropriately for the gcc ABI. // See cgocall.go for more details. TEXT ·asmcgocall(SB),NOSPLIT,$0-20 - MOVQ fn+0(FP), AX - MOVQ arg+8(FP), BX - - MOVQ SP, DX - // Figure out if we need to switch to m->g0 stack. // We get called to create new OS threads too, and those // come in on the m->g0 stack already. Or we might already @@ -938,6 +963,21 @@ TEXT ·asmcgocall(SB),NOSPLIT,$0-20 CMPQ DI, SI JEQ nosave + // Running on a user G + // Figure out if we're running secret code and clear the registers + // so that the C code we're about to call doesn't spill confidential + // information into memory +#ifdef GOEXPERIMENT_runtimesecret + CMPL g_secret(DI), $0 + JEQ nosecret + CALL ·secretEraseRegisters(SB) + +nosecret: +#endif + MOVQ fn+0(FP), AX + MOVQ arg+8(FP), BX + MOVQ SP, DX + // Switch to system stack. // The original frame pointer is stored in BP, // which is useful for stack unwinding. @@ -976,6 +1016,10 @@ nosave: // but then the only path through this code would be a rare case on Solaris. // Using this code for all "already on system stack" calls exercises it more, // which should help keep it correct. + MOVQ fn+0(FP), AX + MOVQ arg+8(FP), BX + MOVQ SP, DX + SUBQ $16, SP ANDQ $~15, SP MOVQ $0, 8(SP) // where above code stores g, in case someone looks during debugging diff --git a/src/runtime/asm_arm64.s b/src/runtime/asm_arm64.s index 01f2690f4e2..9916378a93a 100644 --- a/src/runtime/asm_arm64.s +++ b/src/runtime/asm_arm64.s @@ -300,6 +300,17 @@ TEXT gogo<>(SB), NOSPLIT|NOFRAME, $0 // Fn must never return. It should gogo(&g->sched) // to keep running g. TEXT runtime·mcall(SB), NOSPLIT|NOFRAME, $0-8 +#ifdef GOEXPERIMENT_runtimesecret + MOVW g_secret(g), R26 + CBZ R26, nosecret + // Use R26 as a secondary link register + // We purposefully don't erase it in secretEraseRegistersMcall + MOVD LR, R26 + BL runtime·secretEraseRegistersMcall(SB) + MOVD R26, LR + +nosecret: +#endif MOVD R0, R26 // context // Save caller state in g->sched @@ -340,6 +351,13 @@ TEXT runtime·systemstack_switch(SB), NOSPLIT, $0-0 // func systemstack(fn func()) TEXT runtime·systemstack(SB), NOSPLIT, $0-8 +#ifdef GOEXPERIMENT_runtimesecret + MOVW g_secret(g), R3 + CBZ R3, nosecret + BL ·secretEraseRegisters(SB) + +nosecret: +#endif MOVD fn+0(FP), R3 // R3 = fn MOVD R3, R26 // context MOVD g_m(g), R4 // R4 = m @@ -469,6 +487,16 @@ TEXT runtime·morestack(SB),NOSPLIT|NOFRAME,$0-0 MOVD R0, (m_morebuf+gobuf_sp)(R8) // f's caller's RSP MOVD g, (m_morebuf+gobuf_g)(R8) + // If in secret mode, erase registers on transition + // from G stack to M stack, +#ifdef GOEXPERIMENT_runtimesecret + MOVW g_secret(g), R4 + CBZ R4, nosecret + BL ·secretEraseRegisters(SB) + MOVD g_m(g), R8 +nosecret: +#endif + // Call newstack on m->g0's stack. MOVD m_g0(R8), g BL runtime·save_g(SB) @@ -1143,12 +1171,7 @@ TEXT ·asmcgocall_no_g(SB),NOSPLIT,$0-16 // aligned appropriately for the gcc ABI. // See cgocall.go for more details. TEXT ·asmcgocall(SB),NOSPLIT,$0-20 - MOVD fn+0(FP), R1 - MOVD arg+8(FP), R0 - - MOVD RSP, R2 // save original stack pointer CBZ g, nosave - MOVD g, R4 // Figure out if we need to switch to m->g0 stack. // We get called to create new OS threads too, and those @@ -1162,6 +1185,23 @@ TEXT ·asmcgocall(SB),NOSPLIT,$0-20 CMP R3, g BEQ nosave + // running on a user stack. Figure out if we're running + // secret code and clear our registers if so. +#ifdef GOEXPERIMENT_runtimesecret + MOVW g_secret(g), R5 + CBZ R5, nosecret + BL ·secretEraseRegisters(SB) + // restore g0 back into R3 + MOVD g_m(g), R3 + MOVD m_g0(R3), R3 + +nosecret: +#endif + MOVD fn+0(FP), R1 + MOVD arg+8(FP), R0 + MOVD RSP, R2 + MOVD g, R4 + // Switch to system stack. MOVD R0, R9 // gosave_systemstack_switch<> and save_g might clobber R0 BL gosave_systemstack_switch<>(SB) @@ -1208,7 +1248,10 @@ nosave: // but then the only path through this code would be a rare case on Solaris. // Using this code for all "already on system stack" calls exercises it more, // which should help keep it correct. - MOVD RSP, R13 + MOVD fn+0(FP), R1 + MOVD arg+8(FP), R0 + MOVD RSP, R2 + MOVD R2, R13 SUB $16, R13 MOVD R13, RSP MOVD $0, R4 diff --git a/src/runtime/malloc.go b/src/runtime/malloc.go index 4971e16c6aa..fd79356abab 100644 --- a/src/runtime/malloc.go +++ b/src/runtime/malloc.go @@ -1185,7 +1185,11 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { } else { if size <= maxSmallSize-gc.MallocHeaderSize { if typ == nil || !typ.Pointers() { - if size < maxTinySize { + // tiny allocations might be kept alive by other co-located values. + // Make sure secret allocations get zeroed by avoiding the tiny allocator + // See go.dev/issue/76356 + gp := getg() + if size < maxTinySize && gp.secret == 0 { x, elemsize = mallocgcTiny(size, typ) } else { x, elemsize = mallocgcSmallNoscan(size, typ, needzero) @@ -1205,6 +1209,13 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { } } + gp := getg() + if goexperiment.RuntimeSecret && gp.secret > 0 { + // Mark any object allocated while in secret mode as secret. + // This ensures we zero it immediately when freeing it. + addSecret(x) + } + // Notify sanitizers, if enabled. if raceenabled { racemalloc(x, size-asanRZ) diff --git a/src/runtime/malloc_generated.go b/src/runtime/malloc_generated.go index 5abb61257a4..6864ca05d31 100644 --- a/src/runtime/malloc_generated.go +++ b/src/runtime/malloc_generated.go @@ -5,11 +5,19 @@ package runtime import ( "internal/goarch" + "internal/goexperiment" "internal/runtime/sys" "unsafe" ) func mallocgcSmallScanNoHeaderSC1(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -151,6 +159,11 @@ func mallocgcSmallScanNoHeaderSC1(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -168,6 +181,13 @@ func mallocgcSmallScanNoHeaderSC1(size uintptr, typ *_type, needzero bool) unsaf } func mallocgcSmallScanNoHeaderSC2(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -309,6 +329,11 @@ func mallocgcSmallScanNoHeaderSC2(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -326,6 +351,13 @@ func mallocgcSmallScanNoHeaderSC2(size uintptr, typ *_type, needzero bool) unsaf } func mallocgcSmallScanNoHeaderSC3(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -467,6 +499,11 @@ func mallocgcSmallScanNoHeaderSC3(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -484,6 +521,13 @@ func mallocgcSmallScanNoHeaderSC3(size uintptr, typ *_type, needzero bool) unsaf } func mallocgcSmallScanNoHeaderSC4(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -625,6 +669,11 @@ func mallocgcSmallScanNoHeaderSC4(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -642,6 +691,13 @@ func mallocgcSmallScanNoHeaderSC4(size uintptr, typ *_type, needzero bool) unsaf } func mallocgcSmallScanNoHeaderSC5(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -783,6 +839,11 @@ func mallocgcSmallScanNoHeaderSC5(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -800,6 +861,13 @@ func mallocgcSmallScanNoHeaderSC5(size uintptr, typ *_type, needzero bool) unsaf } func mallocgcSmallScanNoHeaderSC6(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -941,6 +1009,11 @@ func mallocgcSmallScanNoHeaderSC6(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -958,6 +1031,13 @@ func mallocgcSmallScanNoHeaderSC6(size uintptr, typ *_type, needzero bool) unsaf } func mallocgcSmallScanNoHeaderSC7(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -1099,6 +1179,11 @@ func mallocgcSmallScanNoHeaderSC7(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -1116,6 +1201,13 @@ func mallocgcSmallScanNoHeaderSC7(size uintptr, typ *_type, needzero bool) unsaf } func mallocgcSmallScanNoHeaderSC8(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -1257,6 +1349,11 @@ func mallocgcSmallScanNoHeaderSC8(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -1274,6 +1371,13 @@ func mallocgcSmallScanNoHeaderSC8(size uintptr, typ *_type, needzero bool) unsaf } func mallocgcSmallScanNoHeaderSC9(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -1415,6 +1519,11 @@ func mallocgcSmallScanNoHeaderSC9(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -1432,6 +1541,13 @@ func mallocgcSmallScanNoHeaderSC9(size uintptr, typ *_type, needzero bool) unsaf } func mallocgcSmallScanNoHeaderSC10(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -1573,6 +1689,11 @@ func mallocgcSmallScanNoHeaderSC10(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -1590,6 +1711,13 @@ func mallocgcSmallScanNoHeaderSC10(size uintptr, typ *_type, needzero bool) unsa } func mallocgcSmallScanNoHeaderSC11(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -1731,6 +1859,11 @@ func mallocgcSmallScanNoHeaderSC11(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -1748,6 +1881,13 @@ func mallocgcSmallScanNoHeaderSC11(size uintptr, typ *_type, needzero bool) unsa } func mallocgcSmallScanNoHeaderSC12(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -1889,6 +2029,11 @@ func mallocgcSmallScanNoHeaderSC12(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -1906,6 +2051,13 @@ func mallocgcSmallScanNoHeaderSC12(size uintptr, typ *_type, needzero bool) unsa } func mallocgcSmallScanNoHeaderSC13(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -2047,6 +2199,11 @@ func mallocgcSmallScanNoHeaderSC13(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -2064,6 +2221,13 @@ func mallocgcSmallScanNoHeaderSC13(size uintptr, typ *_type, needzero bool) unsa } func mallocgcSmallScanNoHeaderSC14(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -2205,6 +2369,11 @@ func mallocgcSmallScanNoHeaderSC14(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -2222,6 +2391,13 @@ func mallocgcSmallScanNoHeaderSC14(size uintptr, typ *_type, needzero bool) unsa } func mallocgcSmallScanNoHeaderSC15(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -2363,6 +2539,11 @@ func mallocgcSmallScanNoHeaderSC15(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -2380,6 +2561,13 @@ func mallocgcSmallScanNoHeaderSC15(size uintptr, typ *_type, needzero bool) unsa } func mallocgcSmallScanNoHeaderSC16(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -2521,6 +2709,11 @@ func mallocgcSmallScanNoHeaderSC16(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -2538,6 +2731,13 @@ func mallocgcSmallScanNoHeaderSC16(size uintptr, typ *_type, needzero bool) unsa } func mallocgcSmallScanNoHeaderSC17(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -2679,6 +2879,11 @@ func mallocgcSmallScanNoHeaderSC17(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -2696,6 +2901,13 @@ func mallocgcSmallScanNoHeaderSC17(size uintptr, typ *_type, needzero bool) unsa } func mallocgcSmallScanNoHeaderSC18(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -2837,6 +3049,11 @@ func mallocgcSmallScanNoHeaderSC18(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -2854,6 +3071,13 @@ func mallocgcSmallScanNoHeaderSC18(size uintptr, typ *_type, needzero bool) unsa } func mallocgcSmallScanNoHeaderSC19(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -2995,6 +3219,11 @@ func mallocgcSmallScanNoHeaderSC19(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -3012,6 +3241,13 @@ func mallocgcSmallScanNoHeaderSC19(size uintptr, typ *_type, needzero bool) unsa } func mallocgcSmallScanNoHeaderSC20(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -3153,6 +3389,11 @@ func mallocgcSmallScanNoHeaderSC20(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -3170,6 +3411,13 @@ func mallocgcSmallScanNoHeaderSC20(size uintptr, typ *_type, needzero bool) unsa } func mallocgcSmallScanNoHeaderSC21(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -3311,6 +3559,11 @@ func mallocgcSmallScanNoHeaderSC21(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -3328,6 +3581,13 @@ func mallocgcSmallScanNoHeaderSC21(size uintptr, typ *_type, needzero bool) unsa } func mallocgcSmallScanNoHeaderSC22(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -3469,6 +3729,11 @@ func mallocgcSmallScanNoHeaderSC22(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -3486,6 +3751,13 @@ func mallocgcSmallScanNoHeaderSC22(size uintptr, typ *_type, needzero bool) unsa } func mallocgcSmallScanNoHeaderSC23(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -3627,6 +3899,11 @@ func mallocgcSmallScanNoHeaderSC23(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -3644,6 +3921,13 @@ func mallocgcSmallScanNoHeaderSC23(size uintptr, typ *_type, needzero bool) unsa } func mallocgcSmallScanNoHeaderSC24(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -3785,6 +4069,11 @@ func mallocgcSmallScanNoHeaderSC24(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -3802,6 +4091,13 @@ func mallocgcSmallScanNoHeaderSC24(size uintptr, typ *_type, needzero bool) unsa } func mallocgcSmallScanNoHeaderSC25(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -3943,6 +4239,11 @@ func mallocgcSmallScanNoHeaderSC25(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -3960,6 +4261,13 @@ func mallocgcSmallScanNoHeaderSC25(size uintptr, typ *_type, needzero bool) unsa } func mallocgcSmallScanNoHeaderSC26(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -4101,6 +4409,11 @@ func mallocgcSmallScanNoHeaderSC26(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -4118,6 +4431,13 @@ func mallocgcSmallScanNoHeaderSC26(size uintptr, typ *_type, needzero bool) unsa } func mallocTiny1(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 1 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -4169,6 +4489,11 @@ func mallocTiny1(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -4251,6 +4576,11 @@ func mallocTiny1(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -4268,6 +4598,13 @@ func mallocTiny1(size uintptr, typ *_type, needzero bool) unsafe.Pointer { } func mallocTiny2(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 1 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -4319,6 +4656,11 @@ func mallocTiny2(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -4401,6 +4743,11 @@ func mallocTiny2(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -4418,6 +4765,13 @@ func mallocTiny2(size uintptr, typ *_type, needzero bool) unsafe.Pointer { } func mallocTiny3(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 1 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -4469,6 +4823,11 @@ func mallocTiny3(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -4551,6 +4910,11 @@ func mallocTiny3(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -4568,6 +4932,13 @@ func mallocTiny3(size uintptr, typ *_type, needzero bool) unsafe.Pointer { } func mallocTiny4(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 1 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -4619,6 +4990,11 @@ func mallocTiny4(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -4701,6 +5077,11 @@ func mallocTiny4(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -4718,6 +5099,13 @@ func mallocTiny4(size uintptr, typ *_type, needzero bool) unsafe.Pointer { } func mallocTiny5(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 1 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -4769,6 +5157,11 @@ func mallocTiny5(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -4851,6 +5244,11 @@ func mallocTiny5(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -4868,6 +5266,13 @@ func mallocTiny5(size uintptr, typ *_type, needzero bool) unsafe.Pointer { } func mallocTiny6(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 1 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -4919,6 +5324,11 @@ func mallocTiny6(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -5001,6 +5411,11 @@ func mallocTiny6(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -5018,6 +5433,13 @@ func mallocTiny6(size uintptr, typ *_type, needzero bool) unsafe.Pointer { } func mallocTiny7(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 1 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -5069,6 +5491,11 @@ func mallocTiny7(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -5151,6 +5578,11 @@ func mallocTiny7(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -5168,6 +5600,13 @@ func mallocTiny7(size uintptr, typ *_type, needzero bool) unsafe.Pointer { } func mallocTiny8(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 1 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -5219,6 +5658,11 @@ func mallocTiny8(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -5301,6 +5745,11 @@ func mallocTiny8(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -5318,6 +5767,13 @@ func mallocTiny8(size uintptr, typ *_type, needzero bool) unsafe.Pointer { } func mallocTiny9(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 1 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -5369,6 +5825,11 @@ func mallocTiny9(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -5451,6 +5912,11 @@ func mallocTiny9(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -5468,6 +5934,13 @@ func mallocTiny9(size uintptr, typ *_type, needzero bool) unsafe.Pointer { } func mallocTiny10(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 1 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -5519,6 +5992,11 @@ func mallocTiny10(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -5601,6 +6079,11 @@ func mallocTiny10(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -5618,6 +6101,13 @@ func mallocTiny10(size uintptr, typ *_type, needzero bool) unsafe.Pointer { } func mallocTiny11(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 1 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -5669,6 +6159,11 @@ func mallocTiny11(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -5751,6 +6246,11 @@ func mallocTiny11(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -5768,6 +6268,13 @@ func mallocTiny11(size uintptr, typ *_type, needzero bool) unsafe.Pointer { } func mallocTiny12(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 1 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -5819,6 +6326,11 @@ func mallocTiny12(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -5901,6 +6413,11 @@ func mallocTiny12(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -5918,6 +6435,13 @@ func mallocTiny12(size uintptr, typ *_type, needzero bool) unsafe.Pointer { } func mallocTiny13(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 1 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -5969,6 +6493,11 @@ func mallocTiny13(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -6051,6 +6580,11 @@ func mallocTiny13(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -6068,6 +6602,13 @@ func mallocTiny13(size uintptr, typ *_type, needzero bool) unsafe.Pointer { } func mallocTiny14(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 1 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -6119,6 +6660,11 @@ func mallocTiny14(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -6201,6 +6747,11 @@ func mallocTiny14(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -6218,6 +6769,13 @@ func mallocTiny14(size uintptr, typ *_type, needzero bool) unsafe.Pointer { } func mallocTiny15(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 1 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -6269,6 +6827,11 @@ func mallocTiny15(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -6351,6 +6914,11 @@ func mallocTiny15(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -6368,6 +6936,13 @@ func mallocTiny15(size uintptr, typ *_type, needzero bool) unsafe.Pointer { } func mallocgcSmallNoScanSC2(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -6409,6 +6984,11 @@ func mallocgcSmallNoScanSC2(size uintptr, typ *_type, needzero bool) unsafe.Poin x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -6474,6 +7054,11 @@ func mallocgcSmallNoScanSC2(size uintptr, typ *_type, needzero bool) unsafe.Poin gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -6491,6 +7076,13 @@ func mallocgcSmallNoScanSC2(size uintptr, typ *_type, needzero bool) unsafe.Poin } func mallocgcSmallNoScanSC3(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -6532,6 +7124,11 @@ func mallocgcSmallNoScanSC3(size uintptr, typ *_type, needzero bool) unsafe.Poin x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -6597,6 +7194,11 @@ func mallocgcSmallNoScanSC3(size uintptr, typ *_type, needzero bool) unsafe.Poin gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -6614,6 +7216,13 @@ func mallocgcSmallNoScanSC3(size uintptr, typ *_type, needzero bool) unsafe.Poin } func mallocgcSmallNoScanSC4(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -6655,6 +7264,11 @@ func mallocgcSmallNoScanSC4(size uintptr, typ *_type, needzero bool) unsafe.Poin x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -6720,6 +7334,11 @@ func mallocgcSmallNoScanSC4(size uintptr, typ *_type, needzero bool) unsafe.Poin gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -6737,6 +7356,13 @@ func mallocgcSmallNoScanSC4(size uintptr, typ *_type, needzero bool) unsafe.Poin } func mallocgcSmallNoScanSC5(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -6778,6 +7404,11 @@ func mallocgcSmallNoScanSC5(size uintptr, typ *_type, needzero bool) unsafe.Poin x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -6843,6 +7474,11 @@ func mallocgcSmallNoScanSC5(size uintptr, typ *_type, needzero bool) unsafe.Poin gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -6860,6 +7496,13 @@ func mallocgcSmallNoScanSC5(size uintptr, typ *_type, needzero bool) unsafe.Poin } func mallocgcSmallNoScanSC6(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -6901,6 +7544,11 @@ func mallocgcSmallNoScanSC6(size uintptr, typ *_type, needzero bool) unsafe.Poin x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -6966,6 +7614,11 @@ func mallocgcSmallNoScanSC6(size uintptr, typ *_type, needzero bool) unsafe.Poin gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -6983,6 +7636,13 @@ func mallocgcSmallNoScanSC6(size uintptr, typ *_type, needzero bool) unsafe.Poin } func mallocgcSmallNoScanSC7(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -7024,6 +7684,11 @@ func mallocgcSmallNoScanSC7(size uintptr, typ *_type, needzero bool) unsafe.Poin x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -7089,6 +7754,11 @@ func mallocgcSmallNoScanSC7(size uintptr, typ *_type, needzero bool) unsafe.Poin gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -7106,6 +7776,13 @@ func mallocgcSmallNoScanSC7(size uintptr, typ *_type, needzero bool) unsafe.Poin } func mallocgcSmallNoScanSC8(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -7147,6 +7824,11 @@ func mallocgcSmallNoScanSC8(size uintptr, typ *_type, needzero bool) unsafe.Poin x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -7212,6 +7894,11 @@ func mallocgcSmallNoScanSC8(size uintptr, typ *_type, needzero bool) unsafe.Poin gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -7229,6 +7916,13 @@ func mallocgcSmallNoScanSC8(size uintptr, typ *_type, needzero bool) unsafe.Poin } func mallocgcSmallNoScanSC9(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -7270,6 +7964,11 @@ func mallocgcSmallNoScanSC9(size uintptr, typ *_type, needzero bool) unsafe.Poin x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -7335,6 +8034,11 @@ func mallocgcSmallNoScanSC9(size uintptr, typ *_type, needzero bool) unsafe.Poin gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -7352,6 +8056,13 @@ func mallocgcSmallNoScanSC9(size uintptr, typ *_type, needzero bool) unsafe.Poin } func mallocgcSmallNoScanSC10(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -7393,6 +8104,11 @@ func mallocgcSmallNoScanSC10(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -7458,6 +8174,11 @@ func mallocgcSmallNoScanSC10(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -7475,6 +8196,13 @@ func mallocgcSmallNoScanSC10(size uintptr, typ *_type, needzero bool) unsafe.Poi } func mallocgcSmallNoScanSC11(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -7516,6 +8244,11 @@ func mallocgcSmallNoScanSC11(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -7581,6 +8314,11 @@ func mallocgcSmallNoScanSC11(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -7598,6 +8336,13 @@ func mallocgcSmallNoScanSC11(size uintptr, typ *_type, needzero bool) unsafe.Poi } func mallocgcSmallNoScanSC12(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -7639,6 +8384,11 @@ func mallocgcSmallNoScanSC12(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -7704,6 +8454,11 @@ func mallocgcSmallNoScanSC12(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -7721,6 +8476,13 @@ func mallocgcSmallNoScanSC12(size uintptr, typ *_type, needzero bool) unsafe.Poi } func mallocgcSmallNoScanSC13(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -7762,6 +8524,11 @@ func mallocgcSmallNoScanSC13(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -7827,6 +8594,11 @@ func mallocgcSmallNoScanSC13(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -7844,6 +8616,13 @@ func mallocgcSmallNoScanSC13(size uintptr, typ *_type, needzero bool) unsafe.Poi } func mallocgcSmallNoScanSC14(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -7885,6 +8664,11 @@ func mallocgcSmallNoScanSC14(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -7950,6 +8734,11 @@ func mallocgcSmallNoScanSC14(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -7967,6 +8756,13 @@ func mallocgcSmallNoScanSC14(size uintptr, typ *_type, needzero bool) unsafe.Poi } func mallocgcSmallNoScanSC15(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -8008,6 +8804,11 @@ func mallocgcSmallNoScanSC15(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -8073,6 +8874,11 @@ func mallocgcSmallNoScanSC15(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -8090,6 +8896,13 @@ func mallocgcSmallNoScanSC15(size uintptr, typ *_type, needzero bool) unsafe.Poi } func mallocgcSmallNoScanSC16(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -8131,6 +8944,11 @@ func mallocgcSmallNoScanSC16(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -8196,6 +9014,11 @@ func mallocgcSmallNoScanSC16(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -8213,6 +9036,13 @@ func mallocgcSmallNoScanSC16(size uintptr, typ *_type, needzero bool) unsafe.Poi } func mallocgcSmallNoScanSC17(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -8254,6 +9084,11 @@ func mallocgcSmallNoScanSC17(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -8319,6 +9154,11 @@ func mallocgcSmallNoScanSC17(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -8336,6 +9176,13 @@ func mallocgcSmallNoScanSC17(size uintptr, typ *_type, needzero bool) unsafe.Poi } func mallocgcSmallNoScanSC18(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -8377,6 +9224,11 @@ func mallocgcSmallNoScanSC18(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -8442,6 +9294,11 @@ func mallocgcSmallNoScanSC18(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -8459,6 +9316,13 @@ func mallocgcSmallNoScanSC18(size uintptr, typ *_type, needzero bool) unsafe.Poi } func mallocgcSmallNoScanSC19(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -8500,6 +9364,11 @@ func mallocgcSmallNoScanSC19(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -8565,6 +9434,11 @@ func mallocgcSmallNoScanSC19(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -8582,6 +9456,13 @@ func mallocgcSmallNoScanSC19(size uintptr, typ *_type, needzero bool) unsafe.Poi } func mallocgcSmallNoScanSC20(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -8623,6 +9504,11 @@ func mallocgcSmallNoScanSC20(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -8688,6 +9574,11 @@ func mallocgcSmallNoScanSC20(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -8705,6 +9596,13 @@ func mallocgcSmallNoScanSC20(size uintptr, typ *_type, needzero bool) unsafe.Poi } func mallocgcSmallNoScanSC21(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -8746,6 +9644,11 @@ func mallocgcSmallNoScanSC21(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -8811,6 +9714,11 @@ func mallocgcSmallNoScanSC21(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -8828,6 +9736,13 @@ func mallocgcSmallNoScanSC21(size uintptr, typ *_type, needzero bool) unsafe.Poi } func mallocgcSmallNoScanSC22(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -8869,6 +9784,11 @@ func mallocgcSmallNoScanSC22(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -8934,6 +9854,11 @@ func mallocgcSmallNoScanSC22(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -8951,6 +9876,13 @@ func mallocgcSmallNoScanSC22(size uintptr, typ *_type, needzero bool) unsafe.Poi } func mallocgcSmallNoScanSC23(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -8992,6 +9924,11 @@ func mallocgcSmallNoScanSC23(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -9057,6 +9994,11 @@ func mallocgcSmallNoScanSC23(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -9074,6 +10016,13 @@ func mallocgcSmallNoScanSC23(size uintptr, typ *_type, needzero bool) unsafe.Poi } func mallocgcSmallNoScanSC24(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -9115,6 +10064,11 @@ func mallocgcSmallNoScanSC24(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -9180,6 +10134,11 @@ func mallocgcSmallNoScanSC24(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -9197,6 +10156,13 @@ func mallocgcSmallNoScanSC24(size uintptr, typ *_type, needzero bool) unsafe.Poi } func mallocgcSmallNoScanSC25(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -9238,6 +10204,11 @@ func mallocgcSmallNoScanSC25(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -9303,6 +10274,11 @@ func mallocgcSmallNoScanSC25(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -9320,6 +10296,13 @@ func mallocgcSmallNoScanSC25(size uintptr, typ *_type, needzero bool) unsafe.Poi } func mallocgcSmallNoScanSC26(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + const isTiny = 0 == + 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -9361,6 +10344,11 @@ func mallocgcSmallNoScanSC26(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } @@ -9426,6 +10414,11 @@ func mallocgcSmallNoScanSC26(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + if goexperiment.RuntimeSecret && gp.secret > 0 { + + addSecret(x) + } + if valgrindenabled { valgrindMalloc(x, size) } diff --git a/src/runtime/malloc_stubs.go b/src/runtime/malloc_stubs.go index e9752956b82..58ca1d5f79f 100644 --- a/src/runtime/malloc_stubs.go +++ b/src/runtime/malloc_stubs.go @@ -22,6 +22,7 @@ package runtime import ( "internal/goarch" + "internal/goexperiment" "internal/runtime/sys" "unsafe" ) @@ -36,6 +37,7 @@ const elemsize_ = 8 const sizeclass_ = 0 const noscanint_ = 0 const size_ = 0 +const isTiny_ = 0 func malloc0(size uintptr, typ *_type, needzero bool) unsafe.Pointer { if doubleCheckMalloc { @@ -55,6 +57,17 @@ func mallocPanic(size uintptr, typ *_type, needzero bool) unsafe.Pointer { // WARNING: mallocStub does not do any work for sanitizers so callers need // to steer out of this codepath early if sanitizers are enabled. func mallocStub(size uintptr, typ *_type, needzero bool) unsafe.Pointer { + + // secret code, need to avoid the tiny allocator since it might keep + // co-located values alive longer and prevent timely zero-ing + // + // Call directly into the NoScan allocator. + // See go.dev/issue/76356 + const isTiny = isTiny_ == 1 + gp := getg() + if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -82,6 +95,12 @@ func mallocStub(size uintptr, typ *_type, needzero bool) unsafe.Pointer { // Actually do the allocation. x, elemsize := inlinedMalloc(size, typ, needzero) + if goexperiment.RuntimeSecret && gp.secret > 0 { + // Mark any object allocated while in secret mode as secret. + // This ensures we zero it immediately when freeing it. + addSecret(x) + } + // Notify valgrind, if enabled. // To allow the compiler to not know about valgrind, we do valgrind instrumentation // unlike the other sanitizers. diff --git a/src/runtime/mgc.go b/src/runtime/mgc.go index febcd9558c0..32cd8cb0e89 100644 --- a/src/runtime/mgc.go +++ b/src/runtime/mgc.go @@ -838,6 +838,33 @@ func gcStart(trigger gcTrigger) { // Accumulate fine-grained stopping time. work.cpuStats.accumulateGCPauseTime(stw.stoppingCPUTime, 1) + if goexperiment.RuntimeSecret { + // The world is stopped. Every M is either parked + // or in a syscall, or running some non-go code which can't run in secret mode. + // To get to a parked or a syscall state + // they have to transition through a point where we erase any + // confidential information in the registers. Making them + // handle a signal now would clobber the signal stack + // with non-confidential information. + // + // TODO(dmo): this is linear with respect to the number of Ms. + // Investigate just how long this takes and whether we can somehow + // loop over just the Ms that have secret info on their signal stack, + // or cooperatively have the Ms send signals to themselves just + // after they erase their registers, but before they enter a syscall + for mp := allm; mp != nil; mp = mp.alllink { + // even through the world is stopped, the kernel can still + // invoke our signal handlers. No confidential information can be spilled + // (because it's been erased by this time), but we can avoid + // sending additional signals by atomically inspecting this variable + if atomic.Xchg(&mp.signalSecret, 0) != 0 { + noopSignal(mp) + } + // TODO: syncronize with the signal handler to ensure that the signal + // was actually delivered. + } + } + // Finish sweep before we start concurrent scan. systemstack(func() { finishsweep_m() diff --git a/src/runtime/mheap.go b/src/runtime/mheap.go index 0ccaadc891b..61dc5457fc1 100644 --- a/src/runtime/mheap.go +++ b/src/runtime/mheap.go @@ -225,6 +225,7 @@ type mheap struct { specialPinCounterAlloc fixalloc // allocator for specialPinCounter specialWeakHandleAlloc fixalloc // allocator for specialWeakHandle specialBubbleAlloc fixalloc // allocator for specialBubble + specialSecretAlloc fixalloc // allocator for specialSecret speciallock mutex // lock for special record allocators. arenaHintAlloc fixalloc // allocator for arenaHints @@ -803,6 +804,7 @@ func (h *mheap) init() { h.specialprofilealloc.init(unsafe.Sizeof(specialprofile{}), nil, nil, &memstats.other_sys) h.specialReachableAlloc.init(unsafe.Sizeof(specialReachable{}), nil, nil, &memstats.other_sys) h.specialPinCounterAlloc.init(unsafe.Sizeof(specialPinCounter{}), nil, nil, &memstats.other_sys) + h.specialSecretAlloc.init(unsafe.Sizeof(specialSecret{}), nil, nil, &memstats.other_sys) h.specialWeakHandleAlloc.init(unsafe.Sizeof(specialWeakHandle{}), nil, nil, &memstats.gcMiscSys) h.specialBubbleAlloc.init(unsafe.Sizeof(specialBubble{}), nil, nil, &memstats.other_sys) h.arenaHintAlloc.init(unsafe.Sizeof(arenaHint{}), nil, nil, &memstats.other_sys) @@ -1970,6 +1972,9 @@ const ( _KindSpecialCheckFinalizer = 8 // _KindSpecialBubble is used to associate objects with synctest bubbles. _KindSpecialBubble = 9 + // _KindSpecialSecret is a special used to mark an object + // as needing zeroing immediately upon freeing. + _KindSpecialSecret = 10 ) type special struct { @@ -2822,6 +2827,11 @@ func freeSpecial(s *special, p unsafe.Pointer, size uintptr) { lock(&mheap_.speciallock) mheap_.specialBubbleAlloc.free(unsafe.Pointer(st)) unlock(&mheap_.speciallock) + case _KindSpecialSecret: + memclrNoHeapPointers(p, size) + lock(&mheap_.speciallock) + mheap_.specialSecretAlloc.free(unsafe.Pointer(s)) + unlock(&mheap_.speciallock) default: throw("bad special kind") panic("not reached") diff --git a/src/runtime/preempt.go b/src/runtime/preempt.go index 447c7399fcb..892f9000731 100644 --- a/src/runtime/preempt.go +++ b/src/runtime/preempt.go @@ -55,6 +55,7 @@ package runtime import ( "internal/abi" "internal/goarch" + "internal/goexperiment" "internal/stringslite" ) @@ -406,6 +407,22 @@ func isAsyncSafePoint(gp *g, pc, sp, lr uintptr) (bool, uintptr) { return false, 0 } + // If we're in the middle of a secret computation, we can't + // allow any conservative scanning of stacks, as that may lead + // to secrets leaking out from the stack into work buffers. + // Additionally, the preemption code will store the + // machine state (including registers which may contain confidential + // information) into the preemption buffers. + // + // TODO(dmo): there's technically nothing stopping us from doing the + // preemption, granted that don't conservatively scan and we clean up after + // ourselves. This is made slightly harder by the xRegs cached allocations + // that can move between Gs and Ps. In any case, for the intended users (cryptography code) + // they are unlikely get stuck in unterminating loops. + if goexperiment.RuntimeSecret && gp.secret > 0 { + return false, 0 + } + // Check if PC is an unsafe-point. f := findfunc(pc) if !f.valid() { diff --git a/src/runtime/proc.go b/src/runtime/proc.go index 3b98be10748..16538098cf6 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -8,6 +8,7 @@ import ( "internal/abi" "internal/cpu" "internal/goarch" + "internal/goexperiment" "internal/goos" "internal/runtime/atomic" "internal/runtime/exithook" @@ -4454,6 +4455,13 @@ func goexit1() { // goexit continuation on g0. func goexit0(gp *g) { + if goexperiment.RuntimeSecret && gp.secret > 0 { + // Erase the whole stack. This path only occurs when + // runtime.Goexit is called from within a runtime/secret.Do call. + memclrNoHeapPointers(unsafe.Pointer(gp.stack.lo), gp.stack.hi-gp.stack.lo) + // Since this is running on g0, our registers are already zeroed from going through + // mcall in secret mode. + } gdestroy(gp) schedule() } @@ -4482,6 +4490,7 @@ func gdestroy(gp *g) { gp.timer = nil gp.bubble = nil gp.fipsOnlyBypass = false + gp.secret = 0 if gcBlackenEnabled != 0 && gp.gcAssistBytes > 0 { // Flush assist credit to the global pool. This gives @@ -5216,6 +5225,10 @@ func malg(stacksize int32) *g { // The compiler turns a go statement into a call to this. func newproc(fn *funcval) { gp := getg() + if goexperiment.RuntimeSecret && gp.secret > 0 { + panic("goroutine spawned while running in secret mode") + } + pc := sys.GetCallerPC() systemstack(func() { newg := newproc1(fn, gp, pc, false, waitReasonZero) diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go index 58eaf802372..cd75e2dd7c5 100644 --- a/src/runtime/runtime2.go +++ b/src/runtime/runtime2.go @@ -549,6 +549,7 @@ type g struct { syncSafePoint bool // set if g is stopped at a synchronous safe point. runningCleanups atomic.Bool sig uint32 + secret int32 // current nesting of runtime/secret.Do calls. writebuf []byte sigcode0 uintptr sigcode1 uintptr @@ -620,14 +621,15 @@ type m struct { // Fields whose offsets are not known to debuggers. - procid uint64 // for debuggers, but offset not hard-coded - gsignal *g // signal-handling g - goSigStack gsignalStack // Go-allocated signal handling stack - sigmask sigset // storage for saved signal mask - tls [tlsSlots]uintptr // thread-local storage (for x86 extern register) - mstartfn func() - curg *g // current running goroutine - caughtsig guintptr // goroutine running during fatal signal + procid uint64 // for debuggers, but offset not hard-coded + gsignal *g // signal-handling g + goSigStack gsignalStack // Go-allocated signal handling stack + sigmask sigset // storage for saved signal mask + tls [tlsSlots]uintptr // thread-local storage (for x86 extern register) + mstartfn func() + curg *g // current running goroutine + caughtsig guintptr // goroutine running during fatal signal + signalSecret uint32 // whether we have secret information in our signal stack // p is the currently attached P for executing Go code, nil if not executing user Go code. // diff --git a/src/runtime/secret.go b/src/runtime/secret.go new file mode 100644 index 00000000000..4c199d31d03 --- /dev/null +++ b/src/runtime/secret.go @@ -0,0 +1,118 @@ +// Copyright 2024 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 (amd64 || arm64) && linux + +package runtime + +import ( + "internal/goarch" + "unsafe" +) + +//go:linkname secret_count runtime/secret.count +func secret_count() int32 { + return getg().secret +} + +//go:linkname secret_inc runtime/secret.inc +func secret_inc() { + gp := getg() + gp.secret++ +} + +//go:linkname secret_dec runtime/secret.dec +func secret_dec() { + gp := getg() + gp.secret-- +} + +//go:linkname secret_eraseSecrets runtime/secret.eraseSecrets +func secret_eraseSecrets() { + // zero all the stack memory that might be dirtied with + // secrets. We do this from the systemstack so that we + // don't have to figure out which holes we have to keep + // to ensure that we can return from memclr. gp.sched will + // act as a pigeonhole for our actual return. + lo := getg().stack.lo + systemstack(func() { + // Note, this systemstack call happens within the secret mode, + // so we don't have to call out to erase our registers, the systemstack + // code will do that. + mp := acquirem() + sp := mp.curg.sched.sp + // we need to keep systemstack return on top of the stack being cleared + // for traceback + sp -= goarch.PtrSize + // TODO: keep some sort of low water mark so that we don't have + // to zero a potentially large stack if we used just a little + // bit of it. That will allow us to use a higher value for + // lo than gp.stack.lo. + memclrNoHeapPointers(unsafe.Pointer(lo), sp-lo) + releasem(mp) + }) + // Don't put any code here: the stack frame's contents are gone! +} + +// specialSecret tracks whether we need to zero an object immediately +// upon freeing. +type specialSecret struct { + special special +} + +// addSecret records the fact that we need to zero p immediately +// when it is freed. +func addSecret(p unsafe.Pointer) { + // TODO(dmo): figure out the cost of these. These are mostly + // intended to catch allocations that happen via the runtime + // that the user has no control over and not big buffers that user + // code is allocating. The cost should be relatively low, + // but we have run into a wall with other special allocations before. + lock(&mheap_.speciallock) + s := (*specialSecret)(mheap_.specialSecretAlloc.alloc()) + s.special.kind = _KindSpecialSecret + unlock(&mheap_.speciallock) + addspecial(p, &s.special, false) +} + +// send a no-op signal to an M for the purposes of +// clobbering the signal stack +// +// Use sigpreempt. If we don't have a preemption queued, this just +// turns into a no-op +func noopSignal(mp *m) { + signalM(mp, sigPreempt) +} + +// secret_getStack returns the memory range of the +// current goroutine's stack. +// For testing only. +// Note that this is kind of tricky, as the goroutine can +// be copied and/or exit before the result is used, at which +// point it may no longer be valid. +// +//go:linkname secret_getStack runtime/secret.getStack +func secret_getStack() (uintptr, uintptr) { + gp := getg() + return gp.stack.lo, gp.stack.hi +} + +// return a slice of all Ms signal stacks +// For testing only. +// +//go:linkname secret_appendSignalStacks runtime/secret.appendSignalStacks +func secret_appendSignalStacks(sigstacks []stack) []stack { + // This is probably overkill, but it's what + // doAllThreadsSyscall does + stw := stopTheWorld(stwAllThreadsSyscall) + allocmLock.lock() + acquirem() + for mp := allm; mp != nil; mp = mp.alllink { + sigstacks = append(sigstacks, mp.gsignal.stack) + } + releasem(getg().m) + allocmLock.unlock() + startTheWorld(stw) + return sigstacks +} diff --git a/src/runtime/secret/asm_amd64.s b/src/runtime/secret/asm_amd64.s new file mode 100644 index 00000000000..7011afc5eb7 --- /dev/null +++ b/src/runtime/secret/asm_amd64.s @@ -0,0 +1,213 @@ +// Copyright 2024 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. + +// Note: this assembly file is used for testing only. +// We need to access registers directly to properly test +// that secrets are erased and go test doesn't like to conditionally +// include assembly files. +// These functions defined in the package proper and we +// rely on the linker to prune these away in regular builds + +#include "go_asm.h" +#include "funcdata.h" + +TEXT ·loadRegisters(SB),0,$0-8 + MOVQ p+0(FP), AX + + MOVQ (AX), R10 + MOVQ (AX), R11 + MOVQ (AX), R12 + MOVQ (AX), R13 + + MOVOU (AX), X1 + MOVOU (AX), X2 + MOVOU (AX), X3 + MOVOU (AX), X4 + + CMPB internal∕cpu·X86+const_offsetX86HasAVX(SB), $1 + JNE return + + VMOVDQU (AX), Y5 + VMOVDQU (AX), Y6 + VMOVDQU (AX), Y7 + VMOVDQU (AX), Y8 + + CMPB internal∕cpu·X86+const_offsetX86HasAVX512(SB), $1 + JNE return + + VMOVUPD (AX), Z14 + VMOVUPD (AX), Z15 + VMOVUPD (AX), Z16 + VMOVUPD (AX), Z17 + + KMOVQ (AX), K2 + KMOVQ (AX), K3 + KMOVQ (AX), K4 + KMOVQ (AX), K5 + +return: + RET + +TEXT ·spillRegisters(SB),0,$0-16 + MOVQ p+0(FP), AX + MOVQ AX, BX + + MOVQ R10, (AX) + MOVQ R11, 8(AX) + MOVQ R12, 16(AX) + MOVQ R13, 24(AX) + ADDQ $32, AX + + MOVOU X1, (AX) + MOVOU X2, 16(AX) + MOVOU X3, 32(AX) + MOVOU X4, 48(AX) + ADDQ $64, AX + + CMPB internal∕cpu·X86+const_offsetX86HasAVX(SB), $1 + JNE return + + VMOVDQU Y5, (AX) + VMOVDQU Y6, 32(AX) + VMOVDQU Y7, 64(AX) + VMOVDQU Y8, 96(AX) + ADDQ $128, AX + + CMPB internal∕cpu·X86+const_offsetX86HasAVX512(SB), $1 + JNE return + + VMOVUPD Z14, (AX) + ADDQ $64, AX + VMOVUPD Z15, (AX) + ADDQ $64, AX + VMOVUPD Z16, (AX) + ADDQ $64, AX + VMOVUPD Z17, (AX) + ADDQ $64, AX + + KMOVQ K2, (AX) + ADDQ $8, AX + KMOVQ K3, (AX) + ADDQ $8, AX + KMOVQ K4, (AX) + ADDQ $8, AX + KMOVQ K5, (AX) + ADDQ $8, AX + +return: + SUBQ BX, AX + MOVQ AX, ret+8(FP) + RET + +TEXT ·useSecret(SB),0,$64-24 + NO_LOCAL_POINTERS + + // Load secret into AX + MOVQ secret_base+0(FP), AX + MOVQ (AX), AX + + // Scatter secret all across registers. + // Increment low byte so we can tell which register + // a leaking secret came from. + ADDQ $2, AX // add 2 so Rn has secret #n. + MOVQ AX, BX + INCQ AX + MOVQ AX, CX + INCQ AX + MOVQ AX, DX + INCQ AX + MOVQ AX, SI + INCQ AX + MOVQ AX, DI + INCQ AX + MOVQ AX, BP + INCQ AX + MOVQ AX, R8 + INCQ AX + MOVQ AX, R9 + INCQ AX + MOVQ AX, R10 + INCQ AX + MOVQ AX, R11 + INCQ AX + MOVQ AX, R12 + INCQ AX + MOVQ AX, R13 + INCQ AX + MOVQ AX, R14 + INCQ AX + MOVQ AX, R15 + + CMPB internal∕cpu·X86+const_offsetX86HasAVX512(SB), $1 + JNE noavx512 + VMOVUPD (SP), Z0 + VMOVUPD (SP), Z1 + VMOVUPD (SP), Z2 + VMOVUPD (SP), Z3 + VMOVUPD (SP), Z4 + VMOVUPD (SP), Z5 + VMOVUPD (SP), Z6 + VMOVUPD (SP), Z7 + VMOVUPD (SP), Z8 + VMOVUPD (SP), Z9 + VMOVUPD (SP), Z10 + VMOVUPD (SP), Z11 + VMOVUPD (SP), Z12 + VMOVUPD (SP), Z13 + VMOVUPD (SP), Z14 + VMOVUPD (SP), Z15 + VMOVUPD (SP), Z16 + VMOVUPD (SP), Z17 + VMOVUPD (SP), Z18 + VMOVUPD (SP), Z19 + VMOVUPD (SP), Z20 + VMOVUPD (SP), Z21 + VMOVUPD (SP), Z22 + VMOVUPD (SP), Z23 + VMOVUPD (SP), Z24 + VMOVUPD (SP), Z25 + VMOVUPD (SP), Z26 + VMOVUPD (SP), Z27 + VMOVUPD (SP), Z28 + VMOVUPD (SP), Z29 + VMOVUPD (SP), Z30 + VMOVUPD (SP), Z31 + +noavx512: + MOVOU (SP), X0 + MOVOU (SP), X1 + MOVOU (SP), X2 + MOVOU (SP), X3 + MOVOU (SP), X4 + MOVOU (SP), X5 + MOVOU (SP), X6 + MOVOU (SP), X7 + MOVOU (SP), X8 + MOVOU (SP), X9 + MOVOU (SP), X10 + MOVOU (SP), X11 + MOVOU (SP), X12 + MOVOU (SP), X13 + MOVOU (SP), X14 + MOVOU (SP), X15 + + // Put secret on the stack. + INCQ AX + MOVQ AX, (SP) + MOVQ AX, 8(SP) + MOVQ AX, 16(SP) + MOVQ AX, 24(SP) + MOVQ AX, 32(SP) + MOVQ AX, 40(SP) + MOVQ AX, 48(SP) + MOVQ AX, 56(SP) + + // Delay a bit. This makes it more likely that + // we will be the target of a signal while + // registers contain secrets. + // It also tests the path from G stack to M stack + // to scheduler and back. + CALL ·delay(SB) + + RET diff --git a/src/runtime/secret/asm_arm64.s b/src/runtime/secret/asm_arm64.s new file mode 100644 index 00000000000..1d7f7c1c924 --- /dev/null +++ b/src/runtime/secret/asm_arm64.s @@ -0,0 +1,167 @@ +// Copyright 2024 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. + +// Note: this assembly file is used for testing only. +// We need to access registers directly to properly test +// that secrets are erased and go test doesn't like to conditionally +// include assembly files. +// These functions defined in the package proper and we +// rely on the linker to prune these away in regular builds + +#include "go_asm.h" +#include "funcdata.h" + +TEXT ·loadRegisters(SB),0,$0-8 + MOVD p+0(FP), R0 + + MOVD (R0), R10 + MOVD (R0), R11 + MOVD (R0), R12 + MOVD (R0), R13 + + FMOVD (R0), F15 + FMOVD (R0), F16 + FMOVD (R0), F17 + FMOVD (R0), F18 + + VLD1 (R0), [V20.B16] + VLD1 (R0), [V21.H8] + VLD1 (R0), [V22.S4] + VLD1 (R0), [V23.D2] + + RET + +TEXT ·spillRegisters(SB),0,$0-16 + MOVD p+0(FP), R0 + MOVD R0, R1 + + MOVD R10, (R0) + MOVD R11, 8(R0) + MOVD R12, 16(R0) + MOVD R13, 24(R0) + ADD $32, R0 + + FMOVD F15, (R0) + FMOVD F16, 16(R0) + FMOVD F17, 32(R0) + FMOVD F18, 64(R0) + ADD $64, R0 + + VST1.P [V20.B16], (R0) + VST1.P [V21.H8], (R0) + VST1.P [V22.S4], (R0) + VST1.P [V23.D2], (R0) + + SUB R1, R0, R0 + MOVD R0, ret+8(FP) + RET + +TEXT ·useSecret(SB),0,$0-24 + NO_LOCAL_POINTERS + + // Load secret into R0 + MOVD secret_base+0(FP), R0 + MOVD (R0), R0 + // Scatter secret across registers. + // Increment low byte so we can tell which register + // a leaking secret came from. + + // TODO(dmo): more substantial dirtying here + ADD $1, R0 + MOVD R0, R1 + ADD $1, R0 + MOVD R0, R2 + ADD $1, R0 + MOVD R0, R3 + ADD $1, R0 + MOVD R0, R4 + ADD $1, R0 + MOVD R0, R5 + ADD $1, R0 + MOVD R0, R6 + ADD $1, R0 + MOVD R0, R7 + ADD $1, R0 + MOVD R0, R8 + ADD $1, R0 + MOVD R0, R9 + ADD $1, R0 + MOVD R0, R10 + ADD $1, R0 + MOVD R0, R11 + ADD $1, R0 + MOVD R0, R12 + ADD $1, R0 + MOVD R0, R13 + ADD $1, R0 + MOVD R0, R14 + ADD $1, R0 + MOVD R0, R15 + + // Dirty the floating point registers + ADD $1, R0 + FMOVD R0, F0 + ADD $1, R0 + FMOVD R0, F1 + ADD $1, R0 + FMOVD R0, F2 + ADD $1, R0 + FMOVD R0, F3 + ADD $1, R0 + FMOVD R0, F4 + ADD $1, R0 + FMOVD R0, F5 + ADD $1, R0 + FMOVD R0, F6 + ADD $1, R0 + FMOVD R0, F7 + ADD $1, R0 + FMOVD R0, F8 + ADD $1, R0 + FMOVD R0, F9 + ADD $1, R0 + FMOVD R0, F10 + ADD $1, R0 + FMOVD R0, F11 + ADD $1, R0 + FMOVD R0, F12 + ADD $1, R0 + FMOVD R0, F13 + ADD $1, R0 + FMOVD R0, F14 + ADD $1, R0 + FMOVD R0, F15 + ADD $1, R0 + FMOVD R0, F16 + ADD $1, R0 + FMOVD R0, F17 + ADD $1, R0 + FMOVD R0, F18 + ADD $1, R0 + FMOVD R0, F19 + ADD $1, R0 + FMOVD R0, F20 + ADD $1, R0 + FMOVD R0, F21 + ADD $1, R0 + FMOVD R0, F22 + ADD $1, R0 + FMOVD R0, F23 + ADD $1, R0 + FMOVD R0, F24 + ADD $1, R0 + FMOVD R0, F25 + ADD $1, R0 + FMOVD R0, F26 + ADD $1, R0 + FMOVD R0, F27 + ADD $1, R0 + FMOVD R0, F28 + ADD $1, R0 + FMOVD R0, F29 + ADD $1, R0 + FMOVD R0, F30 + ADD $1, R0 + FMOVD R0, F31 + RET diff --git a/src/runtime/secret/crash_test.go b/src/runtime/secret/crash_test.go new file mode 100644 index 00000000000..1bd099aa936 --- /dev/null +++ b/src/runtime/secret/crash_test.go @@ -0,0 +1,427 @@ +// Copyright 2024 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 && linux + +package secret + +import ( + "bytes" + "debug/elf" + "fmt" + "internal/testenv" + "io" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "syscall" + "testing" +) + +// Copied from runtime/runtime-gdb_unix_test.go +func canGenerateCore(t *testing.T) bool { + // Ensure there is enough RLIMIT_CORE available to generate a full core. + var lim syscall.Rlimit + err := syscall.Getrlimit(syscall.RLIMIT_CORE, &lim) + if err != nil { + t.Fatalf("error getting rlimit: %v", err) + } + // Minimum RLIMIT_CORE max to allow. This is a conservative estimate. + // Most systems allow infinity. + const minRlimitCore = 100 << 20 // 100 MB + if lim.Max < minRlimitCore { + t.Skipf("RLIMIT_CORE max too low: %#+v", lim) + } + + // Make sure core pattern will send core to the current directory. + b, err := os.ReadFile("/proc/sys/kernel/core_pattern") + if err != nil { + t.Fatalf("error reading core_pattern: %v", err) + } + if string(b) != "core\n" { + t.Skipf("Unexpected core pattern %q", string(b)) + } + + coreUsesPID := false + b, err = os.ReadFile("/proc/sys/kernel/core_uses_pid") + if err == nil { + switch string(bytes.TrimSpace(b)) { + case "0": + case "1": + coreUsesPID = true + default: + t.Skipf("unexpected core_uses_pid value %q", string(b)) + } + } + return coreUsesPID +} + +func TestCore(t *testing.T) { + // use secret, grab a coredump, rummage through + // it, trying to find our secret. + + switch runtime.GOARCH { + case "amd64", "arm64": + default: + t.Skip("unsupported arch") + } + coreUsesPid := canGenerateCore(t) + + // Build our crashing program + // Because we need assembly files to properly dirty our state + // we need to construct a package in our temporary directory. + tmpDir := t.TempDir() + // copy our base source + err := copyToDir("./testdata/crash.go", tmpDir, nil) + if err != nil { + t.Fatalf("error copying directory %v", err) + } + // Copy our testing assembly files. Use the ones from the package + // to assure that they are always in sync + err = copyToDir("./asm_amd64.s", tmpDir, nil) + if err != nil { + t.Fatalf("error copying file %v", err) + } + err = copyToDir("./asm_arm64.s", tmpDir, nil) + if err != nil { + t.Fatalf("error copying file %v", err) + } + err = copyToDir("./stubs.go", tmpDir, func(s string) string { + return strings.Replace(s, "package secret", "package main", 1) + }) + if err != nil { + t.Fatalf("error copying file %v", err) + } + + // the crashing package will live out of tree, so its source files + // cannot refer to our internal packages. However, the assembly files + // can refer to internal names and we can pass the missing offsets as + // a small generated file + offsets := ` + package main + const ( + offsetX86HasAVX = %v + offsetX86HasAVX512 = %v + ) + ` + err = os.WriteFile(filepath.Join(tmpDir, "offsets.go"), []byte(fmt.Sprintf(offsets, offsetX86HasAVX, offsetX86HasAVX512)), 0666) + if err != nil { + t.Fatalf("error writing offset file %v", err) + } + + // generate go.mod file + cmd := exec.Command(testenv.GoToolPath(t), "mod", "init", "crashtest") + cmd.Dir = tmpDir + out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() + if err != nil { + t.Fatalf("error initing module %v\n%s", err, out) + } + + cmd = exec.Command(testenv.GoToolPath(t), "build", "-o", filepath.Join(tmpDir, "a.exe")) + cmd.Dir = tmpDir + out, err = testenv.CleanCmdEnv(cmd).CombinedOutput() + if err != nil { + t.Fatalf("error building source %v\n%s", err, out) + } + + // Start the test binary. + cmd = testenv.CommandContext(t, t.Context(), "./a.exe") + cmd.Dir = tmpDir + var stdout strings.Builder + cmd.Stdout = &stdout + cmd.Stderr = &stdout + + err = cmd.Run() + // For debugging. + t.Logf("\n\n\n--- START SUBPROCESS ---\n\n\n%s\n\n--- END SUBPROCESS ---\n\n\n", stdout.String()) + if err == nil { + t.Fatalf("test binary did not crash") + } + eErr, ok := err.(*exec.ExitError) + if !ok { + t.Fatalf("error is not exit error: %v", err) + } + if eErr.Exited() { + t.Fatalf("process exited instead of being terminated: %v", eErr) + } + + rummage(t, tmpDir, eErr.Pid(), coreUsesPid) +} + +func copyToDir(name string, dir string, replace func(string) string) error { + f, err := os.ReadFile(name) + if err != nil { + return err + } + if replace != nil { + f = []byte(replace(string(f))) + } + return os.WriteFile(filepath.Join(dir, filepath.Base(name)), f, 0666) +} + +type violation struct { + id byte // secret ID + off uint64 // offset in core dump +} + +// A secret value that should never appear in a core dump, +// except for this global variable itself. +// The first byte of the secret is variable, to track +// different instances of it. +// +// If this value is changed, update ./internal/crashsecret/main.go +// TODO: this is little-endian specific. +var secretStore = [8]byte{ + 0x00, + 0x81, + 0xa0, + 0xc6, + 0xb3, + 0x01, + 0x66, + 0x53, +} + +func rummage(t *testing.T, tmpDir string, pid int, coreUsesPid bool) { + coreFileName := "core" + if coreUsesPid { + coreFileName += fmt.Sprintf(".%d", pid) + } + core, err := os.Open(filepath.Join(tmpDir, coreFileName)) + if err != nil { + t.Fatalf("core file not found: %v", err) + } + b, err := io.ReadAll(core) + if err != nil { + t.Fatalf("can't read core file: %v", err) + } + + // Open elf view onto core file. + coreElf, err := elf.NewFile(core) + if err != nil { + t.Fatalf("can't parse core file: %v", err) + } + + // Look for any places that have the secret. + var violations []violation // core file offsets where we found a secret + i := 0 + for { + j := bytes.Index(b[i:], secretStore[1:]) + if j < 0 { + break + } + j-- + i += j + + t.Errorf("secret %d found at offset %x in core file", b[i], i) + violations = append(violations, violation{ + id: b[i], + off: uint64(i), + }) + + i += len(secretStore) + } + + // Get more specific data about where in the core we found the secrets. + regions := elfRegions(t, core, coreElf) + for _, r := range regions { + for _, v := range violations { + if v.off >= r.min && v.off < r.max { + var addr string + if r.addrMin != 0 { + addr = fmt.Sprintf(" addr=%x", r.addrMin+(v.off-r.min)) + } + t.Logf("additional info: secret %d at offset %x in %s%s", v.id, v.off-r.min, r.name, addr) + } + } + } +} + +type elfRegion struct { + name string + min, max uint64 // core file offset range + addrMin, addrMax uint64 // inferior address range (or 0,0 if no address, like registers) +} + +func elfRegions(t *testing.T, core *os.File, coreElf *elf.File) []elfRegion { + var regions []elfRegion + for _, p := range coreElf.Progs { + regions = append(regions, elfRegion{ + name: fmt.Sprintf("%s[%s]", p.Type, p.Flags), + min: p.Off, + max: p.Off + min(p.Filesz, p.Memsz), + addrMin: p.Vaddr, + addrMax: p.Vaddr + min(p.Filesz, p.Memsz), + }) + } + + // TODO(dmo): parse thread regions for arm64. + // This doesn't invalidate the test, it just makes it harder to figure + // out where we're leaking stuff. + if runtime.GOARCH == "amd64" { + regions = append(regions, threadRegions(t, core, coreElf)...) + } + + for i, r1 := range regions { + for j, r2 := range regions { + if i == j { + continue + } + if r1.max <= r2.min || r2.max <= r1.min { + continue + } + t.Fatalf("overlapping regions %v %v", r1, r2) + } + } + + return regions +} + +func threadRegions(t *testing.T, core *os.File, coreElf *elf.File) []elfRegion { + var regions []elfRegion + + for _, prog := range coreElf.Progs { + if prog.Type != elf.PT_NOTE { + continue + } + + b := make([]byte, prog.Filesz) + _, err := core.ReadAt(b, int64(prog.Off)) + if err != nil { + t.Fatalf("can't read core file %v", err) + } + prefix := "unk" + b0 := b + for len(b) > 0 { + namesz := coreElf.ByteOrder.Uint32(b) + b = b[4:] + descsz := coreElf.ByteOrder.Uint32(b) + b = b[4:] + typ := elf.NType(coreElf.ByteOrder.Uint32(b)) + b = b[4:] + name := string(b[:namesz-1]) + b = b[(namesz+3)/4*4:] + off := prog.Off + uint64(len(b0)-len(b)) + desc := b[:descsz] + b = b[(descsz+3)/4*4:] + + if name != "CORE" && name != "LINUX" { + continue + } + end := off + uint64(len(desc)) + // Note: amd64 specific + // See /usr/include/x86_64-linux-gnu/bits/sigcontext.h + // + // struct _fpstate + switch typ { + case elf.NT_PRSTATUS: + pid := coreElf.ByteOrder.Uint32(desc[32:36]) + prefix = fmt.Sprintf("thread%d: ", pid) + regions = append(regions, elfRegion{ + name: prefix + "prstatus header", + min: off, + max: off + 112, + }) + off += 112 + greg := []string{ + "r15", + "r14", + "r13", + "r12", + "rbp", + "rbx", + "r11", + "r10", + "r9", + "r8", + "rax", + "rcx", + "rdx", + "rsi", + "rdi", + "orig_rax", + "rip", + "cs", + "eflags", + "rsp", + "ss", + "fs_base", + "gs_base", + "ds", + "es", + "fs", + "gs", + } + for _, r := range greg { + regions = append(regions, elfRegion{ + name: prefix + r, + min: off, + max: off + 8, + }) + off += 8 + } + regions = append(regions, elfRegion{ + name: prefix + "prstatus footer", + min: off, + max: off + 8, + }) + off += 8 + case elf.NT_FPREGSET: + regions = append(regions, elfRegion{ + name: prefix + "fpregset header", + min: off, + max: off + 32, + }) + off += 32 + for i := 0; i < 8; i++ { + regions = append(regions, elfRegion{ + name: prefix + fmt.Sprintf("mmx%d", i), + min: off, + max: off + 16, + }) + off += 16 + // They are long double (10 bytes), but + // stored in 16-byte slots. + } + for i := 0; i < 16; i++ { + regions = append(regions, elfRegion{ + name: prefix + fmt.Sprintf("xmm%d", i), + min: off, + max: off + 16, + }) + off += 16 + } + regions = append(regions, elfRegion{ + name: prefix + "fpregset footer", + min: off, + max: off + 96, + }) + off += 96 + /* + case NT_X86_XSTATE: // aka NT_PRPSINFO+511 + // legacy: 512 bytes + // xsave header: 64 bytes + fmt.Printf("hdr %v\n", desc[512:][:64]) + // ymm high128: 256 bytes + + println(len(desc)) + fallthrough + */ + default: + regions = append(regions, elfRegion{ + name: fmt.Sprintf("%s/%s", name, typ), + min: off, + max: off + uint64(len(desc)), + }) + off += uint64(len(desc)) + } + if off != end { + t.Fatalf("note section incomplete") + } + } + } + return regions +} diff --git a/src/runtime/secret/export.go b/src/runtime/secret/export.go new file mode 100644 index 00000000000..34f3c378f3e --- /dev/null +++ b/src/runtime/secret/export.go @@ -0,0 +1,16 @@ +// Copyright 2025 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 secret + +import ( + "internal/cpu" + "unsafe" +) + +// exports for assembly testing functions +const ( + offsetX86HasAVX = unsafe.Offsetof(cpu.X86.HasAVX) + offsetX86HasAVX512 = unsafe.Offsetof(cpu.X86.HasAVX512) +) diff --git a/src/runtime/secret/secret.go b/src/runtime/secret/secret.go new file mode 100644 index 00000000000..f669b98828d --- /dev/null +++ b/src/runtime/secret/secret.go @@ -0,0 +1,128 @@ +// Copyright 2024 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 secret + +import ( + "runtime" + _ "unsafe" +) + +// Do invokes f. +// +// Do ensures that any temporary storage used by f is erased in a +// timely manner. (In this context, "f" is shorthand for the +// entire call tree initiated by f.) +// - Any registers used by f are erased before Do returns. +// - Any stack used by f is erased before Do returns. +// - Any heap allocation done by f is erased as soon as the garbage +// collector realizes that it is no longer reachable. +// - Do works even if f panics or calls runtime.Goexit. As part of +// that, any panic raised by f will appear as if it originates from +// Do itself. +// +// Limitations: +// - Currently only supported on linux/amd64 and linux/arm64. On unsupported +// platforms, Do will invoke f directly. +// - Protection does not extend to any global variables written by f. +// - Any attempt to launch a goroutine by f will result in a panic. +// - If f calls runtime.Goexit, erasure can be delayed by defers +// higher up on the call stack. +// - Heap allocations will only be erased if the program drops all +// references to those allocations, and then the garbage collector +// notices that those references are gone. The former is under +// control of the program, but the latter is at the whim of the +// runtime. +// - Any value panicked by f may point to allocations from within +// f. Those allocations will not be erased until (at least) the +// panicked value is dead. +// - Pointer addresses may leak into data buffers used by the runtime +// to perform garbage collection. Users should not encode confidential +// information into pointers. For example, if an offset into an array or +// struct is confidential, then users should not create a pointer into +// the object. Since this function is intended to be used with constant-time +// cryptographic code, this requirement is usually fulfilled implicitly. +func Do(f func()) { + const osArch = runtime.GOOS + "/" + runtime.GOARCH + switch osArch { + default: + // unsupported, just invoke f directly. + f() + return + case "linux/amd64", "linux/arm64": + } + + // Place to store any panic value. + var p any + + // Step 1: increment the nesting count. + inc() + + // Step 2: call helper. The helper just calls f + // and captures (recovers) any panic result. + p = doHelper(f) + + // Step 3: erase everything used by f (stack, registers). + eraseSecrets() + + // Step 4: decrement the nesting count. + dec() + + // Step 5: re-raise any caught panic. + // This will make the panic appear to come + // from a stack whose bottom frame is + // runtime/secret.Do. + // Anything below that to do with f will be gone. + // + // Note that the panic value is not erased. It behaves + // like any other value that escapes from f. If it is + // heap allocated, it will be erased when the garbage + // collector notices it is no longer referenced. + if p != nil { + panic(p) + } + + // Note: if f calls runtime.Goexit, step 3 and above will not + // happen, as Goexit is unrecoverable. We handle that case in + // runtime/proc.go:goexit0. +} + +func doHelper(f func()) (p any) { + // Step 2b: Pop the stack up to the secret.doHelper frame + // if we are in the process of panicking. + // (It is a no-op if we are not panicking.) + // We return any panicked value to secret.Do, who will + // re-panic it. + defer func() { + // Note: we rely on the go1.21+ behavior that + // if we are panicking, recover returns non-nil. + p = recover() + }() + + // Step 2a: call the secret function. + f() + + return +} + +// Enabled reports whether [Do] appears anywhere on the call stack. +func Enabled() bool { + return count() > 0 +} + +// implemented in runtime + +//go:linkname count +func count() int32 + +//go:linkname inc +func inc() + +//go:linkname dec +func dec() + +//go:linkname eraseSecrets +func eraseSecrets() diff --git a/src/runtime/secret/secret_test.go b/src/runtime/secret/secret_test.go new file mode 100644 index 00000000000..7651a93ca5e --- /dev/null +++ b/src/runtime/secret/secret_test.go @@ -0,0 +1,293 @@ +// Copyright 2024 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. + +// the race detector does not like our pointer shenanigans +// while checking the stack. + +//go:build goexperiment.runtimesecret && (arm64 || amd64) && linux && !race + +package secret + +import ( + "runtime" + "strings" + "testing" + "time" + "unsafe" +) + +type secretType int64 + +const secretValue = 0x53c237_53c237 + +// S is a type that might have some secrets in it. +type S [100]secretType + +// makeS makes an S with secrets in it. +// +//go:noinline +func makeS() S { + // Note: noinline ensures this doesn't get inlined and + // completely optimized away. + var s S + for i := range s { + s[i] = secretValue + } + return s +} + +// heapS allocates an S on the heap with secrets in it. +// +//go:noinline +func heapS() *S { + // Note: noinline forces heap allocation + s := makeS() + return &s +} + +// for the tiny allocator +// +//go:noinline +func heapSTiny() *secretType { + s := new(secretType(secretValue)) + return s +} + +// Test that when we allocate inside secret.Do, the resulting +// allocations are zeroed by the garbage collector when they +// are freed. +// See runtime/mheap.go:freeSpecial. +func TestHeap(t *testing.T) { + var u uintptr + Do(func() { + u = uintptr(unsafe.Pointer(heapS())) + }) + + runtime.GC() + + // Check that object got zeroed. + checkRangeForSecret(t, u, u+unsafe.Sizeof(S{})) + // Also check our stack, just because we can. + checkStackForSecret(t) +} + +func TestHeapTiny(t *testing.T) { + var u uintptr + Do(func() { + u = uintptr(unsafe.Pointer(heapSTiny())) + }) + runtime.GC() + + // Check that object got zeroed. + checkRangeForSecret(t, u, u+unsafe.Sizeof(secretType(0))) + // Also check our stack, just because we can. + checkStackForSecret(t) +} + +// Test that when we return from secret.Do, we zero the stack used +// by the argument to secret.Do. +// See runtime/secret.go:secret_dec. +func TestStack(t *testing.T) { + checkStackForSecret(t) // if this fails, something is wrong with the test + + Do(func() { + s := makeS() + use(&s) + }) + + checkStackForSecret(t) +} + +//go:noinline +func use(s *S) { + // Note: noinline prevents dead variable elimination. +} + +// Test that when we copy a stack, we zero the old one. +// See runtime/stack.go:copystack. +func TestStackCopy(t *testing.T) { + checkStackForSecret(t) // if this fails, something is wrong with the test + + var lo, hi uintptr + Do(func() { + // Put some secrets on the current stack frame. + s := makeS() + use(&s) + // Remember the current stack. + lo, hi = getStack() + // Use a lot more stack to force a stack copy. + growStack() + }) + checkRangeForSecret(t, lo, hi) // pre-grow stack + checkStackForSecret(t) // post-grow stack (just because we can) +} + +func growStack() { + growStack1(1000) +} +func growStack1(n int) { + if n == 0 { + return + } + growStack1(n - 1) +} + +func TestPanic(t *testing.T) { + checkStackForSecret(t) // if this fails, something is wrong with the test + + defer func() { + checkStackForSecret(t) + + p := recover() + if p == nil { + t.Errorf("panic squashed") + return + } + var e error + var ok bool + if e, ok = p.(error); !ok { + t.Errorf("panic not an error") + } + if !strings.Contains(e.Error(), "divide by zero") { + t.Errorf("panic not a divide by zero error: %s", e.Error()) + } + var pcs [10]uintptr + n := runtime.Callers(0, pcs[:]) + frames := runtime.CallersFrames(pcs[:n]) + for { + frame, more := frames.Next() + if strings.Contains(frame.Function, "dividePanic") { + t.Errorf("secret function in traceback") + } + if !more { + break + } + } + }() + Do(dividePanic) +} + +func dividePanic() { + s := makeS() + use(&s) + _ = 8 / zero +} + +var zero int + +func TestGoExit(t *testing.T) { + checkStackForSecret(t) // if this fails, something is wrong with the test + + c := make(chan uintptr, 2) + + go func() { + // Run the test in a separate goroutine + defer func() { + // Tell original goroutine what our stack is + // so it can check it for secrets. + lo, hi := getStack() + c <- lo + c <- hi + }() + Do(func() { + s := makeS() + use(&s) + // there's an entire round-trip through the scheduler between here + // and when we are able to check if the registers are still dirtied, and we're + // not guaranteed to run on the same M. Make a best effort attempt anyway + loadRegisters(unsafe.Pointer(&s)) + runtime.Goexit() + }) + t.Errorf("goexit didn't happen") + }() + lo := <-c + hi := <-c + // We want to wait until the other goroutine has finished Goexiting and + // cleared its stack. There's no signal for that, so just wait a bit. + time.Sleep(1 * time.Millisecond) + + checkRangeForSecret(t, lo, hi) + + var spillArea [64]secretType + n := spillRegisters(unsafe.Pointer(&spillArea)) + if n > unsafe.Sizeof(spillArea) { + t.Fatalf("spill area overrun %d\n", n) + } + for i, v := range spillArea { + if v == secretValue { + t.Errorf("secret found in spill slot %d", i) + } + } +} + +func checkStackForSecret(t *testing.T) { + t.Helper() + lo, hi := getStack() + checkRangeForSecret(t, lo, hi) +} +func checkRangeForSecret(t *testing.T, lo, hi uintptr) { + t.Helper() + for p := lo; p < hi; p += unsafe.Sizeof(secretType(0)) { + v := *(*secretType)(unsafe.Pointer(p)) + if v == secretValue { + t.Errorf("secret found in [%x,%x] at %x", lo, hi, p) + } + } +} + +func TestRegisters(t *testing.T) { + Do(func() { + s := makeS() + loadRegisters(unsafe.Pointer(&s)) + }) + var spillArea [64]secretType + n := spillRegisters(unsafe.Pointer(&spillArea)) + if n > unsafe.Sizeof(spillArea) { + t.Fatalf("spill area overrun %d\n", n) + } + for i, v := range spillArea { + if v == secretValue { + t.Errorf("secret found in spill slot %d", i) + } + } +} + +func TestSignalStacks(t *testing.T) { + Do(func() { + s := makeS() + loadRegisters(unsafe.Pointer(&s)) + // cause a signal with our secret state to dirty + // at least one of the signal stacks + func() { + defer func() { + x := recover() + if x == nil { + panic("did not get panic") + } + }() + var p *int + *p = 20 + }() + }) + // signal stacks aren't cleared until after + // the next GC after secret.Do returns + runtime.GC() + stk := make([]stack, 0, 100) + stk = appendSignalStacks(stk) + for _, s := range stk { + checkRangeForSecret(t, s.lo, s.hi) + } +} + +// hooks into the runtime +func getStack() (uintptr, uintptr) + +// Stack is a copy of runtime.stack for testing export. +// Fields must match. +type stack struct { + lo uintptr + hi uintptr +} + +func appendSignalStacks([]stack) []stack diff --git a/src/runtime/secret/stubs.go b/src/runtime/secret/stubs.go new file mode 100644 index 00000000000..ec66ef2729a --- /dev/null +++ b/src/runtime/secret/stubs.go @@ -0,0 +1,32 @@ +// Copyright 2025 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 arm64 || amd64 + +// testing stubs, these are implemented in assembly in +// asm_$GOARCH.s +// +// Note that this file is also used as a template to build a +// crashing binary that tries to leave secrets in places where +// they are supposed to be erased. see crash_test.go for more info + +package secret + +import "unsafe" + +// Load data from p into test registers. +// +//go:noescape +func loadRegisters(p unsafe.Pointer) + +// Spill data from test registers into p. +// Returns the amount of space filled in. +// +//go:noescape +func spillRegisters(p unsafe.Pointer) uintptr + +// Load secret into all registers. +// +//go:noescape +func useSecret(secret []byte) diff --git a/src/runtime/secret/stubs_noasm.go b/src/runtime/secret/stubs_noasm.go new file mode 100644 index 00000000000..f8091ff393f --- /dev/null +++ b/src/runtime/secret/stubs_noasm.go @@ -0,0 +1,13 @@ +// Copyright 2025 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 !arm64 && !amd64 + +package secret + +import "unsafe" + +func loadRegisters(p unsafe.Pointer) {} +func spillRegisters(p unsafe.Pointer) uintptr { return 0 } +func useSecret(secret []byte) {} diff --git a/src/runtime/secret/testdata/crash.go b/src/runtime/secret/testdata/crash.go new file mode 100644 index 00000000000..cf48fb7d44c --- /dev/null +++ b/src/runtime/secret/testdata/crash.go @@ -0,0 +1,142 @@ +// Copyright 2025 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 + +import ( + "bytes" + "fmt" + "os" + "runtime" + "runtime/debug" + "runtime/secret" + "sync" + "syscall" + "time" + _ "unsafe" + "weak" +) + +// callback from assembly +// +//go:linkname delay main.delay +func delay() { + time.Sleep(1 * time.Millisecond) +} + +// Same secret as in ../../crash_test.go +var secretStore = [8]byte{ + 0x00, + 0x81, + 0xa0, + 0xc6, + 0xb3, + 0x01, + 0x66, + 0x53, +} + +func main() { + enableCore() + useSecretProc() + // clear out secret. That way we don't have + // to figure out which secret is the allowed + // source + clear(secretStore[:]) + panic("terminate") +} + +// Copied from runtime/runtime-gdb_unix_test.go +func enableCore() { + debug.SetTraceback("crash") + + var lim syscall.Rlimit + err := syscall.Getrlimit(syscall.RLIMIT_CORE, &lim) + if err != nil { + panic(fmt.Sprintf("error getting rlimit: %v", err)) + } + lim.Cur = lim.Max + fmt.Fprintf(os.Stderr, "Setting RLIMIT_CORE = %+#v\n", lim) + err = syscall.Setrlimit(syscall.RLIMIT_CORE, &lim) + if err != nil { + panic(fmt.Sprintf("error setting rlimit: %v", err)) + } +} + +// useSecretProc does 5 seconds of work, using the secret value +// inside secret.Do in a bunch of ways. +func useSecretProc() { + stop := make(chan bool) + var wg sync.WaitGroup + + for i := 0; i < 4; i++ { + wg.Add(1) + go func() { + time.Sleep(1 * time.Second) + for { + select { + case <-stop: + wg.Done() + return + default: + secret.Do(func() { + // Copy key into a variable-sized heap allocation. + // This both puts secrets in heap objects, + // and more generally just causes allocation, + // which forces garbage collection, which + // requires interrupts and the like. + s := bytes.Repeat(secretStore[:], 1+i*2) + // Also spam the secret across all registers. + useSecret(s) + }) + } + } + }() + } + + // Send some allocations over a channel. This does 2 things: + // 1) forces some GCs to happen + // 2) causes more scheduling noise (Gs moving between Ms, etc.) + c := make(chan []byte) + wg.Add(2) + go func() { + for { + select { + case <-stop: + wg.Done() + return + case c <- make([]byte, 256): + } + } + }() + go func() { + for { + select { + case <-stop: + wg.Done() + return + case <-c: + } + } + }() + + time.Sleep(5 * time.Second) + close(stop) + wg.Wait() + // use a weak reference for ensuring that the GC has cleared everything + // Use a large value to avoid the tiny allocator. + w := weak.Make(new([2048]byte)) + // 20 seems like a decent amount? + for i := 0; i < 20; i++ { + runtime.GC() // GC should clear any secret heap objects and clear out scheduling buffers. + if w.Value() == nil { + fmt.Fprintf(os.Stderr, "number of GCs %v\n", i+1) + return + } + } + fmt.Fprintf(os.Stderr, "GC didn't clear out in time\n") + // This will cause the core dump to happen with the sentinel value still in memory + // so we will detect the fault. + panic("fault") +} diff --git a/src/runtime/secret_amd64.s b/src/runtime/secret_amd64.s new file mode 100644 index 00000000000..06103d1c0f9 --- /dev/null +++ b/src/runtime/secret_amd64.s @@ -0,0 +1,107 @@ +// Copyright 2024 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. + +#include "go_asm.h" +#include "textflag.h" +#include "funcdata.h" + +// TODO(dmo): generate these with mkpreempt.go, the register sets +// are tightly coupled and this will ensure that we keep them +// all synchronized + +// secretEraseRegisters erases any register that may +// have been used with user code within a secret.Do function. +// This is roughly the general purpose and floating point +// registers, barring any reserved registers and registers generally +// considered architectural (amd64 segment registers, arm64 exception registers) +TEXT ·secretEraseRegisters(SB),NOFRAME|NOSPLIT,$0-0 + XORL AX, AX + JMP ·secretEraseRegistersMcall(SB) + +// Mcall requires an argument in AX. This function +// excludes that register from being cleared +TEXT ·secretEraseRegistersMcall(SB),NOSPLIT|NOFRAME,$0-0 + // integer registers + XORL BX, BX + XORL CX, CX + XORL DX, DX + XORL DI, DI + XORL SI, SI + // BP = frame pointer + // SP = stack pointer + XORL R8, R8 + XORL R9, R9 + XORL R10, R10 + XORL R11, R11 + XORL R12, R12 + XORL R13, R13 + // R14 = G register + XORL R15, R15 + + // floating-point registers + CMPB internal∕cpu·X86+const_offsetX86HasAVX(SB), $1 + JEQ avx + + PXOR X0, X0 + PXOR X1, X1 + PXOR X2, X2 + PXOR X3, X3 + PXOR X4, X4 + PXOR X5, X5 + PXOR X6, X6 + PXOR X7, X7 + PXOR X8, X8 + PXOR X9, X9 + PXOR X10, X10 + PXOR X11, X11 + PXOR X12, X12 + PXOR X13, X13 + PXOR X14, X14 + PXOR X15, X15 + JMP noavx512 + +avx: + // VZEROALL zeroes all of the X0-X15 registers, no matter how wide. + // That includes Y0-Y15 (256-bit avx) and Z0-Z15 (512-bit avx512). + VZEROALL + + // Clear all the avx512 state. + CMPB internal∕cpu·X86+const_offsetX86HasAVX512(SB), $1 + JNE noavx512 + + // Zero X16-X31 + // Note that VZEROALL above already cleared Z0-Z15. + VMOVAPD Z0, Z16 + VMOVAPD Z0, Z17 + VMOVAPD Z0, Z18 + VMOVAPD Z0, Z19 + VMOVAPD Z0, Z20 + VMOVAPD Z0, Z21 + VMOVAPD Z0, Z22 + VMOVAPD Z0, Z23 + VMOVAPD Z0, Z24 + VMOVAPD Z0, Z25 + VMOVAPD Z0, Z26 + VMOVAPD Z0, Z27 + VMOVAPD Z0, Z28 + VMOVAPD Z0, Z29 + VMOVAPD Z0, Z30 + VMOVAPD Z0, Z31 + + // Zero k0-k7 + KXORQ K0, K0, K0 + KXORQ K0, K0, K1 + KXORQ K0, K0, K2 + KXORQ K0, K0, K3 + KXORQ K0, K0, K4 + KXORQ K0, K0, K5 + KXORQ K0, K0, K6 + KXORQ K0, K0, K7 + +noavx512: + // misc registers + CMPL BX, BX //eflags + // segment registers? Direction flag? Both seem overkill. + + RET diff --git a/src/runtime/secret_arm64.s b/src/runtime/secret_arm64.s new file mode 100644 index 00000000000..d21b139df85 --- /dev/null +++ b/src/runtime/secret_arm64.s @@ -0,0 +1,90 @@ +// Copyright 2024 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. + +#include "go_asm.h" +#include "textflag.h" +#include "funcdata.h" + +TEXT ·secretEraseRegisters(SB),NOFRAME|NOSPLIT,$0-0 + MOVD ZR, R0 + MOVD ZR, R26 + JMP ·secretEraseRegistersMcall(SB) + +// Mcall requires an argument in R0 and does not have a +// stack frame to spill into. Additionally, there is no stack +// to spill the link register into. This function deliberately +// doesn't clear R0 and R26, and Mcall uses R26 as a link register. +TEXT ·secretEraseRegistersMcall(SB),NOFRAME|NOSPLIT,$0-0 + // integer registers + MOVD ZR, R1 + MOVD ZR, R2 + MOVD ZR, R3 + MOVD ZR, R4 + MOVD ZR, R5 + MOVD ZR, R6 + MOVD ZR, R7 + MOVD ZR, R8 + MOVD ZR, R9 + MOVD ZR, R10 + MOVD ZR, R11 + MOVD ZR, R12 + MOVD ZR, R13 + MOVD ZR, R14 + MOVD ZR, R15 + MOVD ZR, R16 + MOVD ZR, R17 + // R18 = platform register + MOVD ZR, R19 + MOVD ZR, R20 + MOVD ZR, R21 + MOVD ZR, R22 + MOVD ZR, R23 + MOVD ZR, R24 + MOVD ZR, R25 + // R26 used for extra link register in mcall where we can't spill + MOVD ZR, R27 + // R28 = g + // R29 = frame pointer + // R30 = link pointer (return address) + // R31 = stack pointer + + // floating point registers + // (also clears simd registers) + FMOVD ZR, F0 + FMOVD ZR, F1 + FMOVD ZR, F2 + FMOVD ZR, F3 + FMOVD ZR, F4 + FMOVD ZR, F5 + FMOVD ZR, F6 + FMOVD ZR, F7 + FMOVD ZR, F8 + FMOVD ZR, F9 + FMOVD ZR, F10 + FMOVD ZR, F11 + FMOVD ZR, F12 + FMOVD ZR, F13 + FMOVD ZR, F14 + FMOVD ZR, F15 + FMOVD ZR, F16 + FMOVD ZR, F17 + FMOVD ZR, F18 + FMOVD ZR, F19 + FMOVD ZR, F20 + FMOVD ZR, F21 + FMOVD ZR, F22 + FMOVD ZR, F23 + FMOVD ZR, F24 + FMOVD ZR, F25 + FMOVD ZR, F26 + FMOVD ZR, F27 + FMOVD ZR, F28 + FMOVD ZR, F29 + FMOVD ZR, F30 + FMOVD ZR, F31 + + // misc registers + CMP ZR, ZR // N,Z,C,V flags + + RET diff --git a/src/runtime/secret_asm.go b/src/runtime/secret_asm.go new file mode 100644 index 00000000000..08223a673d9 --- /dev/null +++ b/src/runtime/secret_asm.go @@ -0,0 +1,9 @@ +// Copyright 2024 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 arm64 || amd64 + +package runtime + +func secretEraseRegisters() diff --git a/src/runtime/secret_noasm.go b/src/runtime/secret_noasm.go new file mode 100644 index 00000000000..3f7e49af7a5 --- /dev/null +++ b/src/runtime/secret_noasm.go @@ -0,0 +1,11 @@ +// Copyright 2024 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 !arm64 && !amd64 + +package runtime + +func secretEraseRegisters() { + throw("runtime/secret.Do not supported yet") +} diff --git a/src/runtime/secret_nosecret.go b/src/runtime/secret_nosecret.go new file mode 100644 index 00000000000..bf50fb5a54c --- /dev/null +++ b/src/runtime/secret_nosecret.go @@ -0,0 +1,32 @@ +// Copyright 2025 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 !(amd64 || arm64) || !linux + +package runtime + +import "unsafe" + +// Stubs for platforms that do not implement runtime/secret + +//go:linkname secret_count runtime/secret.count +func secret_count() int32 { return 0 } + +//go:linkname secret_inc runtime/secret.inc +func secret_inc() {} + +//go:linkname secret_dec runtime/secret.dec +func secret_dec() {} + +//go:linkname secret_eraseSecrets runtime/secret.eraseSecrets +func secret_eraseSecrets() {} + +func addSecret(p unsafe.Pointer) {} + +type specialSecret struct{} + +//go:linkname secret_getStack runtime/secret.getStack +func secret_getStack() (uintptr, uintptr) { return 0, 0 } + +func noopSignal(mp *m) {} diff --git a/src/runtime/signal_linux_amd64.go b/src/runtime/signal_linux_amd64.go index 573b1183974..f4559f570e8 100644 --- a/src/runtime/signal_linux_amd64.go +++ b/src/runtime/signal_linux_amd64.go @@ -54,3 +54,31 @@ func (c *sigctxt) set_sigcode(x uint64) { c.info.si_code = int32(x) } func (c *sigctxt) set_sigaddr(x uint64) { *(*uintptr)(add(unsafe.Pointer(c.info), 2*goarch.PtrSize)) = uintptr(x) } + +// dumpSigStack prints a signal stack with the context, fpstate pointer field within that context and +// the beginning of the fpstate annotated by C/F/S respectively +func dumpSigStack(s string, sp uintptr, stackhi uintptr, ctx uintptr) { + println(s) + println("SP:\t", hex(sp)) + println("ctx:\t", hex(ctx)) + fpfield := ctx + unsafe.Offsetof(ucontext{}.uc_mcontext) + unsafe.Offsetof(mcontext{}.fpregs) + println("fpfield:\t", hex(fpfield)) + fpbegin := uintptr(unsafe.Pointer((&sigctxt{nil, unsafe.Pointer(ctx)}).regs().fpstate)) + println("fpstate:\t", hex(fpbegin)) + hexdumpWords(sp, stackhi, func(p uintptr, hm hexdumpMarker) { + switch p { + case ctx: + hm.start() + print("C") + println() + case fpfield: + hm.start() + print("F") + println() + case fpbegin: + hm.start() + print("S") + println() + } + }) +} diff --git a/src/runtime/signal_linux_arm64.go b/src/runtime/signal_linux_arm64.go index 4ccc0307923..2d31051fd04 100644 --- a/src/runtime/signal_linux_arm64.go +++ b/src/runtime/signal_linux_arm64.go @@ -69,3 +69,22 @@ func (c *sigctxt) set_r28(x uint64) { c.regs().regs[28] = x } func (c *sigctxt) set_sigaddr(x uint64) { *(*uintptr)(add(unsafe.Pointer(c.info), 2*goarch.PtrSize)) = uintptr(x) } + +func dumpSigStack(s string, sp uintptr, stackhi uintptr, ctx uintptr) { + println(s) + println("SP:\t", hex(sp)) + println("ctx:\t", hex(ctx)) + entriesStart := uintptr(unsafe.Pointer(&(*ucontext)(unsafe.Pointer(ctx)).uc_mcontext.__reserved)) + hexdumpWords(sp, stackhi, func(p uintptr, hm hexdumpMarker) { + switch p { + case ctx: + hm.start() + print("C") + println() + case entriesStart: + hm.start() + print("E") + println() + } + }) +} diff --git a/src/runtime/signal_unix.go b/src/runtime/signal_unix.go index 96628d6baae..f352cb3c024 100644 --- a/src/runtime/signal_unix.go +++ b/src/runtime/signal_unix.go @@ -8,6 +8,7 @@ package runtime import ( "internal/abi" + "internal/goexperiment" "internal/runtime/atomic" "internal/runtime/sys" "unsafe" @@ -488,6 +489,11 @@ func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) { c.fixsigcode(sig) sighandler(sig, info, ctx, gp) + + if goexperiment.RuntimeSecret && gp.secret > 0 { + atomic.Store(&gp.m.signalSecret, 1) + } + setg(gp) if setStack { restoreGsignalStack(&gsignalStack) diff --git a/src/runtime/sizeof_test.go b/src/runtime/sizeof_test.go index 5888177f0ea..9dde0da9636 100644 --- a/src/runtime/sizeof_test.go +++ b/src/runtime/sizeof_test.go @@ -21,7 +21,7 @@ func TestSizeof(t *testing.T) { _32bit uintptr // size on 32bit platforms _64bit uintptr // size on 64bit platforms }{ - {runtime.G{}, 280 + xreg, 440 + xreg}, // g, but exported for testing + {runtime.G{}, 284 + xreg, 448 + xreg}, // g, but exported for testing {runtime.Sudog{}, 64, 104}, // sudog, but exported for testing } diff --git a/src/runtime/stack.go b/src/runtime/stack.go index c92accf1882..d1c80276a5c 100644 --- a/src/runtime/stack.go +++ b/src/runtime/stack.go @@ -8,6 +8,7 @@ import ( "internal/abi" "internal/cpu" "internal/goarch" + "internal/goexperiment" "internal/goos" "internal/runtime/atomic" "internal/runtime/gc" @@ -985,6 +986,16 @@ func copystack(gp *g, newsize uintptr) { } // free old stack + if goexperiment.RuntimeSecret && gp.secret > 0 { + // Some portion of the old stack has secret stuff on it. + // We don't really know where we entered secret mode, + // so just clear the whole thing. + // TODO(dmo): traceback until we hit secret.Do? clearing + // is fast and optimized, might not be worth it. + memclrNoHeapPointers(unsafe.Pointer(old.lo), old.hi-old.lo) + // The memmove call above might put secrets from the stack into registers. + secretEraseRegisters() + } if stackPoisonCopy != 0 { fillstack(old, 0xfc) } @@ -1026,6 +1037,14 @@ func newstack() { } gp := thisg.m.curg + if goexperiment.RuntimeSecret && gp.secret > 0 { + // If we're entering here from a secret context, clear + // all the registers. This is important because we + // might context switch to a different goroutine which + // is not in secret mode, and it will not be careful + // about clearing its registers. + secretEraseRegisters() + } if thisg.m.curg.throwsplit { // Update syscallsp, syscallpc in case traceback uses them. diff --git a/src/runtime/sys_linux_amd64.s b/src/runtime/sys_linux_amd64.s index e252a4b9147..618553b1969 100644 --- a/src/runtime/sys_linux_amd64.s +++ b/src/runtime/sys_linux_amd64.s @@ -228,6 +228,18 @@ TEXT runtime·nanotime1(SB),NOSPLIT,$16-8 // due to stack probes inserted to avoid stack/heap collisions. // See issue #20427. +#ifdef GOEXPERIMENT_runtimesecret + // The kernel might spill our secrets onto g0 + // erase our registers here. + // TODO(dmo): what is the ABI guarantee here? we use + // R14 later, but the function is ABI0 + CMPL g_secret(R14), $0 + JEQ nosecret + CALL ·secretEraseRegisters(SB) + +nosecret: +#endif + MOVQ SP, R12 // Save old SP; R12 unchanged by C code. MOVQ g_m(R14), BX // BX unchanged by C code. diff --git a/src/runtime/sys_linux_arm64.s b/src/runtime/sys_linux_arm64.s index 7a81d5479e3..88f7213525f 100644 --- a/src/runtime/sys_linux_arm64.s +++ b/src/runtime/sys_linux_arm64.s @@ -225,6 +225,13 @@ TEXT runtime·mincore(SB),NOSPLIT|NOFRAME,$0-28 // func walltime() (sec int64, nsec int32) TEXT runtime·walltime(SB),NOSPLIT,$24-12 +#ifdef GOEXPERIMENT_runtimesecret + MOVW g_secret(g), R20 + CBZ R20, nosecret + BL ·secretEraseRegisters(SB) + +nosecret: +#endif MOVD RSP, R20 // R20 is unchanged by C code MOVD RSP, R1 @@ -309,6 +316,13 @@ finish: RET TEXT runtime·nanotime1(SB),NOSPLIT,$24-8 +#ifdef GOEXPERIMENT_runtimesecret + MOVW g_secret(g), R20 + CBZ R20, nosecret + BL ·secretEraseRegisters(SB) + +nosecret: +#endif MOVD RSP, R20 // R20 is unchanged by C code MOVD RSP, R1 diff --git a/src/runtime/time_linux_amd64.s b/src/runtime/time_linux_amd64.s index fa9561b25b5..4935c6dec3d 100644 --- a/src/runtime/time_linux_amd64.s +++ b/src/runtime/time_linux_amd64.s @@ -12,6 +12,16 @@ // func now() (sec int64, nsec int32, mono int64) TEXT time·now(SB),NOSPLIT,$16-24 +#ifdef GOEXPERIMENT_runtimesecret + // The kernel might spill our secrets onto g0 + // erase our registers here. + CMPL g_secret(R14), $0 + JEQ nosecret + CALL ·secretEraseRegisters(SB) + +nosecret: +#endif + MOVQ SP, R12 // Save old SP; R12 unchanged by C code. MOVQ g_m(R14), BX // BX unchanged by C code. diff --git a/src/runtime/vgetrandom_linux.go b/src/runtime/vgetrandom_linux.go index 225f7029be1..5e755dcc3d5 100644 --- a/src/runtime/vgetrandom_linux.go +++ b/src/runtime/vgetrandom_linux.go @@ -8,6 +8,7 @@ package runtime import ( "internal/cpu" + "internal/goexperiment" "unsafe" ) @@ -95,6 +96,13 @@ func vgetrandom(p []byte, flags uint32) (ret int, supported bool) { return -1, false } + // vDSO code may spill registers to the stack + // Make sure they're zeroed if we're running in secret mode + gp := getg() + if goexperiment.RuntimeSecret && gp.secret > 0 { + secretEraseRegisters() + } + // We use getg().m instead of acquirem() here, because always taking // the lock is slightly more expensive than not always taking the lock. // However, we *do* require that m doesn't migrate elsewhere during the diff --git a/src/syscall/asm_linux_amd64.s b/src/syscall/asm_linux_amd64.s index da170c52ed9..cf2f823855e 100644 --- a/src/syscall/asm_linux_amd64.s +++ b/src/syscall/asm_linux_amd64.s @@ -47,6 +47,10 @@ TEXT ·rawSyscallNoError(SB),NOSPLIT,$0-48 // func gettimeofday(tv *Timeval) (err uintptr) TEXT ·gettimeofday(SB),NOSPLIT,$0-16 + // Usually, we'd check if we're running + // secret code here, but because we execute + // gettimeofday on the G stack, it's fine to leave + // the registers uncleared MOVQ tv+0(FP), DI MOVQ $0, SI MOVQ runtime·vdsoGettimeofdaySym(SB), AX From 21ebed0ac0a3f733811bea2355ed85d3b1bf6fbd Mon Sep 17 00:00:00 2001 From: matloob Date: Wed, 26 Nov 2025 18:04:22 -0500 Subject: [PATCH 080/140] runtime: update mkmalloc to make generated code look nicer This cl adds a new operation that can remove an if statement or replace it with its body if its condition is var or !var for some variable var that's being replaced with a constant. Change-Id: I864abf1f023b2a66b2299ca65d4f837d6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/724940 LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Knyszek Reviewed-by: Michael Matloob Auto-Submit: Michael Matloob --- src/runtime/_mkmalloc/mkmalloc.go | 57 ++- src/runtime/malloc_generated.go | 607 ++++++------------------------ src/runtime/malloc_stubs.go | 32 +- 3 files changed, 175 insertions(+), 521 deletions(-) diff --git a/src/runtime/_mkmalloc/mkmalloc.go b/src/runtime/_mkmalloc/mkmalloc.go index 46c50d66611..8032983da8c 100644 --- a/src/runtime/_mkmalloc/mkmalloc.go +++ b/src/runtime/_mkmalloc/mkmalloc.go @@ -107,6 +107,7 @@ type replacementKind int const ( inlineFunc = replacementKind(iota) subBasicLit + foldCondition ) // op is a single inlining operation for the inliner. Any calls to the function @@ -171,7 +172,7 @@ func specializedMallocConfig(classes []class, sizeToSizeClass []uint8) generator {subBasicLit, "elemsize_", str(elemsize)}, {subBasicLit, "sizeclass_", str(sc)}, {subBasicLit, "noscanint_", str(noscan)}, - {subBasicLit, "isTiny_", str(0)}, + {foldCondition, "isTiny_", str(false)}, }, }) } @@ -199,7 +200,7 @@ func specializedMallocConfig(classes []class, sizeToSizeClass []uint8) generator {subBasicLit, "sizeclass_", str(tinySizeClass)}, {subBasicLit, "size_", str(s)}, {subBasicLit, "noscanint_", str(noscan)}, - {subBasicLit, "isTiny_", str(1)}, + {foldCondition, "isTiny_", str(true)}, }, }) } @@ -217,7 +218,7 @@ func specializedMallocConfig(classes []class, sizeToSizeClass []uint8) generator {subBasicLit, "elemsize_", str(elemsize)}, {subBasicLit, "sizeclass_", str(sc)}, {subBasicLit, "noscanint_", str(noscan)}, - {subBasicLit, "isTiny_", str(0)}, + {foldCondition, "isTiny_", str(false)}, }, }) } @@ -277,10 +278,17 @@ func inline(config generatorConfig) []byte { // Apply each of the ops given by the specs stamped := ast.Node(containingFuncCopy) for _, repl := range spec.ops { - if toDecl, ok := funcDecls[repl.to]; ok { - stamped = inlineFunction(stamped, repl.from, toDecl) - } else { + switch repl.kind { + case inlineFunc: + if toDecl, ok := funcDecls[repl.to]; ok { + stamped = inlineFunction(stamped, repl.from, toDecl) + } + case subBasicLit: stamped = substituteWithBasicLit(stamped, repl.from, repl.to) + case foldCondition: + stamped = foldIfCondition(stamped, repl.from, repl.to) + default: + log.Fatal("unknown op kind %v", repl.kind) } } @@ -310,6 +318,43 @@ func substituteWithBasicLit(node ast.Node, from, to string) ast.Node { }, nil) } +// foldIfCondition looks for if statements with a single boolean variable from, or +// the negation of from and either replaces it with its body or nothing, +// depending on whether the to value is true or false. +func foldIfCondition(node ast.Node, from, to string) ast.Node { + var isTrue bool + switch to { + case "true": + isTrue = true + case "false": + isTrue = false + default: + log.Fatalf("op 'to' expr %q is not true or false", to) + } + return astutil.Apply(node, func(cursor *astutil.Cursor) bool { + var foldIfTrue bool + ifexpr, ok := cursor.Node().(*ast.IfStmt) + if !ok { + return true + } + if isIdentWithName(ifexpr.Cond, from) { + foldIfTrue = true + } else if unaryexpr, ok := ifexpr.Cond.(*ast.UnaryExpr); ok && unaryexpr.Op == token.NOT && isIdentWithName(unaryexpr.X, from) { + foldIfTrue = false + } else { + // not an if with from or !from. + return true + } + if foldIfTrue == isTrue { + for _, stmt := range ifexpr.Body.List { + cursor.InsertBefore(stmt) + } + } + cursor.Delete() + return true + }, nil) +} + // inlineFunction recursively replaces calls to the function 'from' with the body of the function // 'toDecl'. All calls to 'from' must appear in assignment statements. // The replacement is very simple: it doesn't substitute the arguments for the parameters, so the diff --git a/src/runtime/malloc_generated.go b/src/runtime/malloc_generated.go index 6864ca05d31..cf329d26969 100644 --- a/src/runtime/malloc_generated.go +++ b/src/runtime/malloc_generated.go @@ -12,12 +12,6 @@ import ( func mallocgcSmallScanNoHeaderSC1(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -159,6 +153,7 @@ func mallocgcSmallScanNoHeaderSC1(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -182,12 +177,6 @@ func mallocgcSmallScanNoHeaderSC1(size uintptr, typ *_type, needzero bool) unsaf func mallocgcSmallScanNoHeaderSC2(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -329,6 +318,7 @@ func mallocgcSmallScanNoHeaderSC2(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -352,12 +342,6 @@ func mallocgcSmallScanNoHeaderSC2(size uintptr, typ *_type, needzero bool) unsaf func mallocgcSmallScanNoHeaderSC3(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -499,6 +483,7 @@ func mallocgcSmallScanNoHeaderSC3(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -522,12 +507,6 @@ func mallocgcSmallScanNoHeaderSC3(size uintptr, typ *_type, needzero bool) unsaf func mallocgcSmallScanNoHeaderSC4(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -669,6 +648,7 @@ func mallocgcSmallScanNoHeaderSC4(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -692,12 +672,6 @@ func mallocgcSmallScanNoHeaderSC4(size uintptr, typ *_type, needzero bool) unsaf func mallocgcSmallScanNoHeaderSC5(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -839,6 +813,7 @@ func mallocgcSmallScanNoHeaderSC5(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -862,12 +837,6 @@ func mallocgcSmallScanNoHeaderSC5(size uintptr, typ *_type, needzero bool) unsaf func mallocgcSmallScanNoHeaderSC6(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -1009,6 +978,7 @@ func mallocgcSmallScanNoHeaderSC6(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -1032,12 +1002,6 @@ func mallocgcSmallScanNoHeaderSC6(size uintptr, typ *_type, needzero bool) unsaf func mallocgcSmallScanNoHeaderSC7(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -1179,6 +1143,7 @@ func mallocgcSmallScanNoHeaderSC7(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -1202,12 +1167,6 @@ func mallocgcSmallScanNoHeaderSC7(size uintptr, typ *_type, needzero bool) unsaf func mallocgcSmallScanNoHeaderSC8(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -1349,6 +1308,7 @@ func mallocgcSmallScanNoHeaderSC8(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -1372,12 +1332,6 @@ func mallocgcSmallScanNoHeaderSC8(size uintptr, typ *_type, needzero bool) unsaf func mallocgcSmallScanNoHeaderSC9(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -1519,6 +1473,7 @@ func mallocgcSmallScanNoHeaderSC9(size uintptr, typ *_type, needzero bool) unsaf gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -1542,12 +1497,6 @@ func mallocgcSmallScanNoHeaderSC9(size uintptr, typ *_type, needzero bool) unsaf func mallocgcSmallScanNoHeaderSC10(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -1689,6 +1638,7 @@ func mallocgcSmallScanNoHeaderSC10(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -1712,12 +1662,6 @@ func mallocgcSmallScanNoHeaderSC10(size uintptr, typ *_type, needzero bool) unsa func mallocgcSmallScanNoHeaderSC11(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -1859,6 +1803,7 @@ func mallocgcSmallScanNoHeaderSC11(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -1882,12 +1827,6 @@ func mallocgcSmallScanNoHeaderSC11(size uintptr, typ *_type, needzero bool) unsa func mallocgcSmallScanNoHeaderSC12(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -2029,6 +1968,7 @@ func mallocgcSmallScanNoHeaderSC12(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -2052,12 +1992,6 @@ func mallocgcSmallScanNoHeaderSC12(size uintptr, typ *_type, needzero bool) unsa func mallocgcSmallScanNoHeaderSC13(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -2199,6 +2133,7 @@ func mallocgcSmallScanNoHeaderSC13(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -2222,12 +2157,6 @@ func mallocgcSmallScanNoHeaderSC13(size uintptr, typ *_type, needzero bool) unsa func mallocgcSmallScanNoHeaderSC14(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -2369,6 +2298,7 @@ func mallocgcSmallScanNoHeaderSC14(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -2392,12 +2322,6 @@ func mallocgcSmallScanNoHeaderSC14(size uintptr, typ *_type, needzero bool) unsa func mallocgcSmallScanNoHeaderSC15(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -2539,6 +2463,7 @@ func mallocgcSmallScanNoHeaderSC15(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -2562,12 +2487,6 @@ func mallocgcSmallScanNoHeaderSC15(size uintptr, typ *_type, needzero bool) unsa func mallocgcSmallScanNoHeaderSC16(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -2709,6 +2628,7 @@ func mallocgcSmallScanNoHeaderSC16(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -2732,12 +2652,6 @@ func mallocgcSmallScanNoHeaderSC16(size uintptr, typ *_type, needzero bool) unsa func mallocgcSmallScanNoHeaderSC17(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -2879,6 +2793,7 @@ func mallocgcSmallScanNoHeaderSC17(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -2902,12 +2817,6 @@ func mallocgcSmallScanNoHeaderSC17(size uintptr, typ *_type, needzero bool) unsa func mallocgcSmallScanNoHeaderSC18(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -3049,6 +2958,7 @@ func mallocgcSmallScanNoHeaderSC18(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -3072,12 +2982,6 @@ func mallocgcSmallScanNoHeaderSC18(size uintptr, typ *_type, needzero bool) unsa func mallocgcSmallScanNoHeaderSC19(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -3219,6 +3123,7 @@ func mallocgcSmallScanNoHeaderSC19(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -3242,12 +3147,6 @@ func mallocgcSmallScanNoHeaderSC19(size uintptr, typ *_type, needzero bool) unsa func mallocgcSmallScanNoHeaderSC20(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -3389,6 +3288,7 @@ func mallocgcSmallScanNoHeaderSC20(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -3412,12 +3312,6 @@ func mallocgcSmallScanNoHeaderSC20(size uintptr, typ *_type, needzero bool) unsa func mallocgcSmallScanNoHeaderSC21(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -3559,6 +3453,7 @@ func mallocgcSmallScanNoHeaderSC21(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -3582,12 +3477,6 @@ func mallocgcSmallScanNoHeaderSC21(size uintptr, typ *_type, needzero bool) unsa func mallocgcSmallScanNoHeaderSC22(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -3729,6 +3618,7 @@ func mallocgcSmallScanNoHeaderSC22(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -3752,12 +3642,6 @@ func mallocgcSmallScanNoHeaderSC22(size uintptr, typ *_type, needzero bool) unsa func mallocgcSmallScanNoHeaderSC23(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -3899,6 +3783,7 @@ func mallocgcSmallScanNoHeaderSC23(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -3922,12 +3807,6 @@ func mallocgcSmallScanNoHeaderSC23(size uintptr, typ *_type, needzero bool) unsa func mallocgcSmallScanNoHeaderSC24(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -4069,6 +3948,7 @@ func mallocgcSmallScanNoHeaderSC24(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -4092,12 +3972,6 @@ func mallocgcSmallScanNoHeaderSC24(size uintptr, typ *_type, needzero bool) unsa func mallocgcSmallScanNoHeaderSC25(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -4239,6 +4113,7 @@ func mallocgcSmallScanNoHeaderSC25(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -4262,12 +4137,6 @@ func mallocgcSmallScanNoHeaderSC25(size uintptr, typ *_type, needzero bool) unsa func mallocgcSmallScanNoHeaderSC26(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -4409,6 +4278,7 @@ func mallocgcSmallScanNoHeaderSC26(size uintptr, typ *_type, needzero bool) unsa gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -4432,12 +4302,11 @@ func mallocgcSmallScanNoHeaderSC26(size uintptr, typ *_type, needzero bool) unsa func mallocTiny1(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 1 == - 1 gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { return mallocgcSmallNoScanSC2(size, typ, needzero) } + if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -4489,11 +4358,6 @@ func mallocTiny1(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -4576,11 +4440,6 @@ func mallocTiny1(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -4599,12 +4458,11 @@ func mallocTiny1(size uintptr, typ *_type, needzero bool) unsafe.Pointer { func mallocTiny2(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 1 == - 1 gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { return mallocgcSmallNoScanSC2(size, typ, needzero) } + if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -4656,11 +4514,6 @@ func mallocTiny2(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -4743,11 +4596,6 @@ func mallocTiny2(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -4766,12 +4614,11 @@ func mallocTiny2(size uintptr, typ *_type, needzero bool) unsafe.Pointer { func mallocTiny3(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 1 == - 1 gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { return mallocgcSmallNoScanSC2(size, typ, needzero) } + if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -4823,11 +4670,6 @@ func mallocTiny3(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -4910,11 +4752,6 @@ func mallocTiny3(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -4933,12 +4770,11 @@ func mallocTiny3(size uintptr, typ *_type, needzero bool) unsafe.Pointer { func mallocTiny4(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 1 == - 1 gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { return mallocgcSmallNoScanSC2(size, typ, needzero) } + if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -4990,11 +4826,6 @@ func mallocTiny4(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -5077,11 +4908,6 @@ func mallocTiny4(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -5100,12 +4926,11 @@ func mallocTiny4(size uintptr, typ *_type, needzero bool) unsafe.Pointer { func mallocTiny5(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 1 == - 1 gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { return mallocgcSmallNoScanSC2(size, typ, needzero) } + if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -5157,11 +4982,6 @@ func mallocTiny5(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -5244,11 +5064,6 @@ func mallocTiny5(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -5267,12 +5082,11 @@ func mallocTiny5(size uintptr, typ *_type, needzero bool) unsafe.Pointer { func mallocTiny6(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 1 == - 1 gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { return mallocgcSmallNoScanSC2(size, typ, needzero) } + if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -5324,11 +5138,6 @@ func mallocTiny6(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -5411,11 +5220,6 @@ func mallocTiny6(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -5434,12 +5238,11 @@ func mallocTiny6(size uintptr, typ *_type, needzero bool) unsafe.Pointer { func mallocTiny7(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 1 == - 1 gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { return mallocgcSmallNoScanSC2(size, typ, needzero) } + if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -5491,11 +5294,6 @@ func mallocTiny7(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -5578,11 +5376,6 @@ func mallocTiny7(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -5601,12 +5394,11 @@ func mallocTiny7(size uintptr, typ *_type, needzero bool) unsafe.Pointer { func mallocTiny8(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 1 == - 1 gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { return mallocgcSmallNoScanSC2(size, typ, needzero) } + if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -5658,11 +5450,6 @@ func mallocTiny8(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -5745,11 +5532,6 @@ func mallocTiny8(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -5768,12 +5550,11 @@ func mallocTiny8(size uintptr, typ *_type, needzero bool) unsafe.Pointer { func mallocTiny9(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 1 == - 1 gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { return mallocgcSmallNoScanSC2(size, typ, needzero) } + if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -5825,11 +5606,6 @@ func mallocTiny9(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -5912,11 +5688,6 @@ func mallocTiny9(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -5935,12 +5706,11 @@ func mallocTiny9(size uintptr, typ *_type, needzero bool) unsafe.Pointer { func mallocTiny10(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 1 == - 1 gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { return mallocgcSmallNoScanSC2(size, typ, needzero) } + if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -5992,11 +5762,6 @@ func mallocTiny10(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -6079,11 +5844,6 @@ func mallocTiny10(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -6102,12 +5862,11 @@ func mallocTiny10(size uintptr, typ *_type, needzero bool) unsafe.Pointer { func mallocTiny11(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 1 == - 1 gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { return mallocgcSmallNoScanSC2(size, typ, needzero) } + if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -6159,11 +5918,6 @@ func mallocTiny11(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -6246,11 +6000,6 @@ func mallocTiny11(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -6269,12 +6018,11 @@ func mallocTiny11(size uintptr, typ *_type, needzero bool) unsafe.Pointer { func mallocTiny12(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 1 == - 1 gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { return mallocgcSmallNoScanSC2(size, typ, needzero) } + if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -6326,11 +6074,6 @@ func mallocTiny12(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -6413,11 +6156,6 @@ func mallocTiny12(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -6436,12 +6174,11 @@ func mallocTiny12(size uintptr, typ *_type, needzero bool) unsafe.Pointer { func mallocTiny13(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 1 == - 1 gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { return mallocgcSmallNoScanSC2(size, typ, needzero) } + if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -6493,11 +6230,6 @@ func mallocTiny13(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -6580,11 +6312,6 @@ func mallocTiny13(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -6603,12 +6330,11 @@ func mallocTiny13(size uintptr, typ *_type, needzero bool) unsafe.Pointer { func mallocTiny14(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 1 == - 1 gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { return mallocgcSmallNoScanSC2(size, typ, needzero) } + if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -6660,11 +6386,6 @@ func mallocTiny14(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -6747,11 +6468,6 @@ func mallocTiny14(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -6770,12 +6486,11 @@ func mallocTiny14(size uintptr, typ *_type, needzero bool) unsafe.Pointer { func mallocTiny15(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 1 == - 1 gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { + if goexperiment.RuntimeSecret && gp.secret > 0 { return mallocgcSmallNoScanSC2(size, typ, needzero) } + if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -6827,11 +6542,6 @@ func mallocTiny15(size uintptr, typ *_type, needzero bool) unsafe.Pointer { const elemsize = 0 { - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -6914,11 +6624,6 @@ func mallocTiny15(size uintptr, typ *_type, needzero bool) unsafe.Pointer { x = add(x, elemsize-constsize) } - if goexperiment.RuntimeSecret && gp.secret > 0 { - - addSecret(x) - } - if valgrindenabled { valgrindMalloc(x, size) } @@ -6937,12 +6642,6 @@ func mallocTiny15(size uintptr, typ *_type, needzero bool) unsafe.Pointer { func mallocgcSmallNoScanSC2(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -6984,6 +6683,7 @@ func mallocgcSmallNoScanSC2(size uintptr, typ *_type, needzero bool) unsafe.Poin x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -7054,6 +6754,7 @@ func mallocgcSmallNoScanSC2(size uintptr, typ *_type, needzero bool) unsafe.Poin gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -7077,12 +6778,6 @@ func mallocgcSmallNoScanSC2(size uintptr, typ *_type, needzero bool) unsafe.Poin func mallocgcSmallNoScanSC3(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -7124,6 +6819,7 @@ func mallocgcSmallNoScanSC3(size uintptr, typ *_type, needzero bool) unsafe.Poin x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -7194,6 +6890,7 @@ func mallocgcSmallNoScanSC3(size uintptr, typ *_type, needzero bool) unsafe.Poin gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -7217,12 +6914,6 @@ func mallocgcSmallNoScanSC3(size uintptr, typ *_type, needzero bool) unsafe.Poin func mallocgcSmallNoScanSC4(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -7264,6 +6955,7 @@ func mallocgcSmallNoScanSC4(size uintptr, typ *_type, needzero bool) unsafe.Poin x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -7334,6 +7026,7 @@ func mallocgcSmallNoScanSC4(size uintptr, typ *_type, needzero bool) unsafe.Poin gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -7357,12 +7050,6 @@ func mallocgcSmallNoScanSC4(size uintptr, typ *_type, needzero bool) unsafe.Poin func mallocgcSmallNoScanSC5(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -7404,6 +7091,7 @@ func mallocgcSmallNoScanSC5(size uintptr, typ *_type, needzero bool) unsafe.Poin x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -7474,6 +7162,7 @@ func mallocgcSmallNoScanSC5(size uintptr, typ *_type, needzero bool) unsafe.Poin gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -7497,12 +7186,6 @@ func mallocgcSmallNoScanSC5(size uintptr, typ *_type, needzero bool) unsafe.Poin func mallocgcSmallNoScanSC6(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -7544,6 +7227,7 @@ func mallocgcSmallNoScanSC6(size uintptr, typ *_type, needzero bool) unsafe.Poin x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -7614,6 +7298,7 @@ func mallocgcSmallNoScanSC6(size uintptr, typ *_type, needzero bool) unsafe.Poin gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -7637,12 +7322,6 @@ func mallocgcSmallNoScanSC6(size uintptr, typ *_type, needzero bool) unsafe.Poin func mallocgcSmallNoScanSC7(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -7684,6 +7363,7 @@ func mallocgcSmallNoScanSC7(size uintptr, typ *_type, needzero bool) unsafe.Poin x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -7754,6 +7434,7 @@ func mallocgcSmallNoScanSC7(size uintptr, typ *_type, needzero bool) unsafe.Poin gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -7777,12 +7458,6 @@ func mallocgcSmallNoScanSC7(size uintptr, typ *_type, needzero bool) unsafe.Poin func mallocgcSmallNoScanSC8(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -7824,6 +7499,7 @@ func mallocgcSmallNoScanSC8(size uintptr, typ *_type, needzero bool) unsafe.Poin x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -7894,6 +7570,7 @@ func mallocgcSmallNoScanSC8(size uintptr, typ *_type, needzero bool) unsafe.Poin gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -7917,12 +7594,6 @@ func mallocgcSmallNoScanSC8(size uintptr, typ *_type, needzero bool) unsafe.Poin func mallocgcSmallNoScanSC9(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -7964,6 +7635,7 @@ func mallocgcSmallNoScanSC9(size uintptr, typ *_type, needzero bool) unsafe.Poin x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -8034,6 +7706,7 @@ func mallocgcSmallNoScanSC9(size uintptr, typ *_type, needzero bool) unsafe.Poin gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -8057,12 +7730,6 @@ func mallocgcSmallNoScanSC9(size uintptr, typ *_type, needzero bool) unsafe.Poin func mallocgcSmallNoScanSC10(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -8104,6 +7771,7 @@ func mallocgcSmallNoScanSC10(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -8174,6 +7842,7 @@ func mallocgcSmallNoScanSC10(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -8197,12 +7866,6 @@ func mallocgcSmallNoScanSC10(size uintptr, typ *_type, needzero bool) unsafe.Poi func mallocgcSmallNoScanSC11(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -8244,6 +7907,7 @@ func mallocgcSmallNoScanSC11(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -8314,6 +7978,7 @@ func mallocgcSmallNoScanSC11(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -8337,12 +8002,6 @@ func mallocgcSmallNoScanSC11(size uintptr, typ *_type, needzero bool) unsafe.Poi func mallocgcSmallNoScanSC12(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -8384,6 +8043,7 @@ func mallocgcSmallNoScanSC12(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -8454,6 +8114,7 @@ func mallocgcSmallNoScanSC12(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -8477,12 +8138,6 @@ func mallocgcSmallNoScanSC12(size uintptr, typ *_type, needzero bool) unsafe.Poi func mallocgcSmallNoScanSC13(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -8524,6 +8179,7 @@ func mallocgcSmallNoScanSC13(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -8594,6 +8250,7 @@ func mallocgcSmallNoScanSC13(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -8617,12 +8274,6 @@ func mallocgcSmallNoScanSC13(size uintptr, typ *_type, needzero bool) unsafe.Poi func mallocgcSmallNoScanSC14(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -8664,6 +8315,7 @@ func mallocgcSmallNoScanSC14(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -8734,6 +8386,7 @@ func mallocgcSmallNoScanSC14(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -8757,12 +8410,6 @@ func mallocgcSmallNoScanSC14(size uintptr, typ *_type, needzero bool) unsafe.Poi func mallocgcSmallNoScanSC15(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -8804,6 +8451,7 @@ func mallocgcSmallNoScanSC15(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -8874,6 +8522,7 @@ func mallocgcSmallNoScanSC15(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -8897,12 +8546,6 @@ func mallocgcSmallNoScanSC15(size uintptr, typ *_type, needzero bool) unsafe.Poi func mallocgcSmallNoScanSC16(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -8944,6 +8587,7 @@ func mallocgcSmallNoScanSC16(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -9014,6 +8658,7 @@ func mallocgcSmallNoScanSC16(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -9037,12 +8682,6 @@ func mallocgcSmallNoScanSC16(size uintptr, typ *_type, needzero bool) unsafe.Poi func mallocgcSmallNoScanSC17(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -9084,6 +8723,7 @@ func mallocgcSmallNoScanSC17(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -9154,6 +8794,7 @@ func mallocgcSmallNoScanSC17(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -9177,12 +8818,6 @@ func mallocgcSmallNoScanSC17(size uintptr, typ *_type, needzero bool) unsafe.Poi func mallocgcSmallNoScanSC18(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -9224,6 +8859,7 @@ func mallocgcSmallNoScanSC18(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -9294,6 +8930,7 @@ func mallocgcSmallNoScanSC18(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -9317,12 +8954,6 @@ func mallocgcSmallNoScanSC18(size uintptr, typ *_type, needzero bool) unsafe.Poi func mallocgcSmallNoScanSC19(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -9364,6 +8995,7 @@ func mallocgcSmallNoScanSC19(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -9434,6 +9066,7 @@ func mallocgcSmallNoScanSC19(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -9457,12 +9090,6 @@ func mallocgcSmallNoScanSC19(size uintptr, typ *_type, needzero bool) unsafe.Poi func mallocgcSmallNoScanSC20(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -9504,6 +9131,7 @@ func mallocgcSmallNoScanSC20(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -9574,6 +9202,7 @@ func mallocgcSmallNoScanSC20(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -9597,12 +9226,6 @@ func mallocgcSmallNoScanSC20(size uintptr, typ *_type, needzero bool) unsafe.Poi func mallocgcSmallNoScanSC21(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -9644,6 +9267,7 @@ func mallocgcSmallNoScanSC21(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -9714,6 +9338,7 @@ func mallocgcSmallNoScanSC21(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -9737,12 +9362,6 @@ func mallocgcSmallNoScanSC21(size uintptr, typ *_type, needzero bool) unsafe.Poi func mallocgcSmallNoScanSC22(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -9784,6 +9403,7 @@ func mallocgcSmallNoScanSC22(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -9854,6 +9474,7 @@ func mallocgcSmallNoScanSC22(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -9877,12 +9498,6 @@ func mallocgcSmallNoScanSC22(size uintptr, typ *_type, needzero bool) unsafe.Poi func mallocgcSmallNoScanSC23(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -9924,6 +9539,7 @@ func mallocgcSmallNoScanSC23(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -9994,6 +9610,7 @@ func mallocgcSmallNoScanSC23(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -10017,12 +9634,6 @@ func mallocgcSmallNoScanSC23(size uintptr, typ *_type, needzero bool) unsafe.Poi func mallocgcSmallNoScanSC24(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -10064,6 +9675,7 @@ func mallocgcSmallNoScanSC24(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -10134,6 +9746,7 @@ func mallocgcSmallNoScanSC24(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -10157,12 +9770,6 @@ func mallocgcSmallNoScanSC24(size uintptr, typ *_type, needzero bool) unsafe.Poi func mallocgcSmallNoScanSC25(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -10204,6 +9811,7 @@ func mallocgcSmallNoScanSC25(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -10274,6 +9882,7 @@ func mallocgcSmallNoScanSC25(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -10297,12 +9906,6 @@ func mallocgcSmallNoScanSC25(size uintptr, typ *_type, needzero bool) unsafe.Poi func mallocgcSmallNoScanSC26(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - const isTiny = 0 == - 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) - } if doubleCheckMalloc { if gcphase == _GCmarktermination { throw("mallocgc called with gcphase == _GCmarktermination") @@ -10344,6 +9947,7 @@ func mallocgcSmallNoScanSC26(size uintptr, typ *_type, needzero bool) unsafe.Poi x := v { + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) @@ -10414,6 +10018,7 @@ func mallocgcSmallNoScanSC26(size uintptr, typ *_type, needzero bool) unsafe.Poi gcStart(t) } } + gp := getg() if goexperiment.RuntimeSecret && gp.secret > 0 { addSecret(x) diff --git a/src/runtime/malloc_stubs.go b/src/runtime/malloc_stubs.go index 58ca1d5f79f..8c424935bf2 100644 --- a/src/runtime/malloc_stubs.go +++ b/src/runtime/malloc_stubs.go @@ -37,7 +37,7 @@ const elemsize_ = 8 const sizeclass_ = 0 const noscanint_ = 0 const size_ = 0 -const isTiny_ = 0 +const isTiny_ = false func malloc0(size uintptr, typ *_type, needzero bool) unsafe.Pointer { if doubleCheckMalloc { @@ -58,15 +58,16 @@ func mallocPanic(size uintptr, typ *_type, needzero bool) unsafe.Pointer { // to steer out of this codepath early if sanitizers are enabled. func mallocStub(size uintptr, typ *_type, needzero bool) unsafe.Pointer { - // secret code, need to avoid the tiny allocator since it might keep - // co-located values alive longer and prevent timely zero-ing - // - // Call directly into the NoScan allocator. - // See go.dev/issue/76356 - const isTiny = isTiny_ == 1 - gp := getg() - if goexperiment.RuntimeSecret && isTiny && gp.secret > 0 { - return mallocgcSmallNoScanSC2(size, typ, needzero) + if isTiny_ { + // secret code, need to avoid the tiny allocator since it might keep + // co-located values alive longer and prevent timely zero-ing + // + // Call directly into the NoScan allocator. + // See go.dev/issue/76356 + gp := getg() + if goexperiment.RuntimeSecret && gp.secret > 0 { + return mallocgcSmallNoScanSC2(size, typ, needzero) + } } if doubleCheckMalloc { if gcphase == _GCmarktermination { @@ -95,10 +96,13 @@ func mallocStub(size uintptr, typ *_type, needzero bool) unsafe.Pointer { // Actually do the allocation. x, elemsize := inlinedMalloc(size, typ, needzero) - if goexperiment.RuntimeSecret && gp.secret > 0 { - // Mark any object allocated while in secret mode as secret. - // This ensures we zero it immediately when freeing it. - addSecret(x) + if !isTiny_ { + gp := getg() + if goexperiment.RuntimeSecret && gp.secret > 0 { + // Mark any object allocated while in secret mode as secret. + // This ensures we zero it immediately when freeing it. + addSecret(x) + } } // Notify valgrind, if enabled. From 2b8dbb35b0d6a5601ae9b6f1d1de106774251214 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Mon, 15 Sep 2025 18:58:04 +0200 Subject: [PATCH 081/140] crypto,testing/cryptotest: ignore random io.Reader params, add SetGlobalRandom First, we centralize all random bytes generation through drbg.Read. The rest of the FIPS 140-3 module can't use external functions anyway, so drbg.Read needs to have all the logic. Then, make sure that the crypto/... tree uses drbg.Read (or the new crypto/internal/rand.Reader wrapper) instead of crypto/rand, so it is unaffected by applications setting crypto/rand.Reader. Next, pass all unspecified random io.Reader parameters through the new crypto/internal/rand.CustomReader, which just redirects to drbg.Read unless GODEBUG=cryptocustomrand=1 is set. Move all the calls to MaybeReadByte there, since it's only needed for these custom Readers. Finally, add testing/cryptotest.SetGlobalRandom which sets crypto/rand.Reader to a locked deterministic source and overrides drbg.Read. This way SetGlobalRandom should affect all cryptographic randomness in the standard library. Fixes #70942 Co-authored-by: qiulaidongfeng <2645477756@qq.com> Change-Id: I6a6a69641311d9fac318abcc6d79677f0e406100 Reviewed-on: https://go-review.googlesource.com/c/go/+/724480 Reviewed-by: Nicholas Husin Auto-Submit: Filippo Valsorda Reviewed-by: Nicholas Husin Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI --- api/next/70942.txt | 1 + doc/godebug.md | 5 + .../6-stdlib/99-minor/crypto/dsa/70924.md | 4 + .../6-stdlib/99-minor/crypto/ecdh/70924.md | 4 + .../6-stdlib/99-minor/crypto/ecdsa/70924.md | 4 + .../6-stdlib/99-minor/crypto/ed25519/70924.md | 4 + .../6-stdlib/99-minor/crypto/rand/70924.md | 4 + .../6-stdlib/99-minor/crypto/rsa/70924.md | 4 + .../99-minor/testing/cryptotest/70942.md | 4 + src/crypto/dsa/dsa.go | 12 +- src/crypto/ecdh/ecdh.go | 6 +- src/crypto/ecdh/nist.go | 11 +- src/crypto/ecdh/x25519.go | 8 +- src/crypto/ecdsa/ecdsa.go | 69 +++--- src/crypto/ecdsa/ecdsa_legacy.go | 5 + src/crypto/ed25519/ed25519.go | 30 ++- src/crypto/hpke/kem.go | 2 +- src/crypto/hpke/pq.go | 5 +- src/crypto/internal/fips140/drbg/rand.go | 41 ++-- src/crypto/internal/fips140/rsa/pkcs1v22.go | 10 +- src/crypto/internal/rand/rand.go | 65 ++++++ src/crypto/internal/rand/rand_fipsv1.0.go | 13 ++ src/crypto/internal/rand/rand_fipsv2.0.go | 16 ++ src/crypto/internal/sysrand/rand.go | 3 + src/crypto/mlkem/mlkem.go | 8 +- src/crypto/rand/rand.go | 28 +-- src/crypto/rand/util.go | 12 +- src/crypto/rsa/pkcs1v15.go | 17 +- src/crypto/rsa/rsa.go | 20 +- src/crypto/tls/handshake_test.go | 4 + src/go/build/deps_test.go | 4 + src/internal/godebugs/table.go | 1 + src/runtime/metrics/doc.go | 5 + src/testing/cryptotest/rand.go | 76 +++++++ src/testing/cryptotest/rand_test.go | 202 ++++++++++++++++++ src/testing/testing.go | 9 +- 36 files changed, 591 insertions(+), 125 deletions(-) create mode 100644 api/next/70942.txt create mode 100644 doc/next/6-stdlib/99-minor/crypto/dsa/70924.md create mode 100644 doc/next/6-stdlib/99-minor/crypto/ecdh/70924.md create mode 100644 doc/next/6-stdlib/99-minor/crypto/ecdsa/70924.md create mode 100644 doc/next/6-stdlib/99-minor/crypto/ed25519/70924.md create mode 100644 doc/next/6-stdlib/99-minor/crypto/rand/70924.md create mode 100644 doc/next/6-stdlib/99-minor/crypto/rsa/70924.md create mode 100644 doc/next/6-stdlib/99-minor/testing/cryptotest/70942.md create mode 100644 src/crypto/internal/rand/rand.go create mode 100644 src/crypto/internal/rand/rand_fipsv1.0.go create mode 100644 src/crypto/internal/rand/rand_fipsv2.0.go create mode 100644 src/testing/cryptotest/rand.go create mode 100644 src/testing/cryptotest/rand_test.go diff --git a/api/next/70942.txt b/api/next/70942.txt new file mode 100644 index 00000000000..ac212d9c299 --- /dev/null +++ b/api/next/70942.txt @@ -0,0 +1 @@ +pkg testing/cryptotest, func SetGlobalRandom(*testing.T, uint64) #70942 diff --git a/doc/godebug.md b/doc/godebug.md index 0d3354bc0fd..d6bb18603c3 100644 --- a/doc/godebug.md +++ b/doc/godebug.md @@ -178,6 +178,11 @@ includes these key/value pairs in the goroutine status header of runtime tracebacks and debug=2 runtime/pprof stack dumps. This format may change in the future. (see go.dev/issue/76349) +Go 1.26 added a new `cryptocustomrand` setting that controls whether most crypto/... +APIs ignore the random `io.Reader` parameter. For Go 1.26, it defaults +to `cryptocustomrand=0`, ignoring the random parameters. Using `cryptocustomrand=1` +reverts to the pre-Go 1.26 behavior. + ### Go 1.25 Go 1.25 added a new `decoratemappings` setting that controls whether the Go diff --git a/doc/next/6-stdlib/99-minor/crypto/dsa/70924.md b/doc/next/6-stdlib/99-minor/crypto/dsa/70924.md new file mode 100644 index 00000000000..0d99de895ff --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/dsa/70924.md @@ -0,0 +1,4 @@ +The random parameter to [GenerateKey] is now ignored. +Instead, it now always uses a secure source of cryptographically random bytes. +For deterministic testing, use the new [testing/cryptotest.SetGlobalRandom] function. +The new GODEBUG setting `cryptocustomrand=1` temporarily restores the old behavior. diff --git a/doc/next/6-stdlib/99-minor/crypto/ecdh/70924.md b/doc/next/6-stdlib/99-minor/crypto/ecdh/70924.md new file mode 100644 index 00000000000..e70325ca7a1 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/ecdh/70924.md @@ -0,0 +1,4 @@ +The random parameter to [Curve.GenerateKey] is now ignored. +Instead, it now always uses a secure source of cryptographically random bytes. +For deterministic testing, use the new [testing/cryptotest.SetGlobalRandom] function. +The new GODEBUG setting `cryptocustomrand=1` temporarily restores the old behavior. diff --git a/doc/next/6-stdlib/99-minor/crypto/ecdsa/70924.md b/doc/next/6-stdlib/99-minor/crypto/ecdsa/70924.md new file mode 100644 index 00000000000..15344cbf227 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/ecdsa/70924.md @@ -0,0 +1,4 @@ +The random parameter to [GenerateKey], [SignASN1], [Sign], and [PrivateKey.Sign] is now ignored. +Instead, they now always use a secure source of cryptographically random bytes. +For deterministic testing, use the new [testing/cryptotest.SetGlobalRandom] function. +The new GODEBUG setting `cryptocustomrand=1` temporarily restores the old behavior. diff --git a/doc/next/6-stdlib/99-minor/crypto/ed25519/70924.md b/doc/next/6-stdlib/99-minor/crypto/ed25519/70924.md new file mode 100644 index 00000000000..885e425473e --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/ed25519/70924.md @@ -0,0 +1,4 @@ +If the random parameter to [GenerateKey] is nil, GenerateKey now always uses a +secure source of cryptographically random bytes, instead of [crypto/rand.Reader] +(which could have been overridden). The new GODEBUG setting `cryptocustomrand=1` +temporarily restores the old behavior. diff --git a/doc/next/6-stdlib/99-minor/crypto/rand/70924.md b/doc/next/6-stdlib/99-minor/crypto/rand/70924.md new file mode 100644 index 00000000000..dfdbaa3a92f --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/rand/70924.md @@ -0,0 +1,4 @@ +The random parameter to [Prime] is now ignored. +Instead, it now always uses a secure source of cryptographically random bytes. +For deterministic testing, use the new [testing/cryptotest.SetGlobalRandom] function. +The new GODEBUG setting `cryptocustomrand=1` temporarily restores the old behavior. diff --git a/doc/next/6-stdlib/99-minor/crypto/rsa/70924.md b/doc/next/6-stdlib/99-minor/crypto/rsa/70924.md new file mode 100644 index 00000000000..195e3ef11d9 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/rsa/70924.md @@ -0,0 +1,4 @@ +The random parameter to [GenerateKey], [GenerateMultiPrimeKey], and [EncryptPKCS1v15] is now ignored. +Instead, they now always use a secure source of cryptographically random bytes. +For deterministic testing, use the new [testing/cryptotest.SetGlobalRandom] function. +The new GODEBUG setting `cryptocustomrand=1` temporarily restores the old behavior. diff --git a/doc/next/6-stdlib/99-minor/testing/cryptotest/70942.md b/doc/next/6-stdlib/99-minor/testing/cryptotest/70942.md new file mode 100644 index 00000000000..b8d59130946 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/testing/cryptotest/70942.md @@ -0,0 +1,4 @@ +The new [SetGlobalRandom] function configures a global, deterministic +cryptographic randomness source for the duration of the test. It affects +crypto/rand, and all implicit sources of cryptographic randomness in the +`crypto/...` packages. diff --git a/src/crypto/dsa/dsa.go b/src/crypto/dsa/dsa.go index ecc4c82bb52..6724f861b7f 100644 --- a/src/crypto/dsa/dsa.go +++ b/src/crypto/dsa/dsa.go @@ -19,7 +19,7 @@ import ( "math/big" "crypto/internal/fips140only" - "crypto/internal/randutil" + "crypto/internal/rand" ) // Parameters represents the domain parameters for a key. These parameters can @@ -209,14 +209,18 @@ func fermatInverse(k, P *big.Int) *big.Int { // to the byte-length of the subgroup. This function does not perform that // truncation itself. // +// Since Go 1.26, a secure source of random bytes is always used, and the Reader is +// ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed +// in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom]. +// // Be aware that calling Sign with an attacker-controlled [PrivateKey] may // require an arbitrary amount of CPU. -func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) { +func Sign(random io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) { if fips140only.Enforced() { return nil, nil, errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode") } - randutil.MaybeReadByte(rand) + random = rand.CustomReader(random) // FIPS 186-3, section 4.6 @@ -232,7 +236,7 @@ func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err err k := new(big.Int) buf := make([]byte, n) for { - _, err = io.ReadFull(rand, buf) + _, err = io.ReadFull(random, buf) if err != nil { return } diff --git a/src/crypto/ecdh/ecdh.go b/src/crypto/ecdh/ecdh.go index 82daacf4736..3f85a283369 100644 --- a/src/crypto/ecdh/ecdh.go +++ b/src/crypto/ecdh/ecdh.go @@ -18,9 +18,9 @@ import ( type Curve interface { // GenerateKey generates a random PrivateKey. // - // Most applications should use [crypto/rand.Reader] as rand. Note that the - // returned key does not depend deterministically on the bytes read from rand, - // and may change between calls and/or between versions. + // Since Go 1.26, a secure source of random bytes is always used, and rand + // is ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be + // removed in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom]. GenerateKey(rand io.Reader) (*PrivateKey, error) // NewPrivateKey checks that key is valid and returns a PrivateKey. diff --git a/src/crypto/ecdh/nist.go b/src/crypto/ecdh/nist.go index 0d58196842a..de7348d9232 100644 --- a/src/crypto/ecdh/nist.go +++ b/src/crypto/ecdh/nist.go @@ -9,6 +9,7 @@ import ( "crypto/internal/boring" "crypto/internal/fips140/ecdh" "crypto/internal/fips140only" + "crypto/internal/rand" "errors" "io" ) @@ -25,8 +26,8 @@ func (c *nistCurve) String() string { return c.name } -func (c *nistCurve) GenerateKey(rand io.Reader) (*PrivateKey, error) { - if boring.Enabled && rand == boring.RandReader { +func (c *nistCurve) GenerateKey(r io.Reader) (*PrivateKey, error) { + if boring.Enabled && r == boring.RandReader { key, bytes, err := boring.GenerateKeyECDH(c.name) if err != nil { return nil, err @@ -44,11 +45,13 @@ func (c *nistCurve) GenerateKey(rand io.Reader) (*PrivateKey, error) { return k, nil } - if fips140only.Enforced() && !fips140only.ApprovedRandomReader(rand) { + r = rand.CustomReader(r) + + if fips140only.Enforced() && !fips140only.ApprovedRandomReader(r) { return nil, errors.New("crypto/ecdh: only crypto/rand.Reader is allowed in FIPS 140-only mode") } - privateKey, err := c.generate(rand) + privateKey, err := c.generate(r) if err != nil { return nil, err } diff --git a/src/crypto/ecdh/x25519.go b/src/crypto/ecdh/x25519.go index 3ad13f3e73a..21a921aa12d 100644 --- a/src/crypto/ecdh/x25519.go +++ b/src/crypto/ecdh/x25519.go @@ -8,7 +8,7 @@ import ( "bytes" "crypto/internal/fips140/edwards25519/field" "crypto/internal/fips140only" - "crypto/internal/randutil" + "crypto/internal/rand" "errors" "io" ) @@ -34,13 +34,13 @@ func (c *x25519Curve) String() string { return "X25519" } -func (c *x25519Curve) GenerateKey(rand io.Reader) (*PrivateKey, error) { +func (c *x25519Curve) GenerateKey(r io.Reader) (*PrivateKey, error) { if fips140only.Enforced() { return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode") } + r = rand.CustomReader(r) key := make([]byte, x25519PrivateKeySize) - randutil.MaybeReadByte(rand) - if _, err := io.ReadFull(rand, key); err != nil { + if _, err := io.ReadFull(r, key); err != nil { return nil, err } return c.NewPrivateKey(key) diff --git a/src/crypto/ecdsa/ecdsa.go b/src/crypto/ecdsa/ecdsa.go index 9d965c4e7b8..aee15b92838 100644 --- a/src/crypto/ecdsa/ecdsa.go +++ b/src/crypto/ecdsa/ecdsa.go @@ -27,7 +27,7 @@ import ( "crypto/internal/fips140cache" "crypto/internal/fips140hash" "crypto/internal/fips140only" - "crypto/internal/randutil" + "crypto/internal/rand" "crypto/sha512" "crypto/subtle" "errors" @@ -310,31 +310,31 @@ func privateKeyBytes[P ecdsa.Point[P]](c *ecdsa.Curve[P], priv *PrivateKey) ([]b // the bit-length of the private key's curve order, the hash will be truncated // to that length. It returns the ASN.1 encoded signature, like [SignASN1]. // -// If rand is not nil, the signature is randomized. Most applications should use -// [crypto/rand.Reader] as rand. Note that the returned signature does not -// depend deterministically on the bytes read from rand, and may change between -// calls and/or between versions. +// If random is not nil, the signature is randomized. Most applications should use +// [crypto/rand.Reader] as random, but unless GODEBUG=cryptocustomrand=1 is set, a +// secure source of random bytes is always used, and the actual Reader is ignored. +// The GODEBUG setting will be removed in a future Go release. Instead, use +// [testing/cryptotest.SetGlobalRandom]. // -// If rand is nil, Sign will produce a deterministic signature according to RFC +// If random is nil, Sign will produce a deterministic signature according to RFC // 6979. When producing a deterministic signature, opts.HashFunc() must be the // function used to produce digest and priv.Curve must be one of // [elliptic.P224], [elliptic.P256], [elliptic.P384], or [elliptic.P521]. -func (priv *PrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { - if rand == nil { +func (priv *PrivateKey) Sign(random io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { + if random == nil { return signRFC6979(priv, digest, opts) } - return SignASN1(rand, priv, digest) + random = rand.CustomReader(random) + return SignASN1(random, priv, digest) } // GenerateKey generates a new ECDSA private key for the specified curve. // -// Most applications should use [crypto/rand.Reader] as rand. Note that the -// returned key does not depend deterministically on the bytes read from rand, -// and may change between calls and/or between versions. -func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) { - randutil.MaybeReadByte(rand) - - if boring.Enabled && rand == boring.RandReader { +// Since Go 1.26, a secure source of random bytes is always used, and the Reader is +// ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed +// in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom]. +func GenerateKey(c elliptic.Curve, r io.Reader) (*PrivateKey, error) { + if boring.Enabled && r == boring.RandReader { x, y, d, err := boring.GenerateKeyECDSA(c.Params().Name) if err != nil { return nil, err @@ -343,17 +343,19 @@ func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) { } boring.UnreachableExceptTests() + r = rand.CustomReader(r) + switch c.Params() { case elliptic.P224().Params(): - return generateFIPS(c, ecdsa.P224(), rand) + return generateFIPS(c, ecdsa.P224(), r) case elliptic.P256().Params(): - return generateFIPS(c, ecdsa.P256(), rand) + return generateFIPS(c, ecdsa.P256(), r) case elliptic.P384().Params(): - return generateFIPS(c, ecdsa.P384(), rand) + return generateFIPS(c, ecdsa.P384(), r) case elliptic.P521().Params(): - return generateFIPS(c, ecdsa.P521(), rand) + return generateFIPS(c, ecdsa.P521(), r) default: - return generateLegacy(c, rand) + return generateLegacy(c, r) } } @@ -373,13 +375,12 @@ func generateFIPS[P ecdsa.Point[P]](curve elliptic.Curve, c *ecdsa.Curve[P], ran // private key's curve order, the hash will be truncated to that length. It // returns the ASN.1 encoded signature. // -// The signature is randomized. Most applications should use [crypto/rand.Reader] -// as rand. Note that the returned signature does not depend deterministically on -// the bytes read from rand, and may change between calls and/or between versions. -func SignASN1(rand io.Reader, priv *PrivateKey, hash []byte) ([]byte, error) { - randutil.MaybeReadByte(rand) - - if boring.Enabled && rand == boring.RandReader { +// The signature is randomized. Since Go 1.26, a secure source of random bytes +// is always used, and the Reader is ignored unless GODEBUG=cryptocustomrand=1 +// 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 boring.Enabled && r == boring.RandReader { b, err := boringPrivateKey(priv) if err != nil { return nil, err @@ -388,17 +389,19 @@ func SignASN1(rand io.Reader, priv *PrivateKey, hash []byte) ([]byte, error) { } boring.UnreachableExceptTests() + r = rand.CustomReader(r) + switch priv.Curve.Params() { case elliptic.P224().Params(): - return signFIPS(ecdsa.P224(), priv, rand, hash) + return signFIPS(ecdsa.P224(), priv, r, hash) case elliptic.P256().Params(): - return signFIPS(ecdsa.P256(), priv, rand, hash) + return signFIPS(ecdsa.P256(), priv, r, hash) case elliptic.P384().Params(): - return signFIPS(ecdsa.P384(), priv, rand, hash) + return signFIPS(ecdsa.P384(), priv, r, hash) case elliptic.P521().Params(): - return signFIPS(ecdsa.P521(), priv, rand, hash) + return signFIPS(ecdsa.P521(), priv, r, hash) default: - return signLegacy(priv, rand, hash) + return signLegacy(priv, r, hash) } } diff --git a/src/crypto/ecdsa/ecdsa_legacy.go b/src/crypto/ecdsa/ecdsa_legacy.go index f6b4401bd75..2fb1b21a605 100644 --- a/src/crypto/ecdsa/ecdsa_legacy.go +++ b/src/crypto/ecdsa/ecdsa_legacy.go @@ -61,6 +61,11 @@ var errZeroParam = errors.New("zero parameter") // private key's curve order, the hash will be truncated to that length. It // returns the signature as a pair of integers. Most applications should use // [SignASN1] instead of dealing directly with r, s. +// +// The signature is randomized. Since Go 1.26, a secure source of random bytes +// is always used, and the Reader is ignored unless GODEBUG=cryptocustomrand=1 +// is set. This setting will be removed in a future Go release. Instead, use +// [testing/cryptotest.SetGlobalRandom]. func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) { sig, err := SignASN1(rand, priv, hash) if err != nil { diff --git a/src/crypto/ed25519/ed25519.go b/src/crypto/ed25519/ed25519.go index 26b4882b132..f09dabe23e8 100644 --- a/src/crypto/ed25519/ed25519.go +++ b/src/crypto/ed25519/ed25519.go @@ -17,12 +17,15 @@ package ed25519 import ( "crypto" + "crypto/internal/fips140/drbg" "crypto/internal/fips140/ed25519" "crypto/internal/fips140cache" "crypto/internal/fips140only" + "crypto/internal/rand" cryptorand "crypto/rand" "crypto/subtle" "errors" + "internal/godebug" "io" "strconv" ) @@ -135,18 +138,31 @@ type Options struct { // HashFunc returns o.Hash. func (o *Options) HashFunc() crypto.Hash { return o.Hash } -// GenerateKey generates a public/private key pair using entropy from rand. -// If rand is nil, [crypto/rand.Reader] will be used. +var cryptocustomrand = godebug.New("cryptocustomrand") + +// GenerateKey generates a public/private key pair using entropy from random. +// +// If random is nil, a secure random source is used. (Before Go 1.26, a custom +// [crypto/rand.Reader] was used if set by the application. That behavior can be +// restored with GODEBUG=cryptocustomrand=1. This setting will be removed in a +// future Go release. Instead, use [testing/cryptotest.SetGlobalRandom].) // // The output of this function is deterministic, and equivalent to reading -// [SeedSize] bytes from rand, and passing them to [NewKeyFromSeed]. -func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) { - if rand == nil { - rand = cryptorand.Reader +// [SeedSize] bytes from random, and passing them to [NewKeyFromSeed]. +func GenerateKey(random io.Reader) (PublicKey, PrivateKey, error) { + if random == nil { + if cryptocustomrand.Value() == "1" { + random = cryptorand.Reader + if _, ok := random.(drbg.DefaultReader); !ok { + cryptocustomrand.IncNonDefault() + } + } else { + random = rand.Reader + } } seed := make([]byte, SeedSize) - if _, err := io.ReadFull(rand, seed); err != nil { + if _, err := io.ReadFull(random, seed); err != nil { return nil, nil, err } diff --git a/src/crypto/hpke/kem.go b/src/crypto/hpke/kem.go index c30f79bad49..7633aa2b714 100644 --- a/src/crypto/hpke/kem.go +++ b/src/crypto/hpke/kem.go @@ -6,7 +6,7 @@ package hpke import ( "crypto/ecdh" - "crypto/rand" + "crypto/internal/rand" "errors" "internal/byteorder" ) diff --git a/src/crypto/hpke/pq.go b/src/crypto/hpke/pq.go index 322f937ae86..a79dadf58f3 100644 --- a/src/crypto/hpke/pq.go +++ b/src/crypto/hpke/pq.go @@ -8,8 +8,9 @@ import ( "bytes" "crypto" "crypto/ecdh" + "crypto/internal/fips140/drbg" + "crypto/internal/rand" "crypto/mlkem" - "crypto/rand" "crypto/sha3" "errors" "internal/byteorder" @@ -246,7 +247,7 @@ func NewHybridPrivateKey(pq crypto.Decapsulator, t ecdh.KeyExchanger) (PrivateKe func (kem *hybridKEM) GenerateKey() (PrivateKey, error) { seed := make([]byte, 32) - rand.Read(seed) + drbg.Read(seed) return kem.NewPrivateKey(seed) } diff --git a/src/crypto/internal/fips140/drbg/rand.go b/src/crypto/internal/fips140/drbg/rand.go index cec697c7ab8..949e74ac60c 100644 --- a/src/crypto/internal/fips140/drbg/rand.go +++ b/src/crypto/internal/fips140/drbg/rand.go @@ -11,7 +11,6 @@ package drbg import ( entropy "crypto/internal/entropy/v1.0.0" "crypto/internal/fips140" - "crypto/internal/randutil" "crypto/internal/sysrand" "io" "sync" @@ -63,6 +62,15 @@ var drbgPool = sync.Pool{ // uses an SP 800-90A Rev. 1 Deterministic Random Bit Generator (DRBG). // Otherwise, it uses the operating system's random number generator. func Read(b []byte) { + if testingReader != nil { + fips140.RecordNonApproved() + // Avoid letting b escape in the non-testing case. + bb := make([]byte, len(b)) + testingReader.Read(bb) + copy(b, bb) + return + } + if !fips140.Enabled { sysrand.Read(b) return @@ -101,36 +109,33 @@ func Read(b []byte) { } } +var testingReader io.Reader + +// SetTestingReader sets a global, deterministic cryptographic randomness source +// for testing purposes. Its Read method must never return an error, it must +// never return short, and it must be safe for concurrent use. +// +// This is only intended to be used by the testing/cryptotest package. +func SetTestingReader(r io.Reader) { + testingReader = r +} + // DefaultReader is a sentinel type, embedded in the default // [crypto/rand.Reader], used to recognize it when passed to // APIs that accept a rand io.Reader. +// +// Any Reader that implements this interface is assumed to +// call [Read] as its Read method. type DefaultReader interface{ defaultReader() } // ReadWithReader uses Reader to fill b with cryptographically secure random // bytes. It is intended for use in APIs that expose a rand io.Reader. -// -// If Reader is not the default Reader from crypto/rand, -// [randutil.MaybeReadByte] and [fips140.RecordNonApproved] are called. func ReadWithReader(r io.Reader, b []byte) error { if _, ok := r.(DefaultReader); ok { Read(b) return nil } - fips140.RecordNonApproved() - randutil.MaybeReadByte(r) - _, err := io.ReadFull(r, b) - return err -} - -// ReadWithReaderDeterministic is like ReadWithReader, but it doesn't call -// [randutil.MaybeReadByte] on non-default Readers. -func ReadWithReaderDeterministic(r io.Reader, b []byte) error { - if _, ok := r.(DefaultReader); ok { - Read(b) - return nil - } - fips140.RecordNonApproved() _, err := io.ReadFull(r, b) return err diff --git a/src/crypto/internal/fips140/rsa/pkcs1v22.go b/src/crypto/internal/fips140/rsa/pkcs1v22.go index 29c47069a3e..4a043213fd0 100644 --- a/src/crypto/internal/fips140/rsa/pkcs1v22.go +++ b/src/crypto/internal/fips140/rsa/pkcs1v22.go @@ -272,8 +272,8 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash hash.Hash, hashed []byte, sa checkApprovedHash(hash) // Note that while we don't commit to deterministic execution with respect - // to the rand stream, we also don't apply MaybeReadByte, so per Hyrum's Law - // it's probably relied upon by some. It's a tolerable promise because a + // to the rand stream, we also never applied MaybeReadByte, so per Hyrum's + // Law it's probably relied upon by some. It's a tolerable promise because a // well-specified number of random bytes is included in the signature, in a // well-specified way. @@ -286,7 +286,7 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash hash.Hash, hashed []byte, sa fips140.RecordNonApproved() } salt := make([]byte, saltLength) - if err := drbg.ReadWithReaderDeterministic(rand, salt); err != nil { + if err := drbg.ReadWithReader(rand, salt); err != nil { return nil, err } @@ -372,7 +372,7 @@ func checkApprovedHash(hash hash.Hash) { // EncryptOAEP encrypts the given message with RSAES-OAEP. func EncryptOAEP(hash, mgfHash hash.Hash, random io.Reader, pub *PublicKey, msg []byte, label []byte) ([]byte, error) { // Note that while we don't commit to deterministic execution with respect - // to the random stream, we also don't apply MaybeReadByte, so per Hyrum's + // to the random stream, we also never applied MaybeReadByte, so per Hyrum's // Law it's probably relied upon by some. It's a tolerable promise because a // well-specified number of random bytes is included in the ciphertext, in a // well-specified way. @@ -402,7 +402,7 @@ func EncryptOAEP(hash, mgfHash hash.Hash, random io.Reader, pub *PublicKey, msg db[len(db)-len(msg)-1] = 1 copy(db[len(db)-len(msg):], msg) - if err := drbg.ReadWithReaderDeterministic(random, seed); err != nil { + if err := drbg.ReadWithReader(random, seed); err != nil { return nil, err } diff --git a/src/crypto/internal/rand/rand.go b/src/crypto/internal/rand/rand.go new file mode 100644 index 00000000000..3a780952e70 --- /dev/null +++ b/src/crypto/internal/rand/rand.go @@ -0,0 +1,65 @@ +// Copyright 2025 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 rand + +import ( + "crypto/internal/boring" + "crypto/internal/fips140/drbg" + "crypto/internal/randutil" + "internal/godebug" + "io" + _ "unsafe" +) + +type reader struct { + drbg.DefaultReader +} + +func (r reader) Read(b []byte) (n int, err error) { + if boring.Enabled { + if _, err := boring.RandReader.Read(b); err != nil { + panic("crypto/rand: boring RandReader failed: " + err.Error()) + } + return len(b), nil + } + drbg.Read(b) + return len(b), nil +} + +// Reader is an io.Reader that calls [drbg.Read]. +// +// It should be used internally instead of [crypto/rand.Reader], because the +// latter can be set by applications outside of tests. These applications then +// risk breaking between Go releases, if the way the Reader is used changes. +var Reader io.Reader = reader{} + +// SetTestingReader overrides all calls to [drbg.Read]. The Read method of +// r must never return an error or return short. +// +// SetTestingReader panics when building against Go Cryptographic Module v1.0.0. +// +// SetTestingReader is pulled by [testing/cryptotest.setGlobalRandom] via go:linkname. +// +//go:linkname SetTestingReader crypto/internal/rand.SetTestingReader +func SetTestingReader(r io.Reader) { + fips140SetTestingReader(r) +} + +var cryptocustomrand = godebug.New("cryptocustomrand") + +// CustomReader returns [Reader] or, only if the GODEBUG setting +// "cryptocustomrand=1" is set, the provided io.Reader. +// +// If returning a non-default Reader, it calls [randutil.MaybeReadByte] on it. +func CustomReader(r io.Reader) io.Reader { + if cryptocustomrand.Value() == "1" { + if _, ok := r.(drbg.DefaultReader); !ok { + randutil.MaybeReadByte(r) + cryptocustomrand.IncNonDefault() + } + return r + } + return Reader +} diff --git a/src/crypto/internal/rand/rand_fipsv1.0.go b/src/crypto/internal/rand/rand_fipsv1.0.go new file mode 100644 index 00000000000..29eba7e0bca --- /dev/null +++ b/src/crypto/internal/rand/rand_fipsv1.0.go @@ -0,0 +1,13 @@ +// Copyright 2025 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 fips140v1.0 + +package rand + +import "io" + +func fips140SetTestingReader(r io.Reader) { + panic("cryptotest.SetGlobalRandom is not supported when building against Go Cryptographic Module v1.0.0") +} diff --git a/src/crypto/internal/rand/rand_fipsv2.0.go b/src/crypto/internal/rand/rand_fipsv2.0.go new file mode 100644 index 00000000000..0dc18e78838 --- /dev/null +++ b/src/crypto/internal/rand/rand_fipsv2.0.go @@ -0,0 +1,16 @@ +// Copyright 2025 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 !fips140v1.0 + +package rand + +import ( + "crypto/internal/fips140/drbg" + "io" +) + +func fips140SetTestingReader(r io.Reader) { + drbg.SetTestingReader(r) +} diff --git a/src/crypto/internal/sysrand/rand.go b/src/crypto/internal/sysrand/rand.go index 034bf617155..5f3977aaddf 100644 --- a/src/crypto/internal/sysrand/rand.go +++ b/src/crypto/internal/sysrand/rand.go @@ -31,6 +31,9 @@ var testingOnlyFailRead bool // system. It always fills b entirely and crashes the program irrecoverably if // an error is encountered. The operating system APIs are documented to never // return an error on all but legacy Linux systems. +// +// Note that Read is not affected by [testing/cryptotest.SetGlobalRand], and it +// should not be used directly by algorithm implementations. func Read(b []byte) { if firstUse.CompareAndSwap(false, true) { // First use of randomness. Start timer to warn about diff --git a/src/crypto/mlkem/mlkem.go b/src/crypto/mlkem/mlkem.go index 176b79673b0..b652e3bae9d 100644 --- a/src/crypto/mlkem/mlkem.go +++ b/src/crypto/mlkem/mlkem.go @@ -43,7 +43,7 @@ type DecapsulationKey768 struct { } // GenerateKey768 generates a new decapsulation key, drawing random bytes from -// the default crypto/rand source. The decapsulation key must be kept secret. +// a secure source. The decapsulation key must be kept secret. func GenerateKey768() (*DecapsulationKey768, error) { key, err := mlkem.GenerateKey768() if err != nil { @@ -118,7 +118,7 @@ func (ek *EncapsulationKey768) Bytes() []byte { } // Encapsulate generates a shared key and an associated ciphertext from an -// encapsulation key, drawing random bytes from the default crypto/rand source. +// encapsulation key, drawing random bytes from a secure source. // // The shared key must be kept secret. // @@ -135,7 +135,7 @@ type DecapsulationKey1024 struct { } // GenerateKey1024 generates a new decapsulation key, drawing random bytes from -// the default crypto/rand source. The decapsulation key must be kept secret. +// a secure source. The decapsulation key must be kept secret. func GenerateKey1024() (*DecapsulationKey1024, error) { key, err := mlkem.GenerateKey1024() if err != nil { @@ -210,7 +210,7 @@ func (ek *EncapsulationKey1024) Bytes() []byte { } // Encapsulate generates a shared key and an associated ciphertext from an -// encapsulation key, drawing random bytes from the default crypto/rand source. +// encapsulation key, drawing random bytes from a secure source. // // The shared key must be kept secret. // diff --git a/src/crypto/rand/rand.go b/src/crypto/rand/rand.go index 1ca16caa956..004e6b6fedc 100644 --- a/src/crypto/rand/rand.go +++ b/src/crypto/rand/rand.go @@ -8,11 +8,14 @@ package rand import ( "crypto/internal/boring" - "crypto/internal/fips140" "crypto/internal/fips140/drbg" - "crypto/internal/sysrand" + "crypto/internal/rand" "io" _ "unsafe" + + // Ensure the go:linkname from testing/cryptotest to + // crypto/internal/rand.SetTestingReader works. + _ "crypto/internal/rand" ) // Reader is a global, shared instance of a cryptographically @@ -35,21 +38,7 @@ func init() { Reader = boring.RandReader return } - Reader = &reader{} -} - -type reader struct { - drbg.DefaultReader -} - -func (r *reader) Read(b []byte) (n int, err error) { - boring.Unreachable() - if fips140.Enabled { - drbg.Read(b) - } else { - sysrand.Read(b) - } - return len(b), nil + Reader = rand.Reader } // fatal is [runtime.fatal], pushed via linkname. @@ -68,8 +57,9 @@ func Read(b []byte) (n int, err error) { // through a potentially overridden Reader, so we special-case the default // case which we can keep non-escaping, and in the general case we read into // a heap buffer and copy from it. - if r, ok := Reader.(*reader); ok { - _, err = r.Read(b) + if _, ok := Reader.(drbg.DefaultReader); ok { + boring.Unreachable() + drbg.Read(b) } else { bb := make([]byte, len(b)) _, err = io.ReadFull(Reader, bb) diff --git a/src/crypto/rand/util.go b/src/crypto/rand/util.go index 8c928519751..7cb9b47b4a6 100644 --- a/src/crypto/rand/util.go +++ b/src/crypto/rand/util.go @@ -6,7 +6,7 @@ package rand import ( "crypto/internal/fips140only" - "crypto/internal/randutil" + "crypto/internal/rand" "errors" "io" "math/big" @@ -14,7 +14,11 @@ import ( // Prime returns a number of the given bit length that is prime with high probability. // Prime will return error for any error returned by rand.Read or if bits < 2. -func Prime(rand io.Reader, bits int) (*big.Int, error) { +// +// Since Go 1.26, a secure source of random bytes is always used, and the Reader is +// ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed +// in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom]. +func Prime(r io.Reader, bits int) (*big.Int, error) { if fips140only.Enforced() { return nil, errors.New("crypto/rand: use of Prime is not allowed in FIPS 140-only mode") } @@ -22,7 +26,7 @@ func Prime(rand io.Reader, bits int) (*big.Int, error) { return nil, errors.New("crypto/rand: prime size must be at least 2-bit") } - randutil.MaybeReadByte(rand) + r = rand.CustomReader(r) b := uint(bits % 8) if b == 0 { @@ -33,7 +37,7 @@ func Prime(rand io.Reader, bits int) (*big.Int, error) { p := new(big.Int) for { - if _, err := io.ReadFull(rand, bytes); err != nil { + if _, err := io.ReadFull(r, bytes); err != nil { return nil, err } diff --git a/src/crypto/rsa/pkcs1v15.go b/src/crypto/rsa/pkcs1v15.go index caf68957e2d..0f216e01932 100644 --- a/src/crypto/rsa/pkcs1v15.go +++ b/src/crypto/rsa/pkcs1v15.go @@ -8,7 +8,7 @@ import ( "crypto/internal/boring" "crypto/internal/fips140/rsa" "crypto/internal/fips140only" - "crypto/internal/randutil" + "crypto/internal/rand" "crypto/subtle" "errors" "io" @@ -36,12 +36,11 @@ type PKCS1v15DecryptOptions struct { // scheme from PKCS #1 v1.5. The message must be no longer than the // length of the public modulus minus 11 bytes. // -// The random parameter is used as a source of entropy to ensure that -// encrypting the same message twice doesn't result in the same -// ciphertext. Most applications should use [crypto/rand.Reader] -// as random. Note that the returned ciphertext does not depend -// deterministically on the bytes read from random, and may change -// between calls and/or between versions. +// The random parameter is used as a source of entropy to ensure that encrypting +// the same message twice doesn't result in the same ciphertext. Since Go 1.26, +// a secure source of random bytes is always used, and the Reader is ignored +// unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed in a +// future Go release. Instead, use [testing/cryptotest.SetGlobalRandom]. // // Deprecated: PKCS #1 v1.5 encryption is dangerous and should not be used. // See [draft-irtf-cfrg-rsa-guidance-05] for more information. Use @@ -57,8 +56,6 @@ func EncryptPKCS1v15(random io.Reader, pub *PublicKey, msg []byte) ([]byte, erro return nil, err } - randutil.MaybeReadByte(random) - k := pub.Size() if len(msg) > k-11 { return nil, ErrMessageTooLong @@ -73,6 +70,8 @@ func EncryptPKCS1v15(random io.Reader, pub *PublicKey, msg []byte) ([]byte, erro } boring.UnreachableExceptTests() + random = rand.CustomReader(random) + // EM = 0x00 || 0x02 || PS || 0x00 || M em := make([]byte, k) em[1] = 2 diff --git a/src/crypto/rsa/rsa.go b/src/crypto/rsa/rsa.go index 5680d8b5415..62f2de30b0b 100644 --- a/src/crypto/rsa/rsa.go +++ b/src/crypto/rsa/rsa.go @@ -48,8 +48,8 @@ import ( "crypto/internal/fips140/bigmod" "crypto/internal/fips140/rsa" "crypto/internal/fips140only" - "crypto/internal/randutil" - "crypto/rand" + "crypto/internal/rand" + cryptorand "crypto/rand" "crypto/subtle" "errors" "fmt" @@ -304,9 +304,9 @@ func checkPublicKeySize(k *PublicKey) error { // If bits is less than 1024, [GenerateKey] returns an error. See the "[Minimum // key size]" section for further details. // -// Most applications should use [crypto/rand.Reader] as rand. Note that the -// returned key does not depend deterministically on the bytes read from rand, -// and may change between calls and/or between versions. +// Since Go 1.26, a secure source of random bytes is always used, and the Reader is +// ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed +// in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom]. // // [Minimum key size]: https://pkg.go.dev/crypto/rsa#hdr-Minimum_key_size func GenerateKey(random io.Reader, bits int) (*PrivateKey, error) { @@ -350,6 +350,8 @@ func GenerateKey(random io.Reader, bits int) (*PrivateKey, error) { return key, nil } + random = rand.CustomReader(random) + if fips140only.Enforced() && bits < 2048 { return nil, errors.New("crypto/rsa: use of keys smaller than 2048 bits is not allowed in FIPS 140-only mode") } @@ -415,6 +417,10 @@ func GenerateKey(random io.Reader, bits int) (*PrivateKey, error) { // This package does not implement CRT optimizations for multi-prime RSA, so the // keys with more than two primes will have worse performance. // +// Since Go 1.26, a secure source of random bytes is always used, and the Reader is +// ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed +// in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom]. +// // Deprecated: The use of this function with a number of primes different from // two is not recommended for the above security, compatibility, and performance // reasons. Use [GenerateKey] instead. @@ -428,7 +434,7 @@ func GenerateMultiPrimeKey(random io.Reader, nprimes int, bits int) (*PrivateKey return nil, errors.New("crypto/rsa: multi-prime RSA is not allowed in FIPS 140-only mode") } - randutil.MaybeReadByte(random) + random = rand.CustomReader(random) priv := new(PrivateKey) priv.E = 65537 @@ -473,7 +479,7 @@ NextSetOfPrimes: } for i := 0; i < nprimes; i++ { var err error - primes[i], err = rand.Prime(random, todo/(nprimes-i)) + primes[i], err = cryptorand.Prime(random, todo/(nprimes-i)) if err != nil { return nil, err } diff --git a/src/crypto/tls/handshake_test.go b/src/crypto/tls/handshake_test.go index 3e2c5663087..6e15459a9a1 100644 --- a/src/crypto/tls/handshake_test.go +++ b/src/crypto/tls/handshake_test.go @@ -448,6 +448,10 @@ func runMain(m *testing.M) int { os.Exit(1) } + // TODO(filippo): deprecate Config.Rand, and regenerate handshake recordings + // to use cryptotest.SetGlobalRandom instead. + os.Setenv("GODEBUG", "cryptocustomrand=1,"+os.Getenv("GODEBUG")) + testConfig = &Config{ Time: func() time.Time { return time.Unix(0, 0) }, Rand: zeroSource{}, diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index e329c8a1727..d58bd294cd1 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -560,6 +560,7 @@ var depsRules = ` < crypto/cipher < crypto/internal/boring < crypto/boring + < crypto/internal/rand < crypto/aes, crypto/des, crypto/rc4, @@ -713,6 +714,9 @@ var depsRules = ` log/slog, testing < testing/slogtest; + testing, crypto/rand + < testing/cryptotest; + FMT, crypto/sha256, encoding/binary, encoding/json, go/ast, go/parser, go/token, internal/godebug, math/rand, encoding/hex diff --git a/src/internal/godebugs/table.go b/src/internal/godebugs/table.go index f707fc34f2f..8f6d8bbdda6 100644 --- a/src/internal/godebugs/table.go +++ b/src/internal/godebugs/table.go @@ -29,6 +29,7 @@ var All = []Info{ {Name: "allowmultiplevcs", Package: "cmd/go"}, {Name: "asynctimerchan", Package: "time", Changed: 23, Old: "1"}, {Name: "containermaxprocs", Package: "runtime", Changed: 25, Old: "0"}, + {Name: "cryptocustomrand", Package: "crypto", Changed: 26, Old: "1"}, {Name: "dataindependenttiming", Package: "crypto/subtle", Opaque: true}, {Name: "decoratemappings", Package: "runtime", Opaque: true, Changed: 25, Old: "0"}, {Name: "embedfollowsymlinks", Package: "cmd/go"}, diff --git a/src/runtime/metrics/doc.go b/src/runtime/metrics/doc.go index 8f908f5b520..ca032f51b13 100644 --- a/src/runtime/metrics/doc.go +++ b/src/runtime/metrics/doc.go @@ -271,6 +271,11 @@ Below is the full list of supported metrics, ordered lexicographically. package due to a non-default GODEBUG=containermaxprocs=... setting. + /godebug/non-default-behavior/cryptocustomrand:events + The number of non-default behaviors executed by the crypto + package due to a non-default GODEBUG=cryptocustomrand=... + setting. + /godebug/non-default-behavior/embedfollowsymlinks:events The number of non-default behaviors executed by the cmd/go package due to a non-default GODEBUG=embedfollowsymlinks=... diff --git a/src/testing/cryptotest/rand.go b/src/testing/cryptotest/rand.go new file mode 100644 index 00000000000..d00732d42f1 --- /dev/null +++ b/src/testing/cryptotest/rand.go @@ -0,0 +1,76 @@ +// Copyright 2025 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 cryptotest provides deterministic random source testing. +package cryptotest + +import ( + cryptorand "crypto/rand" + "internal/byteorder" + "io" + mathrand "math/rand/v2" + "sync" + "testing" + + // Import unsafe and crypto/rand, which imports crypto/internal/rand, + // for the crypto/internal/rand.SetTestingReader go:linkname. + _ "crypto/rand" + _ "unsafe" +) + +//go:linkname randSetTestingReader crypto/internal/rand.SetTestingReader +func randSetTestingReader(r io.Reader) + +//go:linkname testingCheckParallel testing.checkParallel +func testingCheckParallel(t *testing.T) + +// SetGlobalRandom sets a global, deterministic cryptographic randomness source +// for the duration of test t. It affects crypto/rand, and all implicit sources +// of cryptographic randomness in the crypto/... packages. +// +// SetGlobalRandom may be called multiple times in the same test to reset the +// random stream or change the seed. +// +// Because SetGlobalRandom affects the whole process, it cannot be used in +// parallel tests or tests with parallel ancestors. +// +// Note that the way cryptographic algorithms use randomness is generally not +// specified and may change over time. Thus, if a test expects a specific output +// from a cryptographic function, it may fail in the future even if it uses +// SetGlobalRandom. +// +// SetGlobalRandom is not supported when building against the Go Cryptographic +// Module v1.0.0 (i.e. when [crypto/fips140.Version] returns "v1.0.0"). +func SetGlobalRandom(t *testing.T, seed uint64) { + if t == nil { + panic("cryptotest: SetGlobalRandom called with a nil *testing.T") + } + if !testing.Testing() { + panic("cryptotest: SetGlobalRandom used in a non-test binary") + } + testingCheckParallel(t) + + var s [32]byte + byteorder.LEPutUint64(s[:8], seed) + r := &lockedReader{r: mathrand.NewChaCha8(s)} + + randSetTestingReader(r) + previous := cryptorand.Reader + cryptorand.Reader = r + t.Cleanup(func() { + cryptorand.Reader = previous + randSetTestingReader(nil) + }) +} + +type lockedReader struct { + sync.Mutex + r *mathrand.ChaCha8 +} + +func (lr *lockedReader) Read(b []byte) (n int, err error) { + lr.Lock() + defer lr.Unlock() + return lr.r.Read(b) +} diff --git a/src/testing/cryptotest/rand_test.go b/src/testing/cryptotest/rand_test.go new file mode 100644 index 00000000000..bf18c1b5332 --- /dev/null +++ b/src/testing/cryptotest/rand_test.go @@ -0,0 +1,202 @@ +// Copyright 2025 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 !fips140v1.0 + +package cryptotest + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/mlkem" + "crypto/rand" + "encoding/hex" + "testing" +) + +func TestSetGlobalRandom(t *testing.T) { + seed1, _ := hex.DecodeString("6ae6783f4fbde91b6eb88b73a48ed247dbe5882e2579683432c1bfc525454add" + + "0cd87274d67084caaf0e0d36c8496db7fef55fe0e125750aa608d5e20ffc2d12") + + t.Run("rand.Read", func(t *testing.T) { + buf := make([]byte, 64) + + t.Run("seed 1", func(t *testing.T) { + SetGlobalRandom(t, 1) + rand.Read(buf) + if !bytes.Equal(buf, seed1) { + t.Errorf("rand.Read with seed 1 = %x; want %x", buf, seed1) + } + + rand.Read(buf) + if bytes.Equal(buf, seed1) { + t.Errorf("rand.Read with seed 1 returned same output twice: %x", buf) + } + + SetGlobalRandom(t, 1) + rand.Read(buf) + if !bytes.Equal(buf, seed1) { + t.Errorf("rand.Read with seed 1 after reset = %x; want %x", buf, seed1) + } + + SetGlobalRandom(t, 1) + }) + + rand.Read(buf) + if bytes.Equal(buf, seed1) { + t.Errorf("rand.Read returned seeded output after test end") + } + + t.Run("seed 2", func(t *testing.T) { + SetGlobalRandom(t, 2) + rand.Read(buf) + if bytes.Equal(buf, seed1) { + t.Errorf("rand.Read with seed 2 = %x; want different from %x", buf, seed1) + } + }) + }) + + t.Run("rand.Reader", func(t *testing.T) { + buf := make([]byte, 64) + + t.Run("seed 1", func(t *testing.T) { + SetGlobalRandom(t, 1) + rand.Reader.Read(buf) + if !bytes.Equal(buf, seed1) { + t.Errorf("rand.Reader.Read with seed 1 = %x; want %x", buf, seed1) + } + + SetGlobalRandom(t, 1) + }) + + rand.Reader.Read(buf) + if bytes.Equal(buf, seed1) { + t.Errorf("rand.Reader.Read returned seeded output after test end") + } + + oldReader := rand.Reader + t.Cleanup(func() { rand.Reader = oldReader }) + rand.Reader = bytes.NewReader(bytes.Repeat([]byte{5}, 64)) + + t.Run("seed 1 again", func(t *testing.T) { + SetGlobalRandom(t, 1) + rand.Reader.Read(buf) + if !bytes.Equal(buf, seed1) { + t.Errorf("rand.Reader.Read with seed 1 = %x; want %x", buf, seed1) + } + }) + + rand.Reader.Read(buf) + if !bytes.Equal(buf, bytes.Repeat([]byte{5}, 64)) { + t.Errorf("rand.Reader not restored") + } + }) + + // A direct internal use of drbg.Read. + t.Run("mlkem.GenerateKey768", func(t *testing.T) { + exp, err := mlkem.NewDecapsulationKey768(seed1) + if err != nil { + t.Fatalf("mlkem.NewDecapsulationKey768: %v", err) + } + + SetGlobalRandom(t, 1) + got, err := mlkem.GenerateKey768() + if err != nil { + t.Fatalf("mlkem.GenerateKey768: %v", err) + } + + if gotBytes := got.Bytes(); !bytes.Equal(gotBytes, exp.Bytes()) { + t.Errorf("mlkem.GenerateKey768 with seed 1 = %x; want %x", gotBytes, exp.Bytes()) + } + }) + + // An ignored passed-in Reader. + t.Run("ecdsa.GenerateKey", func(t *testing.T) { + exp, err := ecdsa.ParseRawPrivateKey(elliptic.P384(), seed1[:48]) + if err != nil { + t.Fatalf("ecdsa.ParseRawPrivateKey: %v", err) + } + + SetGlobalRandom(t, 1) + got, err := ecdsa.GenerateKey(elliptic.P384(), bytes.NewReader([]byte("this reader is ignored"))) + if err != nil { + t.Fatalf("ecdsa.GenerateKey: %v", err) + } + + if !got.Equal(exp) { + t.Errorf("ecdsa.GenerateKey with seed 1 = %x; want %x", got.D.Bytes(), exp.D.Bytes()) + } + }) + + // The passed-in Reader is used if cryptocustomrand=1 is set, + // and MaybeReadByte is called on it. + t.Run("cryptocustomrand=1", func(t *testing.T) { + t.Setenv("GODEBUG", "cryptocustomrand=1") + + buf := make([]byte, 49) + buf[0] = 42 + for i := 2; i < 49; i++ { + buf[i] = 1 + } + + exp1, err := ecdsa.ParseRawPrivateKey(elliptic.P384(), buf[:48]) + if err != nil { + t.Fatalf("ecdsa.ParseRawPrivateKey: %v", err) + } + exp2, err := ecdsa.ParseRawPrivateKey(elliptic.P384(), buf[1:49]) + if err != nil { + t.Fatalf("ecdsa.ParseRawPrivateKey: %v", err) + } + + seen := [2]bool{} + for i := 0; i < 1000; i++ { + r := bytes.NewReader(buf) + got, err := ecdsa.GenerateKey(elliptic.P384(), r) + if err != nil { + t.Fatalf("ecdsa.GenerateKey: %v", err) + } + switch { + case got.Equal(exp1): + seen[0] = true + case got.Equal(exp2): + seen[1] = true + default: + t.Fatalf("ecdsa.GenerateKey with custom reader = %x; want %x or %x", got.D.Bytes(), exp1.D.Bytes(), exp2.D.Bytes()) + } + if seen[0] && seen[1] { + break + } + } + if !seen[0] || !seen[1] { + t.Errorf("ecdsa.GenerateKey with custom reader did not produce both expected keys") + } + + // Again, with SetGlobalRandom. + SetGlobalRandom(t, 1) + + seen = [2]bool{} + for i := 0; i < 1000; i++ { + r := bytes.NewReader(buf) + got, err := ecdsa.GenerateKey(elliptic.P384(), r) + if err != nil { + t.Fatalf("ecdsa.GenerateKey: %v", err) + } + switch { + case got.Equal(exp1): + seen[0] = true + case got.Equal(exp2): + seen[1] = true + default: + t.Fatalf("ecdsa.GenerateKey with custom reader and SetGlobalRandom = %x; want %x or %x", got.D.Bytes(), exp1.D.Bytes(), exp2.D.Bytes()) + } + if seen[0] && seen[1] { + break + } + } + if !seen[0] || !seen[1] { + t.Errorf("ecdsa.GenerateKey with custom reader and SetGlobalRandom did not produce both expected keys") + } + }) +} diff --git a/src/testing/testing.go b/src/testing/testing.go index 0d1d08ca89a..34b45b41b9a 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -1749,7 +1749,7 @@ func pcToName(pc uintptr) string { return frame.Function } -const parallelConflict = `testing: test using t.Setenv or t.Chdir can not use t.Parallel` +const parallelConflict = `testing: test using t.Setenv, t.Chdir, or cryptotest.SetGlobalRandom can not use t.Parallel` // Parallel signals that this test is to be run in parallel with (and only with) // other parallel tests. When a test is run multiple times due to use of @@ -1820,6 +1820,13 @@ func (t *T) Parallel() { t.lastRaceErrors.Store(int64(race.Errors())) } +// checkParallel is called by [testing/cryptotest.SetGlobalRandom]. +// +//go:linkname checkParallel testing.checkParallel +func checkParallel(t *T) { + t.checkParallel() +} + func (t *T) checkParallel() { // Non-parallel subtests that have parallel ancestors may still // run in parallel with other tests: they are only non-parallel From 3531ac23d4aac6bdd914f14f65ee5fdc5e6e98fa Mon Sep 17 00:00:00 2001 From: Mark Freeman Date: Mon, 24 Nov 2025 17:04:49 -0500 Subject: [PATCH 082/140] go/types, types2: replace setDefType with pending type check Given a type definition of the form: type T RHS The setDefType function would set T.fromRHS as soon as we knew its top-level type. For instance, in: type S struct { ... } S.fromRHS is set to a struct type before type-checking anything inside the struct. This permit access to the (incomplete) RHS type in a cyclic type declaration. Accessing this information is fraught (as it's incomplete), but was used for reporting certain types of cycles. This CL replaces setDefType with a check that ensures no value of type T is used before its RHS is set up. This CL is strictly more complete than what setDefType achieved. For instance, it enables correct reporting for the below cycles: type A [unsafe.Sizeof(A{})]int var v any = 42 type B [v.(B)]int func f() C { return C{} } type C [unsafe.Sizeof(f())]int Fixes #76383 Fixes #76384 Change-Id: I9dfab5b708013b418fa66e43362bb4d8483fedec Reviewed-on: https://go-review.googlesource.com/c/go/+/724140 Auto-Submit: Mark Freeman Reviewed-by: Robert Griesemer LUCI-TryBot-Result: Go LUCI --- src/cmd/compile/internal/types2/expr.go | 23 +++++++++++++++++++ src/cmd/compile/internal/types2/typexpr.go | 16 ++----------- src/go/types/expr.go | 23 +++++++++++++++++++ src/go/types/typexpr.go | 16 ++----------- src/internal/types/errors/codes.go | 11 +++++---- src/internal/types/testdata/check/cycles0.go | 10 ++++---- src/internal/types/testdata/check/cycles2.go | 4 ++-- src/internal/types/testdata/check/cycles4.go | 4 ++-- src/internal/types/testdata/check/issues0.go | 4 ++-- .../types/testdata/fixedbugs/issue39634.go | 4 ++-- .../types/testdata/fixedbugs/issue49276.go | 16 ++++++------- .../types/testdata/fixedbugs/issue76383.go | 13 +++++++++++ .../types/testdata/fixedbugs/issue76384.go | 13 +++++++++++ test/fixedbugs/issue18392.go | 7 ++---- 14 files changed, 105 insertions(+), 59 deletions(-) create mode 100644 src/internal/types/testdata/fixedbugs/issue76383.go create mode 100644 src/internal/types/testdata/fixedbugs/issue76384.go diff --git a/src/cmd/compile/internal/types2/expr.go b/src/cmd/compile/internal/types2/expr.go index 39bf4055a37..9d7580cb016 100644 --- a/src/cmd/compile/internal/types2/expr.go +++ b/src/cmd/compile/internal/types2/expr.go @@ -993,6 +993,13 @@ func (check *Checker) rawExpr(T *target, x *operand, e syntax.Expr, hint Type, a check.nonGeneric(T, x) } + // Here, x is a value, meaning it has a type. If that type is pending, then we have + // a cycle. As an example: + // + // type T [unsafe.Sizeof(T{})]int + // + // has a cycle T->T which is deemed valid (by decl.go), but which is in fact invalid. + check.pendingType(x) check.record(x) return kind @@ -1027,6 +1034,22 @@ func (check *Checker) nonGeneric(T *target, x *operand) { } } +// If x has a pending type (i.e. its declaring object is on the object path), pendingType +// reports an error and invalidates x.mode and x.typ. +// Otherwise it leaves x alone. +func (check *Checker) pendingType(x *operand) { + if x.mode == invalid || x.mode == novalue { + return + } + if n, ok := Unalias(x.typ).(*Named); ok { + if i, ok := check.objPathIdx[n.obj]; ok { + check.cycleError(check.objPath, i) + x.mode = invalid + x.typ = Typ[Invalid] + } + } +} + // exprInternal contains the core of type checking of expressions. // Must only be called by rawExpr. // (See rawExpr for an explanation of the parameters.) diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go index 303f782ac49..c3e40184f5a 100644 --- a/src/cmd/compile/internal/types2/typexpr.go +++ b/src/cmd/compile/internal/types2/typexpr.go @@ -417,20 +417,8 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { return typ } -func setDefType(def *TypeName, typ Type) { - if def != nil { - switch t := def.typ.(type) { - case *Alias: - t.fromRHS = typ - case *Basic: - assert(t == Typ[Invalid]) - case *Named: - t.fromRHS = typ - default: - panic(fmt.Sprintf("unexpected type %T", t)) - } - } -} +// TODO(markfreeman): Remove this function. +func setDefType(def *TypeName, typ Type) {} func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def *TypeName) (res Type) { if check.conf.Trace { diff --git a/src/go/types/expr.go b/src/go/types/expr.go index 8b3f764f192..790e0111c3d 100644 --- a/src/go/types/expr.go +++ b/src/go/types/expr.go @@ -985,6 +985,13 @@ func (check *Checker) rawExpr(T *target, x *operand, e ast.Expr, hint Type, allo check.nonGeneric(T, x) } + // Here, x is a value, meaning it has a type. If that type is pending, then we have + // a cycle. As an example: + // + // type T [unsafe.Sizeof(T{})]int + // + // has a cycle T->T which is deemed valid (by decl.go), but which is in fact invalid. + check.pendingType(x) check.record(x) return kind @@ -1019,6 +1026,22 @@ func (check *Checker) nonGeneric(T *target, x *operand) { } } +// If x has a pending type (i.e. its declaring object is on the object path), pendingType +// reports an error and invalidates x.mode and x.typ. +// Otherwise it leaves x alone. +func (check *Checker) pendingType(x *operand) { + if x.mode == invalid || x.mode == novalue { + return + } + if n, ok := Unalias(x.typ).(*Named); ok { + if i, ok := check.objPathIdx[n.obj]; ok { + check.cycleError(check.objPath, i) + x.mode = invalid + x.typ = Typ[Invalid] + } + } +} + // exprInternal contains the core of type checking of expressions. // Must only be called by rawExpr. // (See rawExpr for an explanation of the parameters.) diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index b44fe4d7686..c1381ddf4af 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -413,20 +413,8 @@ func (check *Checker) typInternal(e0 ast.Expr, def *TypeName) (T Type) { return typ } -func setDefType(def *TypeName, typ Type) { - if def != nil { - switch t := def.typ.(type) { - case *Alias: - t.fromRHS = typ - case *Basic: - assert(t == Typ[Invalid]) - case *Named: - t.fromRHS = typ - default: - panic(fmt.Sprintf("unexpected type %T", t)) - } - } -} +// TODO(markfreeman): Remove this function. +func setDefType(def *TypeName, typ Type) {} func (check *Checker) instantiatedType(ix *indexedExpr, def *TypeName) (res Type) { if check.conf._Trace { diff --git a/src/internal/types/errors/codes.go b/src/internal/types/errors/codes.go index b0f7d2d4466..5b68cc3af7a 100644 --- a/src/internal/types/errors/codes.go +++ b/src/internal/types/errors/codes.go @@ -114,15 +114,16 @@ const ( // S // } // - InvalidDeclCycle - - // InvalidTypeCycle occurs when a cycle in type definitions results in a - // type that is not well-defined. - // // Example: // import "unsafe" // // type T [unsafe.Sizeof(T{})]int + InvalidDeclCycle + + // TODO(markfreeman): Retire InvalidTypeCycle, as it's never emitted. + + // InvalidTypeCycle occurs when a cycle in type definitions results in a + // type that is not well-defined. InvalidTypeCycle // InvalidConstInit occurs when a const declaration has a non-constant diff --git a/src/internal/types/testdata/check/cycles0.go b/src/internal/types/testdata/check/cycles0.go index 8ad7877f946..d13dc447dd5 100644 --- a/src/internal/types/testdata/check/cycles0.go +++ b/src/internal/types/testdata/check/cycles0.go @@ -45,7 +45,7 @@ type ( // pointers P0 *P0 - PP *struct{ PP.f /* ERROR "PP.f is not a type" */ } + PP /* ERROR "invalid recursive type" */ *struct{ PP.f } // functions F0 func(F0) @@ -157,10 +157,10 @@ type ( // test cases for issue 18643 // (type cycle detection when non-type expressions are involved) type ( - T14 [len(T14 /* ERROR "invalid recursive type" */ {})]int - T15 [][len(T15 /* ERROR "invalid recursive type" */ {})]int - T16 map[[len(T16 /* ERROR "invalid recursive type" */ {1:2})]int]int - T17 map[int][len(T17 /* ERROR "invalid recursive type" */ {1:2})]int + T14 /* ERROR "invalid recursive type" */ [len(T14{})]int + T15 /* ERROR "invalid recursive type" */ [][len(T15{})]int + T16 /* ERROR "invalid recursive type" */ map[[len(T16{1:2})]int]int + T17 /* ERROR "invalid recursive type" */ map[int][len(T17{1:2})]int ) // Test case for types depending on function literals (see also #22992). diff --git a/src/internal/types/testdata/check/cycles2.go b/src/internal/types/testdata/check/cycles2.go index a932d288b58..75016dbe8bc 100644 --- a/src/internal/types/testdata/check/cycles2.go +++ b/src/internal/types/testdata/check/cycles2.go @@ -64,8 +64,8 @@ var _ = x == y // Test case for issue 6638. -type T interface { - m() [T(nil).m /* ERROR "undefined" */ ()[0]]int +type T /* ERROR "invalid recursive type" */ interface { + m() [T(nil).m()[0]]int } // Variations of this test case. diff --git a/src/internal/types/testdata/check/cycles4.go b/src/internal/types/testdata/check/cycles4.go index e82300125c8..86f4f9aa034 100644 --- a/src/internal/types/testdata/check/cycles4.go +++ b/src/internal/types/testdata/check/cycles4.go @@ -114,8 +114,8 @@ type Event interface { // Check that accessing an interface method too early doesn't lead // to follow-on errors due to an incorrectly computed type set. -type T8 interface { - m() [unsafe.Sizeof(T8.m /* ERROR "undefined" */ )]int +type T8 /* ERROR "invalid recursive type" */ interface { + m() [unsafe.Sizeof(T8.m)]int } var _ = T8.m // no error expected here diff --git a/src/internal/types/testdata/check/issues0.go b/src/internal/types/testdata/check/issues0.go index 2b59a9c9b5c..6117f7a8b9c 100644 --- a/src/internal/types/testdata/check/issues0.go +++ b/src/internal/types/testdata/check/issues0.go @@ -96,8 +96,8 @@ func issue10979() { type _ interface { nosuchpkg /* ERROR "undefined: nosuchpkg" */ .Nosuchtype } - type I interface { - I.m /* ERROR "I.m is not a type" */ + type I /* ERROR "invalid recursive type" */ interface { + I.m m() } } diff --git a/src/internal/types/testdata/fixedbugs/issue39634.go b/src/internal/types/testdata/fixedbugs/issue39634.go index 6fbc7cd7bc7..58fc43eea64 100644 --- a/src/internal/types/testdata/fixedbugs/issue39634.go +++ b/src/internal/types/testdata/fixedbugs/issue39634.go @@ -23,7 +23,7 @@ func(*ph1[e,e /* ERROR "redeclared" */ ])h(d /* ERROR "undefined" */ ) // func t2[T Numeric2](s[]T){0 /* ERROR "not a type */ []{s /* ERROR cannot index" */ [0][0]}} // crash 3 -type t3 *interface{ t3.p /* ERROR "t3.p is not a type" */ } +type t3 /* ERROR "invalid recursive type" */ *interface{ t3.p } // crash 4 type Numeric4 interface{t4 /* ERROR "not a type" */ } @@ -66,7 +66,7 @@ func F17[T Z17](T) {} type o18[T any] []func(_ o18[[]_ /* ERROR "cannot use _" */ ]) // crash 19 -type Z19 [][[]Z19{}[0][0]]c19 /* ERROR "undefined" */ +type Z19 /* ERROR "invalid recursive type: Z19 refers to itself" */ [][[]Z19{}[0][0]]int // crash 20 type Z20 /* ERROR "invalid recursive type" */ interface{ Z20 } diff --git a/src/internal/types/testdata/fixedbugs/issue49276.go b/src/internal/types/testdata/fixedbugs/issue49276.go index bdfb42f407f..00da1a72cf4 100644 --- a/src/internal/types/testdata/fixedbugs/issue49276.go +++ b/src/internal/types/testdata/fixedbugs/issue49276.go @@ -14,33 +14,33 @@ var s S // Since f is a pointer, this case could be valid. // But it's pathological and not worth the expense. -type T struct { - f *[unsafe.Sizeof(T /* ERROR "invalid recursive type" */ {})]int +type T /* ERROR "invalid recursive type" */ struct { + f *[unsafe.Sizeof(T{})]int } // a mutually recursive case using unsafe.Sizeof type ( - A1 struct { + A1/* ERROR "invalid recursive type" */ struct { _ [unsafe.Sizeof(B1{})]int } B1 struct { - _ [unsafe.Sizeof(A1 /* ERROR "invalid recursive type" */ {})]int + _ [unsafe.Sizeof(A1{})]int } ) // a mutually recursive case using len type ( - A2 struct { + A2/* ERROR "invalid recursive type" */ struct { f [len(B2{}.f)]int } B2 struct { - f [len(A2 /* ERROR "invalid recursive type" */ {}.f)]int + f [len(A2{}.f)]int } ) // test case from issue -type a struct { - _ [42 - unsafe.Sizeof(a /* ERROR "invalid recursive type" */ {})]byte +type a /* ERROR "invalid recursive type" */ struct { + _ [42 - unsafe.Sizeof(a{})]byte } diff --git a/src/internal/types/testdata/fixedbugs/issue76383.go b/src/internal/types/testdata/fixedbugs/issue76383.go new file mode 100644 index 00000000000..519174ab287 --- /dev/null +++ b/src/internal/types/testdata/fixedbugs/issue76383.go @@ -0,0 +1,13 @@ +// Copyright 2025 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 p + +import "unsafe" + +var v any = 42 + +type T /* ERROR "invalid recursive type" */ struct { + f [unsafe.Sizeof(v.(T))]int +} diff --git a/src/internal/types/testdata/fixedbugs/issue76384.go b/src/internal/types/testdata/fixedbugs/issue76384.go new file mode 100644 index 00000000000..0737eb9e1a7 --- /dev/null +++ b/src/internal/types/testdata/fixedbugs/issue76384.go @@ -0,0 +1,13 @@ +// Copyright 2025 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 p + +import "unsafe" + +type T /* ERROR "invalid recursive type" */ [unsafe.Sizeof(f())]int + +func f() T { + return T{} +} \ No newline at end of file diff --git a/test/fixedbugs/issue18392.go b/test/fixedbugs/issue18392.go index 32c39c3a7fe..9e4d48d6ea3 100644 --- a/test/fixedbugs/issue18392.go +++ b/test/fixedbugs/issue18392.go @@ -6,9 +6,6 @@ package p -type A interface { - // TODO(mdempsky): This should be an error, but this error is - // nonsense. The error should actually mention that there's a - // type loop. - Fn(A.Fn) // ERROR "type A has no method Fn|A.Fn undefined|A.Fn is not a type" +type A interface { // ERROR "invalid recursive type" + Fn(A.Fn) } From ff2fd6327ecb343d96074dff3ccee359b5f1d629 Mon Sep 17 00:00:00 2001 From: Mark Freeman Date: Mon, 24 Nov 2025 14:34:39 -0500 Subject: [PATCH 083/140] go/types, types2: remove setDefType and most def plumbing CL 722161 replaced the setDefType mechanism with boundaries on composite literals, removing the need to pass the def argument in all but 1 case. The exception is interface types, which use def to populate the receiver type for better error messages. Change-Id: Ic78c91238588015153f0d22790be5872a01c5f63 Reviewed-on: https://go-review.googlesource.com/c/go/+/723920 Auto-Submit: Mark Freeman LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Griesemer --- src/cmd/compile/internal/types2/call.go | 12 ++----- src/cmd/compile/internal/types2/decl.go | 11 +++--- src/cmd/compile/internal/types2/expr.go | 4 +-- src/cmd/compile/internal/types2/lookup.go | 4 +-- src/cmd/compile/internal/types2/named.go | 2 +- src/cmd/compile/internal/types2/resolver.go | 8 ++--- src/cmd/compile/internal/types2/typexpr.go | 38 +++++---------------- src/go/types/call.go | 12 ++----- src/go/types/decl.go | 11 +++--- src/go/types/expr.go | 4 +-- src/go/types/lookup.go | 4 +-- src/go/types/named.go | 2 +- src/go/types/resolver.go | 8 ++--- src/go/types/typexpr.go | 38 +++++---------------- 14 files changed, 48 insertions(+), 110 deletions(-) diff --git a/src/cmd/compile/internal/types2/call.go b/src/cmd/compile/internal/types2/call.go index 52dc33b8cd5..3461c890a8b 100644 --- a/src/cmd/compile/internal/types2/call.go +++ b/src/cmd/compile/internal/types2/call.go @@ -669,7 +669,7 @@ var cgoPrefixes = [...]string{ "_Cmacro_", // function to evaluate the expanded expression } -func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, def *TypeName, wantType bool) { +func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, wantType bool) { // these must be declared before the "goto Error" statements var ( obj Object @@ -715,7 +715,7 @@ func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, def *TypeName } goto Error } - check.objDecl(exp, nil) + check.objDecl(exp) } else { exp = pkg.scope.Lookup(sel) if exp == nil { @@ -777,12 +777,6 @@ func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, def *TypeName check.exprOrType(x, e.X, false) switch x.mode { - case typexpr: - // don't crash for "type T T.x" (was go.dev/issue/51509) - if def != nil && def.typ == x.typ { - check.cycleError([]Object{def}, 0) - goto Error - } case builtin: check.errorf(e.Pos(), UncalledBuiltin, "invalid use of %s in selector expression", x) goto Error @@ -844,7 +838,7 @@ func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, def *TypeName // methods may not have a fully set up signature yet if m, _ := obj.(*Func); m != nil { - check.objDecl(m, nil) + check.objDecl(m) } if x.mode == typexpr { diff --git a/src/cmd/compile/internal/types2/decl.go b/src/cmd/compile/internal/types2/decl.go index 4f9f7c25e74..25152545ece 100644 --- a/src/cmd/compile/internal/types2/decl.go +++ b/src/cmd/compile/internal/types2/decl.go @@ -45,8 +45,7 @@ func pathString(path []Object) string { } // objDecl type-checks the declaration of obj in its respective (file) environment. -// For the meaning of def, see Checker.definedType, in typexpr.go. -func (check *Checker) objDecl(obj Object, def *TypeName) { +func (check *Checker) objDecl(obj Object) { if tracePos { check.pushPos(obj.Pos()) defer func() { @@ -156,7 +155,7 @@ func (check *Checker) objDecl(obj Object, def *TypeName) { check.varDecl(obj, d.lhs, d.vtyp, d.init) case *TypeName: // invalid recursive types are detected via path - check.typeDecl(obj, d.tdecl, def) + check.typeDecl(obj, d.tdecl) check.collectMethods(obj) // methods can only be added to top-level types case *Func: // functions may be recursive - no need to track dependencies @@ -440,7 +439,7 @@ func (check *Checker) isImportedConstraint(typ Type) bool { return u != nil && !u.IsMethodSet() } -func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *TypeName) { +func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl) { assert(obj.typ == nil) // Only report a version error if we have not reported one already. @@ -474,7 +473,6 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *TypeN if check.conf.EnableAlias { alias := check.newAlias(obj, nil) - setDefType(def, alias) // If we could not type the RHS, set it to invalid. This should // only ever happen if we panic before setting. @@ -521,7 +519,6 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *TypeN } named := check.newNamed(obj, nil, nil) - setDefType(def, named) // The RHS of a named N can be nil if, for example, N is defined as a cycle of aliases with // gotypesalias=0. Consider: @@ -878,7 +875,7 @@ func (check *Checker) declStmt(list []syntax.Decl) { scopePos := s.Name.Pos() check.declare(check.scope, s.Name, obj, scopePos) check.push(obj) // mark as grey - check.typeDecl(obj, s, nil) + check.typeDecl(obj, s) check.pop() default: diff --git a/src/cmd/compile/internal/types2/expr.go b/src/cmd/compile/internal/types2/expr.go index 9d7580cb016..637cbaee5d3 100644 --- a/src/cmd/compile/internal/types2/expr.go +++ b/src/cmd/compile/internal/types2/expr.go @@ -1067,7 +1067,7 @@ func (check *Checker) exprInternal(T *target, x *operand, e syntax.Expr, hint Ty goto Error // error was reported before case *syntax.Name: - check.ident(x, e, nil, false) + check.ident(x, e, false) case *syntax.DotsType: // dots are handled explicitly where they are valid @@ -1102,7 +1102,7 @@ func (check *Checker) exprInternal(T *target, x *operand, e syntax.Expr, hint Ty return kind case *syntax.SelectorExpr: - check.selector(x, e, nil, false) + check.selector(x, e, false) case *syntax.IndexExpr: if check.indexExpr(x, e) { diff --git a/src/cmd/compile/internal/types2/lookup.go b/src/cmd/compile/internal/types2/lookup.go index 3e18db09f5c..81a126b9c7d 100644 --- a/src/cmd/compile/internal/types2/lookup.go +++ b/src/cmd/compile/internal/types2/lookup.go @@ -447,7 +447,7 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y // methods may not have a fully set up signature yet if check != nil { - check.objDecl(f, nil) + check.objDecl(f) } if !equivalent(f.typ, m.typ) { @@ -466,7 +466,7 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y // This method may be formatted in funcString below, so must have a fully // set up signature. if check != nil { - check.objDecl(f, nil) + check.objDecl(f) } } switch state { diff --git a/src/cmd/compile/internal/types2/named.go b/src/cmd/compile/internal/types2/named.go index b5c8ed142aa..edd6357248e 100644 --- a/src/cmd/compile/internal/types2/named.go +++ b/src/cmd/compile/internal/types2/named.go @@ -456,7 +456,7 @@ func (t *Named) expandMethod(i int) *Func { check := t.check // Ensure that the original method is type-checked. if check != nil { - check.objDecl(origm, nil) + check.objDecl(origm) } origSig := origm.typ.(*Signature) diff --git a/src/cmd/compile/internal/types2/resolver.go b/src/cmd/compile/internal/types2/resolver.go index 4c9eeb329c8..2418de52686 100644 --- a/src/cmd/compile/internal/types2/resolver.go +++ b/src/cmd/compile/internal/types2/resolver.go @@ -664,7 +664,7 @@ func (check *Checker) packageObjects() { // // Investigate and reenable this branch. for _, obj := range check.objList { - check.objDecl(obj, nil) + check.objDecl(obj) } } else { // Without Alias nodes, we process non-alias type declarations first, followed by @@ -680,7 +680,7 @@ func (check *Checker) packageObjects() { if check.objMap[tname].tdecl.Alias { aliasList = append(aliasList, tname) } else { - check.objDecl(obj, nil) + check.objDecl(obj) } } else { othersList = append(othersList, obj) @@ -688,11 +688,11 @@ func (check *Checker) packageObjects() { } // phase 2: alias type declarations for _, obj := range aliasList { - check.objDecl(obj, nil) + check.objDecl(obj) } // phase 3: all other declarations for _, obj := range othersList { - check.objDecl(obj, nil) + check.objDecl(obj) } } diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go index c3e40184f5a..a79b54eaccf 100644 --- a/src/cmd/compile/internal/types2/typexpr.go +++ b/src/cmd/compile/internal/types2/typexpr.go @@ -16,9 +16,8 @@ import ( // ident type-checks identifier e and initializes x with the value or type of e. // If an error occurred, x.mode is set to invalid. -// For the meaning of def, see Checker.declaredType, below. // If wantType is set, the identifier e is expected to denote a type. -func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType bool) { +func (check *Checker) ident(x *operand, e *syntax.Name, wantType bool) { x.mode = invalid x.expr = e @@ -73,7 +72,7 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType // packages, to avoid races: see issue #69912. typ := obj.Type() if typ == nil || (gotType && wantType && obj.Pkg() == check.pkg) { - check.objDecl(obj, def) + check.objDecl(obj) typ = obj.Type() // type must have been assigned by Checker.objDecl } assert(typ != nil) @@ -257,13 +256,11 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { case *syntax.Name: var x operand - check.ident(&x, e, def, true) + check.ident(&x, e, true) switch x.mode { case typexpr: - typ := x.typ - setDefType(def, typ) - return typ + return x.typ case invalid: // ignore - error reported before case novalue: @@ -274,13 +271,11 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { case *syntax.SelectorExpr: var x operand - check.selector(&x, e, def, true) + check.selector(&x, e, true) switch x.mode { case typexpr: - typ := x.typ - setDefType(def, typ) - return typ + return x.typ case invalid: // ignore - error reported before case novalue: @@ -291,7 +286,7 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { case *syntax.IndexExpr: check.verifyVersionf(e, go1_18, "type instantiation") - return check.instantiatedType(e.X, syntax.UnpackListExpr(e.Index), def) + return check.instantiatedType(e.X, syntax.UnpackListExpr(e.Index)) case *syntax.ParenExpr: // Generic types must be instantiated before they can be used in any form. @@ -300,7 +295,6 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { case *syntax.ArrayType: typ := new(Array) - setDefType(def, typ) if e.Len != nil { typ.len = check.arrayLength(e.Len) } else { @@ -316,7 +310,6 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { case *syntax.SliceType: typ := new(Slice) - setDefType(def, typ) typ.elem = check.varType(e.Elem) return typ @@ -326,7 +319,6 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { case *syntax.StructType: typ := new(Struct) - setDefType(def, typ) check.structType(typ, e) return typ @@ -334,7 +326,6 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { if e.Op == syntax.Mul && e.Y == nil { typ := new(Pointer) typ.base = Typ[Invalid] // avoid nil base in invalid recursive type declaration - setDefType(def, typ) typ.base = check.varType(e.X) // If typ.base is invalid, it's unlikely that *base is particularly // useful - even a valid dereferenciation will lead to an invalid @@ -351,20 +342,16 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { case *syntax.FuncType: typ := new(Signature) - setDefType(def, typ) check.funcType(typ, nil, nil, e) return typ case *syntax.InterfaceType: typ := check.newInterface() - setDefType(def, typ) check.interfaceType(typ, e, def) return typ case *syntax.MapType: typ := new(Map) - setDefType(def, typ) - typ.key = check.varType(e.Key) typ.elem = check.varType(e.Value) @@ -388,7 +375,6 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { case *syntax.ChanType: typ := new(Chan) - setDefType(def, typ) dir := SendRecv switch e.Dir { @@ -413,14 +399,10 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) { } typ := Typ[Invalid] - setDefType(def, typ) return typ } -// TODO(markfreeman): Remove this function. -func setDefType(def *TypeName, typ Type) {} - -func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def *TypeName) (res Type) { +func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr) (res Type) { if check.conf.Trace { check.trace(x.Pos(), "-- instantiating type %s with %s", x, xlist) check.indent++ @@ -431,10 +413,6 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def * }() } - defer func() { - setDefType(def, res) - }() - var cause string typ := check.genericType(x, &cause) if cause != "" { diff --git a/src/go/types/call.go b/src/go/types/call.go index 3bc1a39ddcf..50aa7caba6d 100644 --- a/src/go/types/call.go +++ b/src/go/types/call.go @@ -671,7 +671,7 @@ var cgoPrefixes = [...]string{ "_Cmacro_", // function to evaluate the expanded expression } -func (check *Checker) selector(x *operand, e *ast.SelectorExpr, def *TypeName, wantType bool) { +func (check *Checker) selector(x *operand, e *ast.SelectorExpr, wantType bool) { // these must be declared before the "goto Error" statements var ( obj Object @@ -717,7 +717,7 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr, def *TypeName, w } goto Error } - check.objDecl(exp, nil) + check.objDecl(exp) } else { exp = pkg.scope.Lookup(sel) if exp == nil { @@ -779,12 +779,6 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr, def *TypeName, w check.exprOrType(x, e.X, false) switch x.mode { - case typexpr: - // don't crash for "type T T.x" (was go.dev/issue/51509) - if def != nil && def.typ == x.typ { - check.cycleError([]Object{def}, 0) - goto Error - } case builtin: // types2 uses the position of '.' for the error check.errorf(e.Sel, UncalledBuiltin, "invalid use of %s in selector expression", x) @@ -847,7 +841,7 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr, def *TypeName, w // methods may not have a fully set up signature yet if m, _ := obj.(*Func); m != nil { - check.objDecl(m, nil) + check.objDecl(m) } if x.mode == typexpr { diff --git a/src/go/types/decl.go b/src/go/types/decl.go index 1b0aa77ff3b..80b8c206551 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -46,8 +46,7 @@ func pathString(path []Object) string { } // objDecl type-checks the declaration of obj in its respective (file) environment. -// For the meaning of def, see Checker.definedType, in typexpr.go. -func (check *Checker) objDecl(obj Object, def *TypeName) { +func (check *Checker) objDecl(obj Object) { if tracePos { check.pushPos(atPos(obj.Pos())) defer func() { @@ -157,7 +156,7 @@ func (check *Checker) objDecl(obj Object, def *TypeName) { check.varDecl(obj, d.lhs, d.vtyp, d.init) case *TypeName: // invalid recursive types are detected via path - check.typeDecl(obj, d.tdecl, def) + check.typeDecl(obj, d.tdecl) check.collectMethods(obj) // methods can only be added to top-level types case *Func: // functions may be recursive - no need to track dependencies @@ -515,7 +514,7 @@ func (check *Checker) isImportedConstraint(typ Type) bool { return u != nil && !u.IsMethodSet() } -func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *TypeName) { +func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec) { assert(obj.typ == nil) // Only report a version error if we have not reported one already. @@ -549,7 +548,6 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *TypeName if check.conf._EnableAlias { alias := check.newAlias(obj, nil) - setDefType(def, alias) // If we could not type the RHS, set it to invalid. This should // only ever happen if we panic before setting. @@ -603,7 +601,6 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *TypeName } named := check.newNamed(obj, nil, nil) - setDefType(def, named) // The RHS of a named N can be nil if, for example, N is defined as a cycle of aliases with // gotypesalias=0. Consider: @@ -937,7 +934,7 @@ func (check *Checker) declStmt(d ast.Decl) { scopePos := d.spec.Name.Pos() check.declare(check.scope, d.spec.Name, obj, scopePos) check.push(obj) // mark as grey - check.typeDecl(obj, d.spec, nil) + check.typeDecl(obj, d.spec) check.pop() default: check.errorf(d.node(), InvalidSyntaxTree, "unknown ast.Decl node %T", d.node()) diff --git a/src/go/types/expr.go b/src/go/types/expr.go index 790e0111c3d..09f7cdda802 100644 --- a/src/go/types/expr.go +++ b/src/go/types/expr.go @@ -1056,7 +1056,7 @@ func (check *Checker) exprInternal(T *target, x *operand, e ast.Expr, hint Type) goto Error // error was reported before case *ast.Ident: - check.ident(x, e, nil, false) + check.ident(x, e, false) case *ast.Ellipsis: // ellipses are handled explicitly where they are valid @@ -1088,7 +1088,7 @@ func (check *Checker) exprInternal(T *target, x *operand, e ast.Expr, hint Type) return kind case *ast.SelectorExpr: - check.selector(x, e, nil, false) + check.selector(x, e, false) case *ast.IndexExpr, *ast.IndexListExpr: ix := unpackIndexedExpr(e) diff --git a/src/go/types/lookup.go b/src/go/types/lookup.go index 97debb7395e..7283db43f12 100644 --- a/src/go/types/lookup.go +++ b/src/go/types/lookup.go @@ -450,7 +450,7 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y // methods may not have a fully set up signature yet if check != nil { - check.objDecl(f, nil) + check.objDecl(f) } if !equivalent(f.typ, m.typ) { @@ -469,7 +469,7 @@ func (check *Checker) missingMethod(V, T Type, static bool, equivalent func(x, y // This method may be formatted in funcString below, so must have a fully // set up signature. if check != nil { - check.objDecl(f, nil) + check.objDecl(f) } } switch state { diff --git a/src/go/types/named.go b/src/go/types/named.go index b106d7a8eb7..be6a0f54267 100644 --- a/src/go/types/named.go +++ b/src/go/types/named.go @@ -459,7 +459,7 @@ func (t *Named) expandMethod(i int) *Func { check := t.check // Ensure that the original method is type-checked. if check != nil { - check.objDecl(origm, nil) + check.objDecl(origm) } origSig := origm.typ.(*Signature) diff --git a/src/go/types/resolver.go b/src/go/types/resolver.go index 2017b9c8813..52576db2bd1 100644 --- a/src/go/types/resolver.go +++ b/src/go/types/resolver.go @@ -659,7 +659,7 @@ func (check *Checker) packageObjects() { // // Investigate and reenable this branch. for _, obj := range check.objList { - check.objDecl(obj, nil) + check.objDecl(obj) } } else { // Without Alias nodes, we process non-alias type declarations first, followed by @@ -675,7 +675,7 @@ func (check *Checker) packageObjects() { if check.objMap[tname].tdecl.Assign.IsValid() { aliasList = append(aliasList, tname) } else { - check.objDecl(obj, nil) + check.objDecl(obj) } } else { othersList = append(othersList, obj) @@ -683,11 +683,11 @@ func (check *Checker) packageObjects() { } // phase 2: alias type declarations for _, obj := range aliasList { - check.objDecl(obj, nil) + check.objDecl(obj) } // phase 3: all other declarations for _, obj := range othersList { - check.objDecl(obj, nil) + check.objDecl(obj) } } diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index c1381ddf4af..346ff18e9ad 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -16,9 +16,8 @@ import ( // ident type-checks identifier e and initializes x with the value or type of e. // If an error occurred, x.mode is set to invalid. -// For the meaning of def, see Checker.declaredType, below. // If wantType is set, the identifier e is expected to denote a type. -func (check *Checker) ident(x *operand, e *ast.Ident, def *TypeName, wantType bool) { +func (check *Checker) ident(x *operand, e *ast.Ident, wantType bool) { x.mode = invalid x.expr = e @@ -72,7 +71,7 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *TypeName, wantType bo // packages, to avoid races: see issue #69912. typ := obj.Type() if typ == nil || (gotType && wantType && obj.Pkg() == check.pkg) { - check.objDecl(obj, def) + check.objDecl(obj) typ = obj.Type() // type must have been assigned by Checker.objDecl } assert(typ != nil) @@ -255,13 +254,11 @@ func (check *Checker) typInternal(e0 ast.Expr, def *TypeName) (T Type) { case *ast.Ident: var x operand - check.ident(&x, e, def, true) + check.ident(&x, e, true) switch x.mode { case typexpr: - typ := x.typ - setDefType(def, typ) - return typ + return x.typ case invalid: // ignore - error reported before case novalue: @@ -272,13 +269,11 @@ func (check *Checker) typInternal(e0 ast.Expr, def *TypeName) (T Type) { case *ast.SelectorExpr: var x operand - check.selector(&x, e, def, true) + check.selector(&x, e, true) switch x.mode { case typexpr: - typ := x.typ - setDefType(def, typ) - return typ + return x.typ case invalid: // ignore - error reported before case novalue: @@ -290,7 +285,7 @@ func (check *Checker) typInternal(e0 ast.Expr, def *TypeName) (T Type) { case *ast.IndexExpr, *ast.IndexListExpr: ix := unpackIndexedExpr(e) check.verifyVersionf(inNode(e, ix.lbrack), go1_18, "type instantiation") - return check.instantiatedType(ix, def) + return check.instantiatedType(ix) case *ast.ParenExpr: // Generic types must be instantiated before they can be used in any form. @@ -300,13 +295,11 @@ func (check *Checker) typInternal(e0 ast.Expr, def *TypeName) (T Type) { case *ast.ArrayType: if e.Len == nil { typ := new(Slice) - setDefType(def, typ) typ.elem = check.varType(e.Elt) return typ } typ := new(Array) - setDefType(def, typ) // Provide a more specific error when encountering a [...] array // rather than leaving it to the handling of the ... expression. if _, ok := e.Len.(*ast.Ellipsis); ok { @@ -327,14 +320,12 @@ func (check *Checker) typInternal(e0 ast.Expr, def *TypeName) (T Type) { case *ast.StructType: typ := new(Struct) - setDefType(def, typ) check.structType(typ, e) return typ case *ast.StarExpr: typ := new(Pointer) typ.base = Typ[Invalid] // avoid nil base in invalid recursive type declaration - setDefType(def, typ) typ.base = check.varType(e.X) // If typ.base is invalid, it's unlikely that *base is particularly // useful - even a valid dereferenciation will lead to an invalid @@ -347,20 +338,16 @@ func (check *Checker) typInternal(e0 ast.Expr, def *TypeName) (T Type) { case *ast.FuncType: typ := new(Signature) - setDefType(def, typ) check.funcType(typ, nil, e) return typ case *ast.InterfaceType: typ := check.newInterface() - setDefType(def, typ) check.interfaceType(typ, e, def) return typ case *ast.MapType: typ := new(Map) - setDefType(def, typ) - typ.key = check.varType(e.Key) typ.elem = check.varType(e.Value) @@ -384,7 +371,6 @@ func (check *Checker) typInternal(e0 ast.Expr, def *TypeName) (T Type) { case *ast.ChanType: typ := new(Chan) - setDefType(def, typ) dir := SendRecv switch e.Dir { @@ -409,14 +395,10 @@ func (check *Checker) typInternal(e0 ast.Expr, def *TypeName) (T Type) { } typ := Typ[Invalid] - setDefType(def, typ) return typ } -// TODO(markfreeman): Remove this function. -func setDefType(def *TypeName, typ Type) {} - -func (check *Checker) instantiatedType(ix *indexedExpr, def *TypeName) (res Type) { +func (check *Checker) instantiatedType(ix *indexedExpr) (res Type) { if check.conf._Trace { check.trace(ix.Pos(), "-- instantiating type %s with %s", ix.x, ix.indices) check.indent++ @@ -427,10 +409,6 @@ func (check *Checker) instantiatedType(ix *indexedExpr, def *TypeName) (res Type }() } - defer func() { - setDefType(def, res) - }() - var cause string typ := check.genericType(ix.x, &cause) if cause != "" { From c048a9a11f21e879a76e328b7a92f3a47f298b37 Mon Sep 17 00:00:00 2001 From: Mark Freeman Date: Tue, 25 Nov 2025 13:56:18 -0500 Subject: [PATCH 084/140] go/types, types2: remove InvalidTypeCycle from literals.go Both CL 722161 and CL 724140 implement a more general solution to detecting cycles involving values of a type on the object path. The logic in literals.go was intended to be a stop-gap solution and is no longer necessary. Change-Id: I328c0febf35444f07fc1894278dc76ab140710bf Reviewed-on: https://go-review.googlesource.com/c/go/+/724380 LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Griesemer Auto-Submit: Mark Freeman --- src/cmd/compile/internal/types2/literals.go | 29 --------------------- src/go/types/literals.go | 29 --------------------- 2 files changed, 58 deletions(-) diff --git a/src/cmd/compile/internal/types2/literals.go b/src/cmd/compile/internal/types2/literals.go index 5b2dae9b13a..ed1c3f695c8 100644 --- a/src/cmd/compile/internal/types2/literals.go +++ b/src/cmd/compile/internal/types2/literals.go @@ -145,13 +145,6 @@ func (check *Checker) compositeLit(x *operand, e *syntax.CompositeLit, hint Type switch u, _ := commonUnder(base, nil); utyp := u.(type) { case *Struct: - // Prevent crash if the struct referred to is not yet set up. - // See analogous comment for *Array. - if utyp.fields == nil { - check.error(e, InvalidTypeCycle, "invalid recursive type") - x.mode = invalid - return - } if len(e.ElemList) == 0 { break } @@ -225,14 +218,6 @@ func (check *Checker) compositeLit(x *operand, e *syntax.CompositeLit, hint Type } case *Array: - // Prevent crash if the array referred to is not yet set up. Was go.dev/issue/18643. - // This is a stop-gap solution. Should use Checker.objPath to report entire - // path starting with earliest declaration in the source. TODO(gri) fix this. - if utyp.elem == nil { - check.error(e, InvalidTypeCycle, "invalid recursive type") - x.mode = invalid - return - } n := check.indexedElts(e.ElemList, utyp.elem, utyp.len) // If we have an array of unknown length (usually [...]T arrays, but also // arrays [n]T where n is invalid) set the length now that we know it and @@ -254,23 +239,9 @@ func (check *Checker) compositeLit(x *operand, e *syntax.CompositeLit, hint Type } case *Slice: - // Prevent crash if the slice referred to is not yet set up. - // See analogous comment for *Array. - if utyp.elem == nil { - check.error(e, InvalidTypeCycle, "invalid recursive type") - x.mode = invalid - return - } check.indexedElts(e.ElemList, utyp.elem, -1) case *Map: - // Prevent crash if the map referred to is not yet set up. - // See analogous comment for *Array. - if utyp.key == nil || utyp.elem == nil { - check.error(e, InvalidTypeCycle, "invalid recursive type") - x.mode = invalid - return - } // If the map key type is an interface (but not a type parameter), // the type of a constant key must be considered when checking for // duplicates. diff --git a/src/go/types/literals.go b/src/go/types/literals.go index df02b770364..7ca351e60b1 100644 --- a/src/go/types/literals.go +++ b/src/go/types/literals.go @@ -149,13 +149,6 @@ func (check *Checker) compositeLit(x *operand, e *ast.CompositeLit, hint Type) { switch u, _ := commonUnder(base, nil); utyp := u.(type) { case *Struct: - // Prevent crash if the struct referred to is not yet set up. - // See analogous comment for *Array. - if utyp.fields == nil { - check.error(e, InvalidTypeCycle, "invalid recursive type") - x.mode = invalid - return - } if len(e.Elts) == 0 { break } @@ -229,14 +222,6 @@ func (check *Checker) compositeLit(x *operand, e *ast.CompositeLit, hint Type) { } case *Array: - // Prevent crash if the array referred to is not yet set up. Was go.dev/issue/18643. - // This is a stop-gap solution. Should use Checker.objPath to report entire - // path starting with earliest declaration in the source. TODO(gri) fix this. - if utyp.elem == nil { - check.error(e, InvalidTypeCycle, "invalid recursive type") - x.mode = invalid - return - } n := check.indexedElts(e.Elts, utyp.elem, utyp.len) // If we have an array of unknown length (usually [...]T arrays, but also // arrays [n]T where n is invalid) set the length now that we know it and @@ -258,23 +243,9 @@ func (check *Checker) compositeLit(x *operand, e *ast.CompositeLit, hint Type) { } case *Slice: - // Prevent crash if the slice referred to is not yet set up. - // See analogous comment for *Array. - if utyp.elem == nil { - check.error(e, InvalidTypeCycle, "invalid recursive type") - x.mode = invalid - return - } check.indexedElts(e.Elts, utyp.elem, -1) case *Map: - // Prevent crash if the map referred to is not yet set up. - // See analogous comment for *Array. - if utyp.key == nil || utyp.elem == nil { - check.error(e, InvalidTypeCycle, "invalid recursive type") - x.mode = invalid - return - } // If the map key type is an interface (but not a type parameter), // the type of a constant key must be considered when checking for // duplicates. From c6d64f85565e6a934110c4928ca95fea0045ebaa Mon Sep 17 00:00:00 2001 From: Xiaolin Zhao Date: Fri, 21 Nov 2025 17:22:05 +0800 Subject: [PATCH 085/140] cmd/internal/obj/loong64: remove the incorrect unsigned instructions The loong64 ISA does not support the 32-bit unsigned arithmetic instructions ADDU, SUBU and MULU. Change-Id: Ifa67de9c59aa12d08844189ed23e6daad0cc11ea Reviewed-on: https://go-review.googlesource.com/c/go/+/722760 Reviewed-by: abner chenc LUCI-TryBot-Result: Go LUCI Reviewed-by: Dmitri Shuralyov Reviewed-by: Cherry Mui --- .../asm/internal/asm/testdata/loong64enc1.s | 2 -- .../asm/internal/asm/testdata/loong64enc2.s | 4 ---- .../asm/internal/asm/testdata/loong64enc3.s | 4 ---- src/cmd/internal/obj/loong64/a.out.go | 3 --- src/cmd/internal/obj/loong64/anames.go | 3 --- src/cmd/internal/obj/loong64/asm.go | 20 +++++-------------- src/cmd/internal/obj/loong64/obj.go | 7 ------- 7 files changed, 5 insertions(+), 38 deletions(-) diff --git a/src/cmd/asm/internal/asm/testdata/loong64enc1.s b/src/cmd/asm/internal/asm/testdata/loong64enc1.s index 42fa5058323..460a6ae265a 100644 --- a/src/cmd/asm/internal/asm/testdata/loong64enc1.s +++ b/src/cmd/asm/internal/asm/testdata/loong64enc1.s @@ -199,8 +199,6 @@ lable2: MOVHU R4, 1(R5) // a4044029 MOVHU y+8(FP), R4 // 6440402a MOVHU 1(R5), R4 // a404402a - MULU R4, R5 // a5101c00 - MULU R4, R5, R6 // a6101c00 MULH R4, R5 // a5901c00 MULH R4, R5, R6 // a6901c00 MULHU R4, R5 // a5101d00 diff --git a/src/cmd/asm/internal/asm/testdata/loong64enc2.s b/src/cmd/asm/internal/asm/testdata/loong64enc2.s index 0ac85f32252..38f50d2bfc2 100644 --- a/src/cmd/asm/internal/asm/testdata/loong64enc2.s +++ b/src/cmd/asm/internal/asm/testdata/loong64enc2.s @@ -41,10 +41,6 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$0 SGTU $4096, R4, R5 // 3e00001485f81200 SGTU $65536, R4 // 1e02001484f81200 SGTU $4096, R4 // 3e00001484f81200 - ADDU $65536, R4, R5 // 1e02001485781000 - ADDU $4096, R4, R5 // 3e00001485781000 - ADDU $65536, R4 // 1e02001484781000 - ADDU $4096, R4 // 3e00001484781000 ADDVU $65536, R4, R5 // 1e02001485f81000 ADDVU $4096, R4, R5 // 3e00001485f81000 ADDVU $65536, R4 // 1e02001484f81000 diff --git a/src/cmd/asm/internal/asm/testdata/loong64enc3.s b/src/cmd/asm/internal/asm/testdata/loong64enc3.s index c8fb1acb396..8b5f96bf4a2 100644 --- a/src/cmd/asm/internal/asm/testdata/loong64enc3.s +++ b/src/cmd/asm/internal/asm/testdata/loong64enc3.s @@ -111,10 +111,6 @@ TEXT asmtest(SB),DUPOK|NOSPLIT,$0 SGTU $74565, R4, R5 // 5e020014de178d0385f81200 SGTU $4097, R4 // 3e000014de07800384f81200 SGTU $4097, R4, R5 // 3e000014de07800385f81200 - ADDU $74565, R4 // 5e020014de178d0384781000 - ADDU $74565, R4, R5 // 5e020014de178d0385781000 - ADDU $4097, R4 // 3e000014de07800384781000 - ADDU $4097, R4, R5 // 3e000014de07800385781000 ADDVU $4097, R4 // 3e000014de07800384f81000 ADDVU $4097, R4, R5 // 3e000014de07800385f81000 ADDVU $74565, R4 // 5e020014de178d0384f81000 diff --git a/src/cmd/internal/obj/loong64/a.out.go b/src/cmd/internal/obj/loong64/a.out.go index 38d4b749590..2a3ead55ea6 100644 --- a/src/cmd/internal/obj/loong64/a.out.go +++ b/src/cmd/internal/obj/loong64/a.out.go @@ -429,7 +429,6 @@ const ( AADD AADDD AADDF - AADDU AADDW AAND @@ -495,7 +494,6 @@ const ( AMUL AMULD AMULF - AMULU AMULH AMULHU AMULW @@ -531,7 +529,6 @@ const ( ASUBD ASUBF - ASUBU ASUBW ADBAR ASYSCALL diff --git a/src/cmd/internal/obj/loong64/anames.go b/src/cmd/internal/obj/loong64/anames.go index b1fcbce196d..4fe9a35b276 100644 --- a/src/cmd/internal/obj/loong64/anames.go +++ b/src/cmd/internal/obj/loong64/anames.go @@ -10,7 +10,6 @@ var Anames = []string{ "ADD", "ADDD", "ADDF", - "ADDU", "ADDW", "AND", "BEQ", @@ -63,7 +62,6 @@ var Anames = []string{ "MUL", "MULD", "MULF", - "MULU", "MULH", "MULHU", "MULW", @@ -92,7 +90,6 @@ var Anames = []string{ "SUB", "SUBD", "SUBF", - "SUBU", "SUBW", "DBAR", "SYSCALL", diff --git a/src/cmd/internal/obj/loong64/asm.go b/src/cmd/internal/obj/loong64/asm.go index e5f2014e956..3fcd6eafd7c 100644 --- a/src/cmd/internal/obj/loong64/asm.go +++ b/src/cmd/internal/obj/loong64/asm.go @@ -1431,7 +1431,6 @@ func buildop(ctxt *obj.Link) { opset(AADDW, r0) opset(ASGT, r0) opset(ASGTU, r0) - opset(AADDU, r0) case AADDV: opset(AADDVU, r0) @@ -1514,13 +1513,11 @@ func buildop(ctxt *obj.Link) { case ASUB: opset(ASUBW, r0) - opset(ASUBU, r0) opset(ANOR, r0) opset(ASUBV, r0) opset(ASUBVU, r0) opset(AMUL, r0) opset(AMULW, r0) - opset(AMULU, r0) opset(AMULH, r0) opset(AMULHU, r0) opset(AREM, r0) @@ -2296,8 +2293,7 @@ func (c *ctxt0) asmout(p *obj.Prog, o *Optab, out []uint32) { o5 := uint32(0) o6 := uint32(0) - add := AADDU - add = AADDVU + add := AADDVU switch o.type_ { default: @@ -2428,7 +2424,7 @@ func (c *ctxt0) asmout(p *obj.Prog, o *Optab, out []uint32) { v := c.regoff(&p.From) a := AOR if v < 0 { - a = AADDU + a = AADD } o1 = OP_12IRR(c.opirr(a), uint32(v), uint32(0), uint32(REGTMP)) r := int(p.Reg) @@ -2687,7 +2683,7 @@ func (c *ctxt0) asmout(p *obj.Prog, o *Optab, out []uint32) { case 34: // mov $con,fr v := c.regoff(&p.From) - a := AADDU + a := AADD if v > 0 { a = AOR } @@ -3317,8 +3313,6 @@ func (c *ctxt0) oprrr(a obj.As) uint32 { switch a { case AADD, AADDW: return 0x20 << 15 - case AADDU: - return 0x20 << 15 case ASGT: return 0x24 << 15 // SLT case ASGTU: @@ -3337,9 +3331,7 @@ func (c *ctxt0) oprrr(a obj.As) uint32 { return 0x2c << 15 // orn case AANDN: return 0x2d << 15 // andn - case ASUB, ASUBW: - return 0x22 << 15 - case ASUBU, ANEGW: + case ASUB, ASUBW, ANEGW: return 0x22 << 15 case ANOR: return 0x28 << 15 @@ -3370,8 +3362,6 @@ func (c *ctxt0) oprrr(a obj.As) uint32 { case AMUL, AMULW: return 0x38 << 15 // mul.w - case AMULU: - return 0x38 << 15 // mul.w case AMULH: return 0x39 << 15 // mulh.w case AMULHU: @@ -4684,7 +4674,7 @@ func (c *ctxt0) opir(a obj.As) uint32 { func (c *ctxt0) opirr(a obj.As) uint32 { switch a { - case AADD, AADDW, AADDU: + case AADD, AADDW: return 0x00a << 22 case ASGT: return 0x008 << 22 diff --git a/src/cmd/internal/obj/loong64/obj.go b/src/cmd/internal/obj/loong64/obj.go index a97217d3165..51a28d130c6 100644 --- a/src/cmd/internal/obj/loong64/obj.go +++ b/src/cmd/internal/obj/loong64/obj.go @@ -64,12 +64,6 @@ func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) { p.As = AADD } - case ASUBU: - if p.From.Type == obj.TYPE_CONST { - p.From.Offset = -p.From.Offset - p.As = AADDU - } - case ASUBV: if p.From.Type == obj.TYPE_CONST { p.From.Offset = -p.From.Offset @@ -453,7 +447,6 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { q.Link = q1 case AADD, - AADDU, AADDV, AADDVU: if p.To.Type == obj.TYPE_REG && p.To.Reg == REGSP && p.From.Type == obj.TYPE_CONST { From d8269ab0d59212fed0f5975f7083f6bbbfc00ec4 Mon Sep 17 00:00:00 2001 From: limeidan Date: Mon, 24 Nov 2025 17:28:42 +0800 Subject: [PATCH 086/140] cmd/link, cmd/internal/obj: fix a remote call failure issue When a function call exceeds the immediate value range of the instruction, a trampoline is required to assist in the jump. Trampoline is only omitted when plt is needed; otherwise, a check is required. Change-Id: I7fe2e08d75f6f574475837b560e650bbd4215858 Reviewed-on: https://go-review.googlesource.com/c/go/+/724580 Reviewed-by: abner chenc LUCI-TryBot-Result: Go LUCI Reviewed-by: sophie zhao Reviewed-by: Cherry Mui Reviewed-by: Dmitri Shuralyov --- src/cmd/internal/obj/loong64/asm.go | 3 +++ src/cmd/link/internal/loong64/asm.go | 13 ++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/cmd/internal/obj/loong64/asm.go b/src/cmd/internal/obj/loong64/asm.go index 3fcd6eafd7c..f9925180153 100644 --- a/src/cmd/internal/obj/loong64/asm.go +++ b/src/cmd/internal/obj/loong64/asm.go @@ -2437,6 +2437,9 @@ func (c *ctxt0) asmout(p *obj.Prog, o *Optab, out []uint32) { v := int32(0) if p.To.Target() != nil { v = int32(p.To.Target().Pc-p.Pc) >> 2 + if v < -1<<25 || v >= 1<<25 { + c.ctxt.Diag("branch too far \n%v", p) + } } o1 = OP_B_BL(c.opirr(p.As), uint32(v)) if p.To.Sym != nil { diff --git a/src/cmd/link/internal/loong64/asm.go b/src/cmd/link/internal/loong64/asm.go index 219cfc7196a..142578bcebf 100644 --- a/src/cmd/link/internal/loong64/asm.go +++ b/src/cmd/link/internal/loong64/asm.go @@ -643,11 +643,14 @@ func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) { relocs := ldr.Relocs(s) r := relocs.At(ri) switch r.Type() { - case objabi.ElfRelocOffset + objabi.RelocType(elf.R_LARCH_B26): - // Nothing to do. - // The plt symbol has not been added. If we add tramp - // here, plt will not work. - case objabi.R_CALLLOONG64: + case objabi.ElfRelocOffset + objabi.RelocType(elf.R_LARCH_B26), objabi.R_CALLLOONG64: + if ldr.SymType(rs) == sym.SDYNIMPORT { + // Nothing to do. + // The plt symbol has not been added. If we add tramp + // here, plt will not work. + return + } + var t int64 // ldr.SymValue(rs) == 0 indicates a cross-package jump to a function that is not yet // laid out. Conservatively use a trampoline. This should be rare, as we lay out packages From 4879151d1dc9f951e4598bd433cd2142976ed39d Mon Sep 17 00:00:00 2001 From: thepudds Date: Tue, 25 Nov 2025 09:57:50 -0500 Subject: [PATCH 087/140] cmd/compile: introduce alias analysis and automatically free non-aliased memory after growslice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This CL is part of a set of CLs that attempt to reduce how much work the GC must do. See the design in https://go.dev/design/74299-runtime-freegc This CL updates the compiler to examine append calls to prove whether or not the slice is aliased. If proven unaliased, the compiler automatically inserts a call to a new runtime function introduced with this CL, runtime.growsliceNoAlias, which frees the old backing memory immediately after slice growth is complete and the old storage is logically dead. Two append benchmarks below show promising results, executing up to ~2x faster and up to factor of ~3 memory reduction with this CL. The approach works with multiple append calls for the same slice, including inside loops, and the final slice memory can be escaping, such as in a classic pattern of returning a slice from a function after the slice is built. (The final slice memory is never freed with this CL, though we have other work that tackles that.) An example target for this CL is we automatically free the intermediate memory for the appends in the loop in this function: func f1(input []int) []int { var s []int for _, x := range input { s = append(s, g(x)) // s cannot be aliased here if h(x) { s = append(s, x) // s cannot be aliased here } } return s // slice escapes at end } In this case, the compiler and the runtime collaborate so that the heap allocated backing memory for s is automatically freed after a successful grow. (For the first grow, there is nothing to free, but for the second and subsequent growths, the old heap memory is freed automatically.) The new runtime.growsliceNoAlias is primarily implemented by calling runtime.freegc, which we introduced in CL 673695. The high-level approach here is we step through the IR starting from a slice declaration and look for any operations that either alias the slice or might do so, and treat any IR construct we don't specifically handle as a potential alias (and therefore conservatively fall back to treating the slice as aliased when encountering something not understood). For loops, some additional care is required. We arrange the analysis so that an alias in the body of a loop causes all the appends in that same loop body to be marked aliased, even if the aliasing occurs after the append in the IR: func f2() { var s []int for i := range 10 { s = append(s, i) // aliased due to next line alias = s } } For nested loops, we analyse the nesting appropriately so that for example this append is still proven as non-aliased in the inner loop even though it aliased for the outer loop: func f3() { for range 10 { var s []int for i := range 10 { s = append(s, i) // append using non-aliased slice } alias = s } } A good starting point is the beginning of the test/escape_alias.go file, which starts with ~10 introductory examples with brief comments that attempt to illustrate the high-level approach. For more details, see the new .../internal/escape/alias.go file, especially the (*aliasAnalysis).analyze method. In the first benchmark, an append in a loop builds up a slice from nothing, where the slice elements are each 64 bytes. In the table below, 'count' is the number of appends. With 1 append, there is no opportunity for this CL to free memory. Once there are 2 appends, the growth from 1 element to 2 elements means the compiler-inserted growsliceNoAlias frees the 1-element array, and we see a ~33% reduction in memory use and a small reported speed improvement. As the number of appends increases for example to 5, we are at a ~20% speed improvement and ~45% memory reduction, and so on until we reach ~40% faster and ~50% less memory allocated at the end of the table. There can be variation in the reported numbers based on -randlayout, so this table is for 30 different values of -randlayout with a total n=150. (Even so, there is still some variation, so we probably should not read too much into small changes.) This is with GOAMD64=v3 on a VM that gcc reports is cascadelake. goos: linux goarch: amd64 pkg: runtime cpu: Intel(R) Xeon(R) CPU @ 2.80GHz │ old-1bb1f2bf0c │ freegc-8ba7421-ps16 │ │ sec/op │ sec/op vs base │ Append64Bytes/count=1-4 31.09n ± 2% 31.69n ± 1% +1.95% (n=150) Append64Bytes/count=2-4 73.31n ± 1% 70.27n ± 0% -4.15% (n=150) Append64Bytes/count=3-4 142.7n ± 1% 124.6n ± 1% -12.68% (n=150) Append64Bytes/count=4-4 149.6n ± 1% 127.7n ± 0% -14.64% (n=150) Append64Bytes/count=5-4 277.1n ± 1% 213.6n ± 0% -22.90% (n=150) Append64Bytes/count=6-4 280.7n ± 1% 216.5n ± 1% -22.87% (n=150) Append64Bytes/count=10-4 544.3n ± 1% 386.6n ± 0% -28.97% (n=150) Append64Bytes/count=20-4 1058.5n ± 1% 715.6n ± 1% -32.39% (n=150) Append64Bytes/count=50-4 2.121µ ± 1% 1.404µ ± 1% -33.83% (n=150) Append64Bytes/count=100-4 4.152µ ± 1% 2.736µ ± 1% -34.11% (n=150) Append64Bytes/count=200-4 7.753µ ± 1% 4.882µ ± 1% -37.03% (n=150) Append64Bytes/count=400-4 15.163µ ± 2% 9.273µ ± 1% -38.84% (n=150) geomean 601.8n 455.0n -24.39% │ old-1bb1f2bf0c │ freegc-8ba7421-ps16 │ │ B/op │ B/op vs base │ Append64Bytes/count=1-4 64.00 ± 0% 64.00 ± 0% ~ (n=150) Append64Bytes/count=2-4 192.0 ± 0% 128.0 ± 0% -33.33% (n=150) Append64Bytes/count=3-4 448.0 ± 0% 256.0 ± 0% -42.86% (n=150) Append64Bytes/count=4-4 448.0 ± 0% 256.0 ± 0% -42.86% (n=150) Append64Bytes/count=5-4 960.0 ± 0% 512.0 ± 0% -46.67% (n=150) Append64Bytes/count=6-4 960.0 ± 0% 512.0 ± 0% -46.67% (n=150) Append64Bytes/count=10-4 1.938Ki ± 0% 1.000Ki ± 0% -48.39% (n=150) Append64Bytes/count=20-4 3.938Ki ± 0% 2.001Ki ± 0% -49.18% (n=150) Append64Bytes/count=50-4 7.938Ki ± 0% 4.005Ki ± 0% -49.54% (n=150) Append64Bytes/count=100-4 15.938Ki ± 0% 8.021Ki ± 0% -49.67% (n=150) Append64Bytes/count=200-4 31.94Ki ± 0% 16.08Ki ± 0% -49.64% (n=150) Append64Bytes/count=400-4 63.94Ki ± 0% 32.33Ki ± 0% -49.44% (n=150) geomean 1.991Ki 1.124Ki -43.54% │ old-1bb1f2bf0c │ freegc-8ba7421-ps16 │ │ allocs/op │ allocs/op vs base │ Append64Bytes/count=1-4 1.000 ± 0% 1.000 ± 0% ~ (n=150) Append64Bytes/count=2-4 2.000 ± 0% 1.000 ± 0% -50.00% (n=150) Append64Bytes/count=3-4 3.000 ± 0% 1.000 ± 0% -66.67% (n=150) Append64Bytes/count=4-4 3.000 ± 0% 1.000 ± 0% -66.67% (n=150) Append64Bytes/count=5-4 4.000 ± 0% 1.000 ± 0% -75.00% (n=150) Append64Bytes/count=6-4 4.000 ± 0% 1.000 ± 0% -75.00% (n=150) Append64Bytes/count=10-4 5.000 ± 0% 1.000 ± 0% -80.00% (n=150) Append64Bytes/count=20-4 6.000 ± 0% 1.000 ± 0% -83.33% (n=150) Append64Bytes/count=50-4 7.000 ± 0% 1.000 ± 0% -85.71% (n=150) Append64Bytes/count=100-4 8.000 ± 0% 1.000 ± 0% -87.50% (n=150) Append64Bytes/count=200-4 9.000 ± 0% 1.000 ± 0% -88.89% (n=150) Append64Bytes/count=400-4 10.000 ± 0% 1.000 ± 0% -90.00% (n=150) geomean 4.331 1.000 -76.91% The second benchmark is similar, but instead uses an 8-byte integer for the slice element. The first 4 appends in the loop never call into the runtime thanks to the excellent CL 664299 introduced by Keith in Go 1.25 that allows some <= 32 byte dynamically-sized slices to be on the stack, so this CL is neutral for <= 32 bytes. Once the 5th append occurs at count=5, a grow happens via the runtime and heap allocates as normal, but freegc does not yet have anything to free, so we see a small ~1.4ns penalty reported there. But once the second growth happens, the older heap memory is now automatically freed by freegc, so we start to see some benefit in memory reductions and speed improvements, starting at a tiny speed improvement (close to a wash, or maybe noise) by the second growth before count=10, and building up to ~2x faster with ~68% fewer allocated bytes reported. goos: linux goarch: amd64 pkg: runtime cpu: Intel(R) Xeon(R) CPU @ 2.80GHz │ old-1bb1f2bf0c │ freegc-8ba7421-ps16 │ │ sec/op │ sec/op vs base │ AppendInt/count=1-4 2.978n ± 0% 2.969n ± 0% -0.30% (p=0.000 n=150) AppendInt/count=4-4 4.292n ± 3% 4.163n ± 3% ~ (p=0.528 n=150) AppendInt/count=5-4 33.50n ± 0% 34.93n ± 0% +4.25% (p=0.000 n=150) AppendInt/count=10-4 76.21n ± 1% 75.67n ± 0% -0.72% (p=0.000 n=150) AppendInt/count=20-4 150.6n ± 1% 133.0n ± 0% -11.65% (n=150) AppendInt/count=50-4 284.1n ± 1% 225.6n ± 0% -20.59% (n=150) AppendInt/count=100-4 544.2n ± 1% 392.4n ± 1% -27.89% (n=150) AppendInt/count=200-4 1051.5n ± 1% 702.3n ± 0% -33.21% (n=150) AppendInt/count=400-4 2.041µ ± 1% 1.312µ ± 1% -35.70% (n=150) AppendInt/count=1000-4 5.224µ ± 2% 2.851µ ± 1% -45.43% (n=150) AppendInt/count=2000-4 11.770µ ± 1% 6.010µ ± 1% -48.94% (n=150) AppendInt/count=3000-4 17.747µ ± 2% 8.264µ ± 1% -53.44% (n=150) geomean 331.8n 246.4n -25.72% │ old-1bb1f2bf0c │ freegc-8ba7421-ps16 │ │ B/op │ B/op vs base │ AppendInt/count=1-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=150) AppendInt/count=4-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=150) AppendInt/count=5-4 64.00 ± 0% 64.00 ± 0% ~ (p=1.000 n=150) AppendInt/count=10-4 192.0 ± 0% 128.0 ± 0% -33.33% (n=150) AppendInt/count=20-4 448.0 ± 0% 256.0 ± 0% -42.86% (n=150) AppendInt/count=50-4 960.0 ± 0% 512.0 ± 0% -46.67% (n=150) AppendInt/count=100-4 1.938Ki ± 0% 1.000Ki ± 0% -48.39% (n=150) AppendInt/count=200-4 3.938Ki ± 0% 2.001Ki ± 0% -49.18% (n=150) AppendInt/count=400-4 7.938Ki ± 0% 4.005Ki ± 0% -49.54% (n=150) AppendInt/count=1000-4 24.56Ki ± 0% 10.05Ki ± 0% -59.07% (n=150) AppendInt/count=2000-4 58.56Ki ± 0% 20.31Ki ± 0% -65.32% (n=150) AppendInt/count=3000-4 85.19Ki ± 0% 27.30Ki ± 0% -67.95% (n=150) geomean ² -42.81% │ old-1bb1f2bf0c │ freegc-8ba7421-ps16 │ │ allocs/op │ allocs/op vs base │ AppendInt/count=1-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=150) AppendInt/count=4-4 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=150) AppendInt/count=5-4 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=150) AppendInt/count=10-4 2.000 ± 0% 1.000 ± 0% -50.00% (n=150) AppendInt/count=20-4 3.000 ± 0% 1.000 ± 0% -66.67% (n=150) AppendInt/count=50-4 4.000 ± 0% 1.000 ± 0% -75.00% (n=150) AppendInt/count=100-4 5.000 ± 0% 1.000 ± 0% -80.00% (n=150) AppendInt/count=200-4 6.000 ± 0% 1.000 ± 0% -83.33% (n=150) AppendInt/count=400-4 7.000 ± 0% 1.000 ± 0% -85.71% (n=150) AppendInt/count=1000-4 9.000 ± 0% 1.000 ± 0% -88.89% (n=150) AppendInt/count=2000-4 11.000 ± 0% 1.000 ± 0% -90.91% (n=150) AppendInt/count=3000-4 12.000 ± 0% 1.000 ± 0% -91.67% (n=150) geomean ² -72.76% ² Of course, these are just microbenchmarks, but likely indicate there are some opportunities here. The immediately following CL 712422 tackles inlining and is able to get runtime.freegc working automatically with iterators such as used by slices.Collect, which becomes able to automatically free the intermediate memory from its repeated appends (which earlier in this work required a temporary hand edit to the slices package). For now, we only use the NoAlias version for element types without pointers while waiting on additional runtime support in CL 698515. Updates #74299 Change-Id: I1b9d286aa97c170dcc2e203ec0f8ca72d84e8221 Reviewed-on: https://go-review.googlesource.com/c/go/+/710015 Reviewed-by: Keith Randall Auto-Submit: Keith Randall Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: Go LUCI Reviewed-by: Keith Randall --- src/cmd/compile/internal/base/debug.go | 3 + src/cmd/compile/internal/base/flag.go | 1 + src/cmd/compile/internal/escape/alias.go | 528 ++++++++++++ src/cmd/compile/internal/escape/escape.go | 11 + .../inline/interleaved/interleaved.go | 7 + src/cmd/compile/internal/ir/expr.go | 19 +- src/cmd/compile/internal/ir/symtab.go | 2 + .../compile/internal/ssa/_gen/generic.rules | 9 +- .../compile/internal/ssa/rewritegeneric.go | 16 +- src/cmd/compile/internal/ssagen/ssa.go | 23 +- src/cmd/compile/internal/test/free_test.go | 55 ++ .../internal/typecheck/_builtin/runtime.go | 2 + src/cmd/compile/internal/typecheck/builtin.go | 2 + src/runtime/slice.go | 56 ++ src/runtime/slice_test.go | 19 + test/codegen/append.go | 91 +- test/codegen/append_freegc.go | 217 +++++ test/escape_alias.go | 779 ++++++++++++++++++ 18 files changed, 1785 insertions(+), 55 deletions(-) create mode 100644 src/cmd/compile/internal/escape/alias.go create mode 100644 src/cmd/compile/internal/test/free_test.go create mode 100644 test/codegen/append_freegc.go create mode 100644 test/escape_alias.go diff --git a/src/cmd/compile/internal/base/debug.go b/src/cmd/compile/internal/base/debug.go index e32a07d4617..5e0268bb881 100644 --- a/src/cmd/compile/internal/base/debug.go +++ b/src/cmd/compile/internal/base/debug.go @@ -32,9 +32,12 @@ type DebugFlags struct { DwarfInl int `help:"print information about DWARF inlined function creation"` EscapeMutationsCalls int `help:"print extra escape analysis diagnostics about mutations and calls" concurrent:"ok"` EscapeDebug int `help:"print information about escape analysis and resulting optimizations" concurrent:"ok"` + EscapeAlias int `help:"print information about alias analysis" concurrent:"ok"` + EscapeAliasCheck int `help:"enable additional validation for alias analysis" concurrent:"ok"` Export int `help:"print export data"` FIPSHash string `help:"hash value for FIPS debugging" concurrent:"ok"` Fmahash string `help:"hash value for use in debugging platform-dependent multiply-add use" concurrent:"ok"` + FreeAppend int `help:"insert frees of append results when proven safe (0 disabled, 1 enabled, 2 enabled + log)" concurrent:"ok"` GCAdjust int `help:"log adjustments to GOGC" concurrent:"ok"` GCCheck int `help:"check heap/gc use by compiler" concurrent:"ok"` GCProg int `help:"print dump of GC programs"` diff --git a/src/cmd/compile/internal/base/flag.go b/src/cmd/compile/internal/base/flag.go index 63cae41524c..4a2b7c5434f 100644 --- a/src/cmd/compile/internal/base/flag.go +++ b/src/cmd/compile/internal/base/flag.go @@ -182,6 +182,7 @@ func ParseFlags() { Debug.AlignHot = 1 Debug.InlFuncsWithClosures = 1 Debug.InlStaticInit = 1 + Debug.FreeAppend = 1 Debug.PGOInline = 1 Debug.PGODevirtualize = 2 Debug.SyncFrames = -1 // disable sync markers by default diff --git a/src/cmd/compile/internal/escape/alias.go b/src/cmd/compile/internal/escape/alias.go new file mode 100644 index 00000000000..f0351e8f671 --- /dev/null +++ b/src/cmd/compile/internal/escape/alias.go @@ -0,0 +1,528 @@ +// Copyright 2025 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 escape + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/internal/src" + "fmt" + "maps" + "path/filepath" +) + +type aliasAnalysis struct { + // fn is the function being analyzed. + fn *ir.Func + + // candidateSlices are declared slices that + // start unaliased and might still be unaliased. + candidateSlices map[*ir.Name]candidateSlice + + // noAliasAppends are appends that have been + // proven to use an unaliased slice. + noAliasAppends []*ir.CallExpr + + // loops is a stack of observed loops, + // each with a list of candidate appends. + loops [][]candidateAppend + + // State for optional validation checking (doubleCheck mode): + processed map[ir.Node]int // count of times each node was processed, for doubleCheck mode + doubleCheck bool // whether to do doubleCheck mode +} + +// candidateSlice tracks information about a declared slice +// that might be unaliased. +type candidateSlice struct { + loopDepth int // depth of loop when slice was declared +} + +// candidateAppend tracks information about an OAPPEND that +// might be using an unaliased slice. +type candidateAppend struct { + s *ir.Name // the slice argument in 's = append(s, ...)' + call *ir.CallExpr // the append call +} + +// aliasAnalysis looks for specific patterns of slice usage and proves +// that certain appends are operating on non-aliased slices. +// +// This allows us to emit calls to free the backing arrays for certain +// non-aliased slices at runtime when we know the memory is logically dead. +// +// The analysis is conservative, giving up on any operation we do not +// explicitly understand. +func (aa *aliasAnalysis) analyze(fn *ir.Func) { + // Walk the function body to discover slice declarations, their uses, + // and any append that we can prove is using an unaliased slice. + // + // An example is: + // + // var s []T + // for _, v := range input { + // f() + // s = append(s, g(v)) // s cannot be aliased here + // h() + // } + // return s + // + // Here, we can prove that the append to s is operating on an unaliased slice, + // and that conclusion is unaffected by s later being returned and escaping. + // + // In contrast, in this example, the aliasing of s in the loop body means the + // append can be operating on an aliased slice, so we do not record s as unaliased: + // + // var s []T + // var alias []T + // for _, v := range input { + // s = append(s, v) // s is aliased on second pass through loop body + // alias = s + // } + // + // Arbitrary uses of s after an append do not affect the aliasing conclusion + // for that append, but only if the append cannot be revisited at execution time + // via a loop or goto. + // + // We track the loop depth when a slice was declared and verify all uses of a slice + // are non-aliasing until we return to that depth. In other words, we make sure + // we have processed any possible execution-time revisiting of the slice prior + // to making our final determination. + // + // This approach helps for example with nested loops, such as: + // + // var s []int + // for range 10 { + // for range 10 { + // s = append(s, 0) // s is proven as non-aliased here + // } + // } + // alias = s // both loops are complete + // + // Or in contrast: + // + // var s []int + // for range 10 { + // for range 10 { + // s = append(s, 0) // s is treated as aliased here + // } + // alias = s // aliased, and outermost loop cycles back + // } + // + // As we walk the function, we look for things like: + // + // 1. Slice declarations (currently supporting 'var s []T', 's := make([]T, ...)', + // and 's := []T{...}'). + // 2. Appends to a slice of the form 's = append(s, ...)'. + // 3. Other uses of the slice, which we treat as potential aliasing outside + // of a few known safe cases. + // 4. A start of a loop, which we track in a stack so that + // any uses of a slice within a loop body are treated as potential + // aliasing, including statements in the loop body after an append. + // Candidate appends are stored in the loop stack at the loop depth of their + // corresponding slice declaration (rather than the loop depth of the append), + // which essentially postpones a decision about the candidate append. + // 5. An end of a loop, which pops the loop stack and allows us to + // conclusively treat candidate appends from the loop body based + // on the loop depth of the slice declaration. + // + // Note that as we pop a candidate append at the end of a loop, we know + // its corresponding slice was unaliased throughout the loop being popped + // if the slice is still in the candidate slice map (without having been + // removed for potential aliasing), and we know we can make a final decision + // about a candidate append if we have returned to the loop depth + // where its slice was declared. In other words, there is no unanalyzed + // control flow that could take us back at execution-time to the + // candidate append in the now analyzed loop. This helps for example + // with nested loops, such as in our examples just above. + // + // We give up on a particular candidate slice if we see any use of it + // that we don't explicitly understand, and we give up on all of + // our candidate slices if we see any goto or label, which could be + // unstructured control flow. (TODO(thepudds): we remove the goto/label + // restriction in a subsequent CL.) + // + // Note that the intended use is to indicate that a slice is safe to pass + // to runtime.freegc, which currently requires that the passed pointer + // point to the base of its heap object. + // + // Therefore, we currently do not allow any re-slicing of the slice, though we could + // potentially allow s[0:x] or s[:x] or similar. (Slice expressions that alter + // the capacity might be possible to allow with freegc changes, though they are + // currently disallowed here like all slice expressions). + // + // TODO(thepudds): we could support the slice being used as non-escaping function call parameter + // but to do that, we need to verify any creation of specials via user code triggers an escape, + // or mail better runtime.freegc support for specials, or have a temporary compile-time solution + // for specials. (Currently, this analysis side-steps specials because any use of a slice + // that might cause a user-created special will cause it to be treated as aliased, and + // separately, runtime.freegc handles profiling-related specials). + + // Initialize. + aa.fn = fn + aa.candidateSlices = make(map[*ir.Name]candidateSlice) // slices that might be unaliased + + // doubleCheck controls whether we do a sanity check of our processing logic + // by counting each node visited in our main pass, and then comparing those counts + // against a simple walk at the end. The main intent is to help catch missing + // any nodes squirreled away in some spot we forgot to examine in our main pass. + aa.doubleCheck = base.Debug.EscapeAliasCheck > 0 + aa.processed = make(map[ir.Node]int) + + if base.Debug.EscapeAlias >= 2 { + aa.diag(fn.Pos(), fn, "====== starting func", "======") + } + + ir.DoChildren(fn, aa.visit) + + for _, call := range aa.noAliasAppends { + if base.Debug.EscapeAlias >= 1 { + base.WarnfAt(call.Pos(), "alias analysis: append using non-aliased slice: %v in func %v", + call, fn) + } + if base.Debug.FreeAppend > 0 { + call.AppendNoAlias = true + } + } + + if aa.doubleCheck { + doubleCheckProcessed(fn, aa.processed) + } +} + +func (aa *aliasAnalysis) visit(n ir.Node) bool { + if n == nil { + return false + } + + if base.Debug.EscapeAlias >= 3 { + fmt.Printf("%-25s alias analysis: visiting node: %12s %-18T %v\n", + fmtPosShort(n.Pos())+":", n.Op().String(), n, n) + } + + // As we visit nodes, we want to ensure we handle all children + // without missing any (through ignorance or future changes). + // We do this by counting nodes as we visit them or otherwise + // declare a node to be fully processed. + // + // In particular, we want to ensure we don't miss the use + // of a slice in some expression that might be an aliasing usage. + // + // When doubleCheck is enabled, we compare the counts + // accumulated in our analysis against counts from a trivial walk, + // failing if there is any mismatch. + // + // This call here counts that we have visited this node n + // via our main visit method. (In contrast, some nodes won't + // be visited by the main visit method, but instead will be + // manually marked via countProcessed when we believe we have fully + // dealt with the node). + aa.countProcessed(n) + + switch n.Op() { + case ir.ODCL: + decl := n.(*ir.Decl) + + if decl.X != nil && decl.X.Type().IsSlice() && decl.X.Class == ir.PAUTO { + s := decl.X + if _, ok := aa.candidateSlices[s]; ok { + base.FatalfAt(n.Pos(), "candidate slice already tracked as candidate: %v", s) + } + if base.Debug.EscapeAlias >= 2 { + aa.diag(n.Pos(), s, "adding candidate slice", "(loop depth: %d)", len(aa.loops)) + } + aa.candidateSlices[s] = candidateSlice{loopDepth: len(aa.loops)} + } + // No children aside from the declared ONAME. + aa.countProcessed(decl.X) + return false + + case ir.ONAME: + + // We are seeing a name we have not already handled in another case, + // so remove any corresponding candidate slice. + if n.Type().IsSlice() { + name := n.(*ir.Name) + _, ok := aa.candidateSlices[name] + if ok { + delete(aa.candidateSlices, name) + if base.Debug.EscapeAlias >= 2 { + aa.diag(n.Pos(), name, "removing candidate slice", "") + } + } + } + // No children. + return false + + case ir.OAS2: + n := n.(*ir.AssignListStmt) + aa.analyzeAssign(n, n.Lhs, n.Rhs) + return false + + case ir.OAS: + assign := n.(*ir.AssignStmt) + aa.analyzeAssign(n, []ir.Node{assign.X}, []ir.Node{assign.Y}) + return false + + case ir.OFOR, ir.ORANGE: + aa.visitList(n.Init()) + + if n.Op() == ir.ORANGE { + // TODO(thepudds): previously we visited this range expression + // in the switch just below, after pushing the loop. This current placement + // is more correct, but generate a test or find an example in stdlib or similar + // where it matters. (Our current tests do not complain.) + aa.visit(n.(*ir.RangeStmt).X) + } + + // Push a new loop. + aa.loops = append(aa.loops, nil) + + // Process the loop. + switch n.Op() { + case ir.OFOR: + forstmt := n.(*ir.ForStmt) + aa.visit(forstmt.Cond) + aa.visitList(forstmt.Body) + aa.visit(forstmt.Post) + case ir.ORANGE: + rangestmt := n.(*ir.RangeStmt) + aa.visit(rangestmt.Key) + aa.visit(rangestmt.Value) + aa.visitList(rangestmt.Body) + default: + base.Fatalf("loop not OFOR or ORANGE: %v", n) + } + + // Pop the loop. + var candidateAppends []candidateAppend + candidateAppends, aa.loops = aa.loops[len(aa.loops)-1], aa.loops[:len(aa.loops)-1] + for _, a := range candidateAppends { + // We are done with the loop, so we can validate any candidate appends + // that have not had their slice removed yet. We know a slice is unaliased + // throughout the loop if the slice is still in the candidate slice map. + if cs, ok := aa.candidateSlices[a.s]; ok { + if cs.loopDepth == len(aa.loops) { + // We've returned to the loop depth where the slice was declared and + // hence made it all the way through any loops that started after + // that declaration. + if base.Debug.EscapeAlias >= 2 { + aa.diag(n.Pos(), a.s, "proved non-aliased append", + "(completed loop, decl at depth: %d)", cs.loopDepth) + } + aa.noAliasAppends = append(aa.noAliasAppends, a.call) + } else if cs.loopDepth < len(aa.loops) { + if base.Debug.EscapeAlias >= 2 { + aa.diag(n.Pos(), a.s, "cannot prove non-aliased append", + "(completed loop, decl at depth: %d)", cs.loopDepth) + } + } else { + panic("impossible: candidate slice loopDepth > current loop depth") + } + } + } + return false + + case ir.OLEN, ir.OCAP: + n := n.(*ir.UnaryExpr) + if n.X.Op() == ir.ONAME { + // This does not disqualify a candidate slice. + aa.visitList(n.Init()) + aa.countProcessed(n.X) + } else { + ir.DoChildren(n, aa.visit) + } + return false + + case ir.OCLOSURE: + // Give up on all our in-progress slices. + closure := n.(*ir.ClosureExpr) + if base.Debug.EscapeAlias >= 2 { + aa.diag(n.Pos(), closure.Func, "clearing all in-progress slices due to OCLOSURE", + "(was %d in-progress slices)", len(aa.candidateSlices)) + } + clear(aa.candidateSlices) + return ir.DoChildren(n, aa.visit) + + case ir.OLABEL, ir.OGOTO: + // Give up on all our in-progress slices. + if base.Debug.EscapeAlias >= 2 { + aa.diag(n.Pos(), n, "clearing all in-progress slices due to label or goto", + "(was %d in-progress slices)", len(aa.candidateSlices)) + } + clear(aa.candidateSlices) + return false + + default: + return ir.DoChildren(n, aa.visit) + } +} + +func (aa *aliasAnalysis) visitList(nodes []ir.Node) { + for _, n := range nodes { + aa.visit(n) + } +} + +// analyzeAssign evaluates the assignment dsts... = srcs... +// +// assign is an *ir.AssignStmt or *ir.AssignListStmt. +func (aa *aliasAnalysis) analyzeAssign(assign ir.Node, dsts, srcs []ir.Node) { + aa.visitList(assign.Init()) + for i := range dsts { + dst := dsts[i] + src := srcs[i] + + if dst.Op() != ir.ONAME || !dst.Type().IsSlice() { + // Nothing for us to do aside from visiting the remaining children. + aa.visit(dst) + aa.visit(src) + continue + } + + // We have a slice being assigned to an ONAME. + + // Check for simple zero value assignments to an ONAME, which we ignore. + if src == nil { + aa.countProcessed(dst) + continue + } + + if base.Debug.EscapeAlias >= 4 { + srcfn := "" + if src.Op() == ir.ONAME { + srcfn = fmt.Sprintf("%v.", src.Name().Curfn) + } + aa.diag(assign.Pos(), assign, "visiting slice assignment", "%v.%v = %s%v (%s %T = %s %T)", + dst.Name().Curfn, dst, srcfn, src, dst.Op().String(), dst, src.Op().String(), src) + } + + // Now check what we have on the RHS. + switch src.Op() { + // Cases: + + // Check for s := make([]T, ...) or s := []T{...}, along with the '=' version + // of those which does not alias s as long as s is not used in the make. + // + // TODO(thepudds): we need to be sure that 's := []T{1,2,3}' does not end up backed by a + // global static. Ad-hoc testing indicates that example and similar seem to be + // stack allocated, but that was not exhaustive testing. We do have runtime.freegc + // able to throw if it finds a global static, but should test more. + // + // TODO(thepudds): could also possibly allow 's := append([]T(nil), ...)' + // and 's := append([]T{}, ...)'. + case ir.OMAKESLICE, ir.OSLICELIT: + name := dst.(*ir.Name) + if name.Class == ir.PAUTO { + if base.Debug.EscapeAlias > 1 { + aa.diag(assign.Pos(), assign, "assignment from make or slice literal", "") + } + // If this is Def=true, the ODCL in the init will causes this to be tracked + // as a candidate slice. We walk the init and RHS but avoid visiting the name + // in the LHS, which would remove the slice from the candidate list after it + // was just added. + aa.visit(src) + aa.countProcessed(name) + continue + } + + // Check for s = append(s, <...>). + case ir.OAPPEND: + s := dst.(*ir.Name) + call := src.(*ir.CallExpr) + if call.Args[0] == s { + // Matches s = append(s, <...>). + // First visit other arguments in case they use s. + aa.visitList(call.Args[1:]) + // Mark the call as processed, and s twice. + aa.countProcessed(s, call, s) + + // We have now examined all non-ONAME children of assign. + + // This is now the heart of the analysis. + // Check to see if this slice is a live candidate. + cs, ok := aa.candidateSlices[s] + if ok { + if cs.loopDepth == len(aa.loops) { + // No new loop has started after the declaration of s, + // so this is definitive. + if base.Debug.EscapeAlias >= 2 { + aa.diag(assign.Pos(), assign, "proved non-aliased append", + "(loop depth: %d, equals decl depth)", len(aa.loops)) + } + aa.noAliasAppends = append(aa.noAliasAppends, call) + } else if cs.loopDepth < len(aa.loops) { + // A new loop has started since the declaration of s, + // so we can't validate this append yet, but + // remember it in case we can validate it later when + // all loops using s are done. + aa.loops[cs.loopDepth] = append(aa.loops[cs.loopDepth], + candidateAppend{s: s, call: call}) + } else { + panic("impossible: candidate slice loopDepth > current loop depth") + } + } + continue + } + } // End of switch on src.Op(). + + // Reached bottom of the loop over assignments. + // If we get here, we need to visit the dst and src normally. + aa.visit(dst) + aa.visit(src) + } +} + +func (aa *aliasAnalysis) countProcessed(nodes ...ir.Node) { + if aa.doubleCheck { + for _, n := range nodes { + aa.processed[n]++ + } + } +} + +func (aa *aliasAnalysis) diag(pos src.XPos, n ir.Node, what string, format string, args ...any) { + fmt.Printf("%-25s alias analysis: %-30s %-20s %s\n", + fmtPosShort(pos)+":", + what+":", + fmt.Sprintf("%v", n), + fmt.Sprintf(format, args...)) +} + +// doubleCheckProcessed does a sanity check for missed nodes in our visit. +func doubleCheckProcessed(fn *ir.Func, processed map[ir.Node]int) { + // Do a trivial walk while counting the nodes + // to compare against the counts in processed. + + observed := make(map[ir.Node]int) + var walk func(n ir.Node) bool + walk = func(n ir.Node) bool { + observed[n]++ + return ir.DoChildren(n, walk) + } + ir.DoChildren(fn, walk) + + if !maps.Equal(processed, observed) { + // The most likely mistake might be something was missed while building processed, + // so print extra details in that direction. + for n, observedCount := range observed { + processedCount, ok := processed[n] + if processedCount != observedCount || !ok { + base.WarnfAt(n.Pos(), + "alias analysis: mismatch for %T: %v: processed %d times, observed %d times", + n, n, processedCount, observedCount) + } + } + base.FatalfAt(fn.Pos(), "alias analysis: mismatch in visited nodes") + } +} + +func fmtPosShort(xpos src.XPos) string { + // TODO(thepudds): I think I did this a simpler way a while ago? Or maybe add base.FmtPosShort + // or similar? Or maybe just use base.FmtPos and give up on nicely aligned log messages? + pos := base.Ctxt.PosTable.Pos(xpos) + shortLine := filepath.Base(pos.AbsFilename()) + ":" + pos.LineNumber() + return shortLine +} diff --git a/src/cmd/compile/internal/escape/escape.go b/src/cmd/compile/internal/escape/escape.go index 59250edfef0..9d01156eb8e 100644 --- a/src/cmd/compile/internal/escape/escape.go +++ b/src/cmd/compile/internal/escape/escape.go @@ -8,6 +8,7 @@ import ( "fmt" "go/constant" "go/token" + "internal/goexperiment" "cmd/compile/internal/base" "cmd/compile/internal/ir" @@ -369,6 +370,16 @@ func (b *batch) finish(fns []*ir.Func) { } } } + + if goexperiment.RuntimeFreegc { + // Look for specific patterns of usage, such as appends + // to slices that we can prove are not aliased. + for _, fn := range fns { + a := aliasAnalysis{} + a.analyze(fn) + } + } + } // inMutualBatch reports whether function fn is in the batch of diff --git a/src/cmd/compile/internal/inline/interleaved/interleaved.go b/src/cmd/compile/internal/inline/interleaved/interleaved.go index 80a0cb97df1..d66f1a84cc9 100644 --- a/src/cmd/compile/internal/inline/interleaved/interleaved.go +++ b/src/cmd/compile/internal/inline/interleaved/interleaved.go @@ -20,6 +20,13 @@ import ( // DevirtualizeAndInlinePackage interleaves devirtualization and inlining on // all functions within pkg. func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) { + if base.Flag.W > 1 { + for _, fn := range typecheck.Target.Funcs { + s := fmt.Sprintf("\nbefore devirtualize-and-inline %v", fn.Sym()) + ir.DumpList(s, fn.Body) + } + } + if profile != nil && base.Debug.PGODevirtualize > 0 { // TODO(mdempsky): Integrate into DevirtualizeAndInlineFunc below. ir.VisitFuncsBottomUp(typecheck.Target.Funcs, func(list []*ir.Func, recursive bool) { diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go index 7f156b5e754..f32998f333e 100644 --- a/src/cmd/compile/internal/ir/expr.go +++ b/src/cmd/compile/internal/ir/expr.go @@ -184,15 +184,16 @@ func (n *BinaryExpr) SetOp(op Op) { // A CallExpr is a function call Fun(Args). type CallExpr struct { miniExpr - Fun Node - Args Nodes - DeferAt Node - RType Node `mknode:"-"` // see reflectdata/helpers.go - KeepAlive []*Name // vars to be kept alive until call returns - IsDDD bool - GoDefer bool // whether this call is part of a go or defer statement - NoInline bool // whether this call must not be inlined - UseBuf bool // use stack buffer for backing store (OAPPEND only) + Fun Node + Args Nodes + DeferAt Node + RType Node `mknode:"-"` // see reflectdata/helpers.go + KeepAlive []*Name // vars to be kept alive until call returns + IsDDD bool + GoDefer bool // whether this call is part of a go or defer statement + NoInline bool // whether this call must not be inlined + UseBuf bool // use stack buffer for backing store (OAPPEND only) + AppendNoAlias bool // backing store proven to be unaliased (OAPPEND only) // whether it's a runtime.KeepAlive call the compiler generates to // keep a variable alive. See #73137. IsCompilerVarLive bool diff --git a/src/cmd/compile/internal/ir/symtab.go b/src/cmd/compile/internal/ir/symtab.go index 32297354644..f0e91f6db36 100644 --- a/src/cmd/compile/internal/ir/symtab.go +++ b/src/cmd/compile/internal/ir/symtab.go @@ -30,6 +30,8 @@ type symsStruct struct { Goschedguarded *obj.LSym Growslice *obj.LSym GrowsliceBuf *obj.LSym + GrowsliceBufNoAlias *obj.LSym + GrowsliceNoAlias *obj.LSym MoveSlice *obj.LSym MoveSliceNoScan *obj.LSym MoveSliceNoCap *obj.LSym diff --git a/src/cmd/compile/internal/ssa/_gen/generic.rules b/src/cmd/compile/internal/ssa/_gen/generic.rules index 7710f6f2097..fe8fc5b2620 100644 --- a/src/cmd/compile/internal/ssa/_gen/generic.rules +++ b/src/cmd/compile/internal/ssa/_gen/generic.rules @@ -2054,8 +2054,13 @@ // See issue 56440. // Note there are 2 rules here, one for the pre-decomposed []T result and one for // the post-decomposed (*T,int,int) result. (The latter is generated after call expansion.) -(SliceLen (SelectN [0] (StaticLECall {sym} _ newLen:(Const(64|32)) _ _ _ _))) && isSameCall(sym, "runtime.growslice") => newLen -(SelectN [1] (StaticCall {sym} _ newLen:(Const(64|32)) _ _ _ _)) && v.Type.IsInteger() && isSameCall(sym, "runtime.growslice") => newLen +// TODO(thepudds): we probably need the new growsliceBuf and growsliceBufNoAlias here as well? +(SliceLen (SelectN [0] (StaticLECall {sym} _ newLen:(Const(64|32)) _ _ _ _))) + && (isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias")) + => newLen +(SelectN [1] (StaticCall {sym} _ newLen:(Const(64|32)) _ _ _ _)) && v.Type.IsInteger() + && (isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias")) + => newLen // Collapse moving A -> B -> C into just A -> C. // Later passes (deadstore, elim unread auto) will remove the A -> B move, if possible. diff --git a/src/cmd/compile/internal/ssa/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go index fea126bd4d4..dbbb7105afa 100644 --- a/src/cmd/compile/internal/ssa/rewritegeneric.go +++ b/src/cmd/compile/internal/ssa/rewritegeneric.go @@ -30157,7 +30157,7 @@ func rewriteValuegeneric_OpSelectN(v *Value) bool { return true } // match: (SelectN [1] (StaticCall {sym} _ newLen:(Const64) _ _ _ _)) - // cond: v.Type.IsInteger() && isSameCall(sym, "runtime.growslice") + // cond: v.Type.IsInteger() && (isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias")) // result: newLen for { if auxIntToInt64(v.AuxInt) != 1 || v_0.Op != OpStaticCall || len(v_0.Args) != 6 { @@ -30166,14 +30166,14 @@ func rewriteValuegeneric_OpSelectN(v *Value) bool { sym := auxToCall(v_0.Aux) _ = v_0.Args[1] newLen := v_0.Args[1] - if newLen.Op != OpConst64 || !(v.Type.IsInteger() && isSameCall(sym, "runtime.growslice")) { + if newLen.Op != OpConst64 || !(v.Type.IsInteger() && (isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias"))) { break } v.copyOf(newLen) return true } // match: (SelectN [1] (StaticCall {sym} _ newLen:(Const32) _ _ _ _)) - // cond: v.Type.IsInteger() && isSameCall(sym, "runtime.growslice") + // cond: v.Type.IsInteger() && (isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias")) // result: newLen for { if auxIntToInt64(v.AuxInt) != 1 || v_0.Op != OpStaticCall || len(v_0.Args) != 6 { @@ -30182,7 +30182,7 @@ func rewriteValuegeneric_OpSelectN(v *Value) bool { sym := auxToCall(v_0.Aux) _ = v_0.Args[1] newLen := v_0.Args[1] - if newLen.Op != OpConst32 || !(v.Type.IsInteger() && isSameCall(sym, "runtime.growslice")) { + if newLen.Op != OpConst32 || !(v.Type.IsInteger() && (isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias"))) { break } v.copyOf(newLen) @@ -30594,7 +30594,7 @@ func rewriteValuegeneric_OpSliceLen(v *Value) bool { return true } // match: (SliceLen (SelectN [0] (StaticLECall {sym} _ newLen:(Const64) _ _ _ _))) - // cond: isSameCall(sym, "runtime.growslice") + // cond: (isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias")) // result: newLen for { if v_0.Op != OpSelectN || auxIntToInt64(v_0.AuxInt) != 0 { @@ -30607,14 +30607,14 @@ func rewriteValuegeneric_OpSliceLen(v *Value) bool { sym := auxToCall(v_0_0.Aux) _ = v_0_0.Args[1] newLen := v_0_0.Args[1] - if newLen.Op != OpConst64 || !(isSameCall(sym, "runtime.growslice")) { + if newLen.Op != OpConst64 || !(isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias")) { break } v.copyOf(newLen) return true } // match: (SliceLen (SelectN [0] (StaticLECall {sym} _ newLen:(Const32) _ _ _ _))) - // cond: isSameCall(sym, "runtime.growslice") + // cond: (isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias")) // result: newLen for { if v_0.Op != OpSelectN || auxIntToInt64(v_0.AuxInt) != 0 { @@ -30627,7 +30627,7 @@ func rewriteValuegeneric_OpSliceLen(v *Value) bool { sym := auxToCall(v_0_0.Aux) _ = v_0_0.Args[1] newLen := v_0_0.Args[1] - if newLen.Op != OpConst32 || !(isSameCall(sym, "runtime.growslice")) { + if newLen.Op != OpConst32 || !(isSameCall(sym, "runtime.growslice") || isSameCall(sym, "runtime.growsliceNoAlias")) { break } v.copyOf(newLen) diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 830e0136972..33fcf979c59 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -12,6 +12,7 @@ import ( "go/constant" "html" "internal/buildcfg" + "internal/goexperiment" "internal/runtime/gc" "os" "path/filepath" @@ -125,6 +126,8 @@ func InitConfig() { ir.Syms.Goschedguarded = typecheck.LookupRuntimeFunc("goschedguarded") ir.Syms.Growslice = typecheck.LookupRuntimeFunc("growslice") ir.Syms.GrowsliceBuf = typecheck.LookupRuntimeFunc("growsliceBuf") + ir.Syms.GrowsliceBufNoAlias = typecheck.LookupRuntimeFunc("growsliceBufNoAlias") + ir.Syms.GrowsliceNoAlias = typecheck.LookupRuntimeFunc("growsliceNoAlias") ir.Syms.MoveSlice = typecheck.LookupRuntimeFunc("moveSlice") ir.Syms.MoveSliceNoScan = typecheck.LookupRuntimeFunc("moveSliceNoScan") ir.Syms.MoveSliceNoCap = typecheck.LookupRuntimeFunc("moveSliceNoCap") @@ -4048,9 +4051,25 @@ func (s *state) append(n *ir.CallExpr, inplace bool) *ssa.Value { s.defvars[s.f.Entry.ID][memVar] = mem info.usedStatic = true } - r = s.rtcall(ir.Syms.GrowsliceBuf, true, []*types.Type{n.Type()}, p, l, c, nargs, taddr, s.addr(info.store), s.constInt(types.Types[types.TINT], info.K)) + fn := ir.Syms.GrowsliceBuf + if goexperiment.RuntimeFreegc && n.AppendNoAlias && !et.HasPointers() { + // The append is for a non-aliased slice where the runtime knows how to free + // the old logically dead backing store after growth. + // TODO(thepudds): for now, we only use the NoAlias version for element types + // without pointers while waiting on additional runtime support (CL 698515). + fn = ir.Syms.GrowsliceBufNoAlias + } + r = s.rtcall(fn, true, []*types.Type{n.Type()}, p, l, c, nargs, taddr, s.addr(info.store), s.constInt(types.Types[types.TINT], info.K)) } else { - r = s.rtcall(ir.Syms.Growslice, true, []*types.Type{n.Type()}, p, l, c, nargs, taddr) + fn := ir.Syms.Growslice + if goexperiment.RuntimeFreegc && n.AppendNoAlias && !et.HasPointers() { + // The append is for a non-aliased slice where the runtime knows how to free + // the old logically dead backing store after growth. + // TODO(thepudds): for now, we only use the NoAlias version for element types + // without pointers while waiting on additional runtime support (CL 698515). + fn = ir.Syms.GrowsliceNoAlias + } + r = s.rtcall(fn, true, []*types.Type{n.Type()}, p, l, c, nargs, taddr) } // Decompose output slice diff --git a/src/cmd/compile/internal/test/free_test.go b/src/cmd/compile/internal/test/free_test.go new file mode 100644 index 00000000000..061cc9a6e4e --- /dev/null +++ b/src/cmd/compile/internal/test/free_test.go @@ -0,0 +1,55 @@ +// Copyright 2025 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 test + +import ( + "internal/asan" + "internal/goexperiment" + "internal/msan" + "internal/race" + "testing" +) + +func TestFreeAppendAllocations(t *testing.T) { + t.Run("slice-no-alias", func(t *testing.T) { + if !goexperiment.RuntimeFreegc { + t.Skip("skipping allocation test when runtime.freegc is disabled") + } + if race.Enabled || msan.Enabled || asan.Enabled { + // TODO(thepudds): we get 8 allocs for slice-no-alias instead of 1 with -race. This + // might be expected given some allocation optimizations are already disabled + // under race, but if not, we might need to update walk. + t.Skip("skipping allocation test under race detector and other sanitizers") + } + + allocs := testing.AllocsPerRun(100, func() { + var s []int64 + for i := range 100 { + s = append(s, int64(i)) + } + _ = s + }) + t.Logf("allocs: %v", allocs) + if allocs != 1 { + t.Errorf("allocs: %v, want 1", allocs) + } + }) + + t.Run("slice-aliased", func(t *testing.T) { + allocs := testing.AllocsPerRun(100, func() { + var s []int64 + var alias []int64 + for i := range 100 { + s = append(s, int64(i)) + alias = s + } + _ = alias + }) + t.Logf("allocs: %v", allocs) + if allocs < 2 { + t.Errorf("allocs: %v, want >= 2", allocs) + } + }) +} diff --git a/src/cmd/compile/internal/typecheck/_builtin/runtime.go b/src/cmd/compile/internal/typecheck/_builtin/runtime.go index 35fbbb6b120..a7603c3e33c 100644 --- a/src/cmd/compile/internal/typecheck/_builtin/runtime.go +++ b/src/cmd/compile/internal/typecheck/_builtin/runtime.go @@ -197,6 +197,8 @@ func makeslice64(typ *byte, len int64, cap int64) unsafe.Pointer func makeslicecopy(typ *byte, tolen int, fromlen int, from unsafe.Pointer) unsafe.Pointer func growslice(oldPtr *any, newLen, oldCap, num int, et *byte) (ary []any) func growsliceBuf(oldPtr *any, newLen, oldCap, num int, et *byte, buf *any, bufLen int) (ary []any) +func growsliceBufNoAlias(oldPtr *any, newLen, oldCap, num int, et *byte, buf *any, bufLen int) (ary []any) +func growsliceNoAlias(oldPtr *any, newLen, oldCap, num int, et *byte) (ary []any) func unsafeslicecheckptr(typ *byte, ptr unsafe.Pointer, len int64) func panicunsafeslicelen() func panicunsafeslicenilptr() diff --git a/src/cmd/compile/internal/typecheck/builtin.go b/src/cmd/compile/internal/typecheck/builtin.go index 8a505073f7a..955e65e5988 100644 --- a/src/cmd/compile/internal/typecheck/builtin.go +++ b/src/cmd/compile/internal/typecheck/builtin.go @@ -162,6 +162,8 @@ var runtimeDecls = [...]struct { {"makeslicecopy", funcTag, 125}, {"growslice", funcTag, 127}, {"growsliceBuf", funcTag, 128}, + {"growsliceBufNoAlias", funcTag, 128}, + {"growsliceNoAlias", funcTag, 127}, {"unsafeslicecheckptr", funcTag, 129}, {"panicunsafeslicelen", funcTag, 9}, {"panicunsafeslicenilptr", funcTag, 9}, diff --git a/src/runtime/slice.go b/src/runtime/slice.go index a9e8fc16109..2a442977542 100644 --- a/src/runtime/slice.go +++ b/src/runtime/slice.go @@ -7,6 +7,7 @@ package runtime import ( "internal/abi" "internal/goarch" + "internal/goexperiment" "internal/runtime/math" "internal/runtime/sys" "unsafe" @@ -285,6 +286,42 @@ func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice return slice{p, newLen, newcap} } +// growsliceNoAlias is like growslice but only for the case where +// we know that oldPtr is not aliased. +// +// In other words, the caller must know that there are no other references +// to the backing memory of the slice being grown aside from the slice header +// that will be updated with new backing memory when growsliceNoAlias +// returns, and therefore oldPtr must be the only pointer to its referent +// aside from the slice header updated by the returned slice. +// +// In addition, oldPtr must point to the start of the allocation and match +// the pointer that was returned by mallocgc. In particular, oldPtr must not +// be an interior pointer, such as after a reslice. +// +// See freegc for details. +func growsliceNoAlias(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice { + s := growslice(oldPtr, newLen, oldCap, num, et) + if goexperiment.RuntimeFreegc && oldPtr != nil && oldPtr != s.array { + if gp := getg(); uintptr(oldPtr) < gp.stack.lo || gp.stack.hi <= uintptr(oldPtr) { + // oldPtr does not point into the current stack, and it is not + // the data pointer for s after the grow, so attempt to free it. + // (Note that freegc also verifies that oldPtr does not point into our stack, + // but checking here first is slightly cheaper for the case when + // oldPtr is on the stack and freegc would be a no-op.) + // + // TODO(thepudds): it may be that oldPtr==s.array only when elemsize==0, + // so perhaps we could prohibit growsliceNoAlias being called in that case + // and eliminate that check here, or alternatively, we could lean into + // freegc being a no-op for zero-sized allocations (that is, no check of + // oldPtr != s.array here and just let freegc return quickly). + noscan := !et.Pointers() + freegc(oldPtr, uintptr(oldCap)*et.Size_, noscan) + } + } + return s +} + // nextslicecap computes the next appropriate slice length. func nextslicecap(newLen, oldCap int) int { newcap := oldCap @@ -503,3 +540,22 @@ func growsliceBuf(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type, buf return slice{bufPtr, newLen, newCap} } + +// growsliceBufNoAlias is a combination of growsliceBuf and growsliceNoAlias. +// bufPtr must be on the stack. +func growsliceBufNoAlias(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type, bufPtr unsafe.Pointer, bufLen int) slice { + s := growsliceBuf(oldPtr, newLen, oldCap, num, et, bufPtr, bufLen) + if goexperiment.RuntimeFreegc && oldPtr != bufPtr && oldPtr != nil && oldPtr != s.array { + // oldPtr is not bufPtr (the stack buffer) and it is not + // the data pointer for s after the grow, so attempt to free it. + // (Note that freegc does a broader check that oldPtr does not point into our stack, + // but checking here first is slightly cheaper for a common case when oldPtr is bufPtr + // and freegc would be a no-op.) + // + // TODO(thepudds): see related TODO in growsliceNoAlias about possibly eliminating + // the oldPtr != s.array check. + noscan := !et.Pointers() + freegc(oldPtr, uintptr(oldCap)*et.Size_, noscan) + } + return s +} diff --git a/src/runtime/slice_test.go b/src/runtime/slice_test.go index 376b4a58f23..4779459c48c 100644 --- a/src/runtime/slice_test.go +++ b/src/runtime/slice_test.go @@ -7,6 +7,7 @@ package runtime_test import ( "fmt" "internal/asan" + "internal/goexperiment" "internal/msan" "internal/race" "internal/testenv" @@ -541,6 +542,18 @@ func TestAppendByteInLoop(t *testing.T) { n := test[0] want := test[1] wantCap := test[2] + + if goexperiment.RuntimeFreegc && n > 64 { + // Only 1 allocation is expected to be reported. + // + // TODO(thepudds): consider a test export or similar that lets us more directly see + // the count of freed objects, reused objects, and allocated objects. This could + // be in advance of any future runtime/metrics or user-visible API, or perhaps + // we could introduce the concept of build tag or debug flag controlled runtime/metrics + // targeting people working on the runtime and compiler (but not formally supported). + want = 1 + } + var r []byte got := testing.AllocsPerRun(10, func() { r = byteSlice(n) @@ -659,6 +672,12 @@ func TestAppendByteCapInLoop(t *testing.T) { n := test[0] want := test[1] wantCap := test[2] + + if goexperiment.RuntimeFreegc && n > 64 { + // Only 1 allocation is expected to be reported. + want = 1 + } + var r []byte got := testing.AllocsPerRun(10, func() { r, _ = byteCapSlice(n) diff --git a/test/codegen/append.go b/test/codegen/append.go index e90fa87ed2c..d232f0b170f 100644 --- a/test/codegen/append.go +++ b/test/codegen/append.go @@ -1,5 +1,7 @@ // asmcheck +//go:build !goexperiment.runtimefreegc + // Copyright 2025 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. @@ -9,114 +11,135 @@ package codegen func Append1(n int) []int { var r []int for i := range n { - // amd64:`.*growslice` + // amd64:`.*growslice\b` r = append(r, i) } - // amd64:`.*moveSliceNoCapNoScan` + // amd64:`.*moveSliceNoCapNoScan\b` return r } func Append2(n int) (r []int) { for i := range n { - // amd64:`.*growslice` + // amd64:`.*growslice\b` r = append(r, i) } - // amd64:`.*moveSliceNoCapNoScan` + // amd64:`.*moveSliceNoCapNoScan\b` return } func Append3(n int) (r []int) { for i := range n { - // amd64:`.*growslice` + // amd64:`.*growslice\b` r = append(r, i) } - // amd64:`.*moveSliceNoCapNoScan` + // amd64:`.*moveSliceNoCapNoScan\b` return r } func Append4(n int) []int { var r []int for i := range n { - // amd64:`.*growsliceBuf` + // amd64:`.*growsliceBuf\b` r = append(r, i) } println(cap(r)) - // amd64:`.*moveSliceNoScan` + // amd64:`.*moveSliceNoScan\b` return r } func Append5(n int) []int { var r []int for i := range n { - // amd64:`.*growsliceBuf` + // amd64:`.*growsliceBuf\b` r = append(r, i) } useSlice(r) - // amd64:`.*moveSliceNoScan` + // amd64:`.*moveSliceNoScan\b` + return r +} + +func Append5b(n int) []int { + var r []int + useSlice(r) + for i := range n { + // amd64:`.*growsliceBuf\b` + r = append(r, i) + } + // amd64:`.*moveSliceNoScan\b` return r } func Append6(n int) []*int { var r []*int for i := range n { - // amd64:`.*growslice` + // amd64:`.*growslice\b` r = append(r, new(i)) } - // amd64:`.*moveSliceNoCap` + // amd64:`.*moveSliceNoCap\b` return r } func Append7(n int) []*int { var r []*int for i := range n { - // amd64:`.*growsliceBuf` + // amd64:`.*growsliceBuf\b` r = append(r, new(i)) } println(cap(r)) - // amd64:`.*moveSlice` + // amd64:`.*moveSlice\b` return r } func Append8(n int, p *[]int) { var r []int for i := range n { - // amd64:`.*growslice` + // amd64:`.*growslice\b` r = append(r, i) } - // amd64:`.*moveSliceNoCapNoScan` + // amd64:`.*moveSliceNoCapNoScan\b` *p = r } +func Append8b(n int, p *[]int) { + var r []int + // amd64:`.*moveSliceNoCapNoScan\b` + *p = r + for i := range n { + // amd64:`.*growslice\b` + r = append(r, i) + } +} + func Append9(n int) []int { var r []int for i := range n { - // amd64:`.*growslice` + // amd64:`.*growslice\b` r = append(r, i) } println(len(r)) - // amd64:`.*moveSliceNoCapNoScan` + // amd64:`.*moveSliceNoCapNoScan\b` return r } func Append10(n int) []int { var r []int for i := range n { - // amd64:`.*growslice` + // amd64:`.*growslice\b` r = append(r, i) } println(r[3]) - // amd64:`.*moveSliceNoCapNoScan` + // amd64:`.*moveSliceNoCapNoScan\b` return r } func Append11(n int) []int { var r []int for i := range n { - // amd64:`.*growsliceBuf` + // amd64:`.*growsliceBuf\b` r = append(r, i) } r = r[3:5] - // amd64:`.*moveSliceNoScan` + // amd64:`.*moveSliceNoScan\b` return r } @@ -124,10 +147,10 @@ func Append12(n int) []int { var r []int r = nil for i := range n { - // amd64:`.*growslice` + // amd64:`.*growslice\b` r = append(r, i) } - // amd64:`.*moveSliceNoCapNoScan` + // amd64:`.*moveSliceNoCapNoScan\b` return r } @@ -135,10 +158,10 @@ func Append13(n int) []int { var r []int r, r = nil, nil for i := range n { - // amd64:`.*growslice` + // amd64:`.*growslice\b` r = append(r, i) } - // amd64:`.*moveSliceNoCapNoScan` + // amd64:`.*moveSliceNoCapNoScan\b` return r } @@ -146,42 +169,42 @@ func Append14(n int) []int { var r []int r = []int{3, 4, 5} for i := range n { - // amd64:`.*growsliceBuf` + // amd64:`.*growsliceBuf\b` r = append(r, i) } - // amd64:`.*moveSliceNoScan` + // amd64:`.*moveSliceNoScan\b` return r } func Append15(n int) []int { r := []int{3, 4, 5} for i := range n { - // amd64:`.*growsliceBuf` + // amd64:`.*growsliceBuf\b` r = append(r, i) } - // amd64:`.*moveSliceNoScan` + // amd64:`.*moveSliceNoScan\b` return r } func Append16(r []int, n int) []int { for i := range n { - // amd64:`.*growslice` + // amd64:`.*growslice\b` r = append(r, i) } - // amd64:`.*moveSliceNoCapNoScan` + // amd64:`.*moveSliceNoCapNoScan\b` return r } func Append17(n int) []int { var r []int for i := range n { - // amd64:`.*growslice` + // amd64:`.*growslice\b` r = append(r, i) } for i, x := range r { println(i, x) } - // amd64:`.*moveSliceNoCapNoScan` + // amd64:`.*moveSliceNoCapNoScan\b` return r } diff --git a/test/codegen/append_freegc.go b/test/codegen/append_freegc.go new file mode 100644 index 00000000000..91f0901e357 --- /dev/null +++ b/test/codegen/append_freegc.go @@ -0,0 +1,217 @@ +// asmcheck + +//go:build goexperiment.runtimefreegc + +// Copyright 2025 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 codegen + +func Append1(n int) []int { + var r []int + for i := range n { + // amd64:`.*growsliceNoAlias\b` + r = append(r, i) + } + // amd64:`.*moveSliceNoCapNoScan\b` + return r +} + +func Append2(n int) (r []int) { + for i := range n { + // amd64:`.*growslice\b` + r = append(r, i) + } + // amd64:`.*moveSliceNoCapNoScan\b` + return +} + +func Append3(n int) (r []int) { + for i := range n { + // amd64:`.*growslice\b` + r = append(r, i) + } + // amd64:`.*moveSliceNoCapNoScan\b` + return r +} + +func Append4(n int) []int { + var r []int + for i := range n { + // amd64:`.*growsliceBufNoAlias\b` + r = append(r, i) + } + println(cap(r)) + // amd64:`.*moveSliceNoScan\b` + return r +} + +func Append5(n int) []int { + var r []int + for i := range n { + // amd64:`.*growsliceBufNoAlias\b` + r = append(r, i) + } + useSlice(r) + // amd64:`.*moveSliceNoScan\b` + return r +} + +func Append5b(n int) []int { + var r []int + useSlice(r) + for i := range n { + // amd64:`.*growsliceBuf\b` -`.*growsliceBufNoAlias\b` + r = append(r, i) + } + // amd64:`.*moveSliceNoScan\b` + return r +} + +func Append6(n int) []*int { + var r []*int + for i := range n { + // TODO(thepudds): for now, the compiler only uses the NoAlias version + // for element types without pointers. + // amd64:`.*growslice\b` + r = append(r, new(i)) + } + // amd64:`.*moveSliceNoCap\b` + return r +} + +func Append7(n int) []*int { + var r []*int + for i := range n { + // TODO(thepudds): for now, the compiler only uses the NoAlias version + // for element types without pointers. + // amd64:`.*growsliceBuf\b` + r = append(r, new(i)) + } + println(cap(r)) + // amd64:`.*moveSlice\b` + return r +} + +func Append8(n int, p *[]int) { + var r []int + for i := range n { + // amd64:`.*growsliceNoAlias\b` + r = append(r, i) + } + // amd64:`.*moveSliceNoCapNoScan\b` + *p = r +} + +func Append8b(n int, p *[]int) { + var r []int + // amd64:`.*moveSliceNoCapNoScan\b` + *p = r + for i := range n { + // amd64:`.*growslice\b` -`.*growsliceNoAlias\b` + r = append(r, i) + } +} + +func Append9(n int) []int { + var r []int + for i := range n { + // amd64:`.*growsliceNoAlias\b` + r = append(r, i) + } + println(len(r)) + // amd64:`.*moveSliceNoCapNoScan\b` + return r +} + +func Append10(n int) []int { + var r []int + for i := range n { + // amd64:`.*growsliceNoAlias\b` + r = append(r, i) + } + println(r[3]) + // amd64:`.*moveSliceNoCapNoScan\b` + return r +} + +func Append11(n int) []int { + var r []int + for i := range n { + // amd64:`.*growsliceBufNoAlias\b` + r = append(r, i) + } + r = r[3:5] + // amd64:`.*moveSliceNoScan\b` + return r +} + +func Append12(n int) []int { + var r []int + r = nil + for i := range n { + // amd64:`.*growslice\b` + r = append(r, i) + } + // amd64:`.*moveSliceNoCapNoScan\b` + return r +} + +func Append13(n int) []int { + var r []int + r, r = nil, nil + for i := range n { + // amd64:`.*growslice\b` + r = append(r, i) + } + // amd64:`.*moveSliceNoCapNoScan\b` + return r +} + +func Append14(n int) []int { + var r []int + r = []int{3, 4, 5} + for i := range n { + // amd64:`.*growsliceBufNoAlias\b` + r = append(r, i) + } + // amd64:`.*moveSliceNoScan\b` + return r +} + +func Append15(n int) []int { + r := []int{3, 4, 5} + for i := range n { + // amd64:`.*growsliceBufNoAlias\b` + r = append(r, i) + } + // amd64:`.*moveSliceNoScan\b` + return r +} + +func Append16(r []int, n int) []int { + for i := range n { + // amd64:`.*growslice\b` + r = append(r, i) + } + // amd64:`.*moveSliceNoCapNoScan\b` + return r +} + +func Append17(n int) []int { + var r []int + for i := range n { + // amd64:`.*growsliceNoAlias\b` + r = append(r, i) + } + for i, x := range r { + println(i, x) + } + // amd64:`.*moveSliceNoCapNoScan\b` + return r +} + +//go:noinline +func useSlice(s []int) { +} diff --git a/test/escape_alias.go b/test/escape_alias.go new file mode 100644 index 00000000000..f8d455b653f --- /dev/null +++ b/test/escape_alias.go @@ -0,0 +1,779 @@ +// errorcheck -0 -d=escapealias=1 + +//go:build goexperiment.runtimefreegc + +// Copyright 2025 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. + +// Test recognizing certain patterns of usage, +// currently focused on whether a slice is aliased. + +package escapealias + +import "runtime" + +// Basic examples. +// +// Some of these directly overlap with later tests below, but are presented at the start +// to help show the big picture (before going into more variations). + +var alias []int + +func basic1() { + // A simple append with no aliasing of s. + var s []int + s = append(s, 0) // ERROR "append using non-aliased slice" + _ = s +} + +func basic2() []int { + // The slice can escape. + var s []int + s = append(s, 0) // ERROR "append using non-aliased slice" + return s +} + +func basic3() { + // A simple example of s being aliased. + // We give up when we see the aliasing. + var s []int + alias = s + s = append(s, 0) + _ = s +} + +func basic4() { + // The analysis is conservative, giving up on + // IR nodes it doesn't understand. It does not + // yet understand comparisons, for example. + var s []int + _ = s == nil + s = append(s, 0) + _ = s +} + +func basic5() { + // We also give up if s is assigned to another variable. + var s []int + s2 := s + s2 = append(s2, 0) + _ = s2 +} + +func basic6() { + // A self-assigning append does not create an alias, + // so s is still unaliased when we reach the second append here. + var s []int + s = append(s, 0) // ERROR "append using non-aliased slice" + s = append(s, 0) // ERROR "append using non-aliased slice" + _ = s +} + +func basic7() { + // An append can be unaliased if it happens before aliasing. + var s []int + s = append(s, 0) // ERROR "append using non-aliased slice" + alias = s + s = append(s, 0) + _ = s +} + +func basic8() { + // Aliasing anywhere in a loop means we give up for the whole loop body, + // even if the aliasing is after the append in the loop body. + var s []int + for range 10 { + s = append(s, 0) + alias = s + } + _ = s +} + +func basic9() { + // Aliases after a loop do not affect whether this is aliasing in the loop. + var s []int + for range 10 { + s = append(s, 0) // ERROR "append using non-aliased slice" + } + alias = s + _ = s +} + +func basic10() { + // We track the depth at which a slice is declared vs. aliased, + // which helps for example with nested loops. + // In this example, the aliasing occurs after both loops are done. + var s []int + for range 10 { + for range 10 { + s = append(s, 0) // ERROR "append using non-aliased slice" + } + } + alias = s +} + +func basic11() { + // In contrast, here the aliasing occurs in the outer loop body. + var s []int + for range 10 { + for range 10 { + s = append(s, 0) + } + alias = s + } +} + +// Some variations on single appends. + +func singleAppend1() []int { + var s []int + s = append(s, 0) // ERROR "append using non-aliased slice" + return s +} + +func singleAppend2() { + var s []int + alias = s + s = append(s, 0) +} + +func singleAppend3() { + var s []int + s = append(s, 0) // ERROR "append using non-aliased slice" + alias = s +} + +func singleAppend4() { + var s []int + p := &s + _ = p + s = append(s, 0) +} + +func singleAppend5(s []int) { + s = append(s, 0) +} + +func singleAppend6() { + var s []int + alias, _ = s, 0 + s = append(s, 0) +} + +// Examples with variations on slice declarations. + +func sliceDeclaration1() { + s := []int{} + s = append(s, 0) // ERROR "append using non-aliased slice" +} + +func sliceDeclaration2() { + s := []int{1, 2, 3} + s = append(s, 0) // ERROR "append using non-aliased slice" +} + +func sliceDeclaration3() { + s := make([]int, 3) + s = append(s, 0) // ERROR "append using non-aliased slice" +} + +func sliceDeclaration4() { + s := []int{} + alias = s + s = append(s, 0) +} + +func sliceDeclaration5() { + s := []int{1, 2, 3} + alias = s + s = append(s, 0) +} + +func sliceDeclaration6() { + s := make([]int, 3) + alias = s + s = append(s, 0) +} + +func sliceDeclaration7() { + s, x := []int{}, 0 + s = append(s, x) // ERROR "append using non-aliased slice" +} + +// Basic loops. First, a single loop. + +func loops1a() { + var s []int + for i := range 10 { + s = append(s, i) // ERROR "append using non-aliased slice" + } +} + +func loops1b() { + var s []int + for i := range 10 { + alias = s + s = append(s, i) + } +} + +func loops1c() { + var s []int + for i := range 10 { + s = append(s, i) + alias = s + } +} + +func loops1d() { + var s []int + for i := range 10 { + s = append(s, i) // ERROR "append using non-aliased slice" + } + alias = s +} + +func loops1e() { + var s []int + for i := range use(s) { + s = append(s, i) + } +} + +func loops1f() { + var s []int + for i := range use(s) { + s = append(s, i) + } + s = append(s, 0) +} + +// Nested loops with s declared outside the loops. + +func loops2a() { + var s []int + for range 10 { + for i := range 10 { + s = append(s, i) // ERROR "append using non-aliased slice" + } + } +} + +func loops2b() { + var s []int + for range 10 { + alias = s + for i := range 10 { + s = append(s, i) + } + } +} + +func loops2c() { + var s []int + for range 10 { + for i := range 10 { + s = append(s, i) + } + alias = s + } +} + +func loops2d() { + var s []int + for range 10 { + for i := range 10 { + s = append(s, i) // ERROR "append using non-aliased slice" + } + } + alias = s +} + +func loops2e() { + var s []int + for range use(s) { + for i := range 10 { + s = append(s, i) + } + s = append(s, 0) + } + s = append(s, 0) +} + +func loops2f() { + var s []int + for range 10 { + for i := range use(s) { + s = append(s, i) + } + s = append(s, 0) + } + s = append(s, 0) +} + +// Nested loops with s declared inside the first loop. + +func loops3a() { + for range 10 { + var s []int + for i := range 10 { + s = append(s, i) // ERROR "append using non-aliased slice" + } + } +} + +func loops3b() { + for range 10 { + var s []int + for i := range 10 { + alias = s + s = append(s, i) + } + } +} + +func loops3c() { + for range 10 { + var s []int + for i := range 10 { + s = append(s, i) + alias = s + } + } +} + +func loops3d() { + for range 10 { + var s []int + for i := range 10 { + s = append(s, i) // ERROR "append using non-aliased slice" + } + alias = s + } +} + +func loops3e() { + for range 10 { + var s []int + for i := range use(s) { + s = append(s, i) + } + s = append(s, 0) + } +} + +// Loops using OFOR instead of ORANGE. + +func loops4a() { + var s []int + for i := 0; i < 10; i++ { + s = append(s, i) // ERROR "append using non-aliased slice" + } +} + +func loops4b() { + var s []int + for i := 0; i < 10; i++ { + alias = s + s = append(s, i) + } +} + +func loops4c() { + var s []int + for i := 0; i < 10; i++ { + s = append(s, i) + alias = s + } +} + +func loops4d() { + var s []int + for i := 0; i < 10; i++ { + s = append(s, i) // ERROR "append using non-aliased slice" + } + alias = s +} + +// Loops with some initialization variations. + +func loopsInit1() { + var i int + for s := []int{}; i < 10; i++ { + s = append(s, i) // ERROR "append using non-aliased slice" + } +} + +func loopsInit2() { + var i int + for s := []int{}; i < 10; i++ { + s = append(s, i) + alias = s + } +} + +func loopsInit3() { + var i int + for s := []int{}; i < 10; i++ { + for range 10 { + s = append(s, i) // ERROR "append using non-aliased slice" + } + } +} + +func loopsInit5() { + var i int + for s := []int{}; i < 10; i++ { + for range 10 { + s = append(s, i) + alias = s + } + } +} + +func loopsInit5b() { + var i int + for s := []int{}; i < 10; i++ { + for range 10 { + s = append(s, i) + } + alias = s + } +} + +func loopsInit6() { + for range 10 { + var i int + for s := []int{}; i < 10; i++ { + s = append(s, i) // ERROR "append using non-aliased slice" + } + } +} + +func loopsInit7() { + for range 10 { + var i int + for s := []int{}; i < 10; i++ { + s = append(s, i) + alias = s + } + } +} + +// Some initialization variations with use of s in the for or range. + +func loopsInit8() { + var s []int + for use(s) == 0 { + s = append(s, 0) + } +} + +func loopsInit9() { + for s := []int{}; use(s) == 0; { + s = append(s, 0) + } +} + +func loopsInit10() { + for s := []int{}; ; use(s) { + s = append(s, 0) + } +} + +func loopsInit11() { + var s [][]int + for _, s2 := range s { + s = append(s, s2) + } +} + +// Examples of calling functions that get inlined, +// starting with a simple pass-through function. + +// TODO(thepudds): we handle many of these starting in https://go.dev/cl/712422 + +func inlineReturn(param []int) []int { + return param +} + +func inline1a() { + var s []int + s = inlineReturn(s) + s = append(s, 0) +} + +func inline1b() { + var s []int + for range 10 { + s = inlineReturn(s) + s = append(s, 0) + } +} + +func inline1c() { + var s []int + for range 10 { + s = inlineReturn(s) + alias = s + s = append(s, 0) + } +} + +func inline1d() { + var s []int + for range 10 { + s = inlineReturn(s) + s = append(s, 0) + alias = s + } +} + +// Examples with an inlined function that uses append. + +func inlineAppend(param []int) []int { + param = append(param, 0) + // TODO(thepudds): could in theory also handle a direct 'return append(param, 0)' + return param +} + +func inline2a() { + var s []int + s = inlineAppend(s) + s = append(s, 0) +} + +func inline2b() { + var s []int + for range 10 { + s = inlineAppend(s) + s = append(s, 0) + } +} + +func inline2c() { + var s []int + for range 10 { + s = inlineAppend(s) + alias = s + s = append(s, 0) + } +} + +func inline2d() { + var s []int + for range 10 { + s = inlineAppend(s) + s = append(s, 0) + alias = s + } +} + +// Examples calling non-inlined functions that do and do not escape. + +var sink interface{} + +//go:noinline +func use(s []int) int { return 0 } // s content does not escape + +//go:noinline +func escape(s []int) int { sink = s; return 0 } // s content escapes + +func call1() { + var s []int + s = append(s, 0) // ERROR "append using non-aliased slice" + use(s) +} + +// TODO(thepudds): OK to disallow this for now, but would be nice to allow this given use(s) is non-escaping. +func call2() { + var s []int + use(s) + s = append(s, 0) +} + +func call3() { + var s []int + s = append(s, use(s)) +} + +func call4() { + var s []int + for i := range 10 { + s = append(s, i) + use(s) + } +} + +func callEscape1() { + var s []int + s = append(s, 0) // ERROR "append using non-aliased slice" + escape(s) +} + +func callEscape2() { + var s []int + escape(s) + s = append(s, 0) +} + +func callEscape3() { + var s []int + s = append(s, escape(s)) +} + +func callEscape4() { + var s []int + for i := range 10 { + s = append(s, i) + escape(s) + } +} + +// Examples of some additional expressions we understand. + +func expr1() { + var s []int + _ = len(s) + _ = cap(s) + s = append(s, 0) // ERROR "append using non-aliased slice" +} + +// Examples of some expressions or statements we do not understand. +// Some of these we could handle in the future, but some likely not. + +func notUnderstood1() { + var s []int + s = append(s[:], 0) +} + +func notUnderstood2() { + // Note: we must be careful if we analyze slice expressions. + // See related comment about slice expressions in (*aliasAnalysis).analyze. + var s []int + s = append(s, 0) // ERROR "append using non-aliased slice" + s = s[1:] // s no longer points to the base of the heap object. + s = append(s, 0) +} + +func notUnderstood3() { + // The first append is currently the heart of slices.Grow. + var s []int + n := 1000 + s = append(s[:cap(s)], make([]int, n)...)[:len(s)] + s = append(s, 0) +} + +func notUnderstood4() []int { + // A return statement could be allowed to use the slice in a loop + // because we cannot revisit the append once we return. + var s []int + for i := range 10 { + s = append(s, 0) + if i > 5 { + return s + } + } + return s +} + +func notUnderstood5() { + // AddCleanup is an example function call that we do not understand. + // See related comment about specials in (*aliasAnalysis).analyze. + var s []int + runtime.AddCleanup(&s, func(int) {}, 0) + s = append(s, 0) +} + +// Examples with closures. + +func closure1() { + var s []int // declared outside the closure + f := func() { + for i := range 10 { + s = append(s, i) + } + } + _ = f // avoid calling f, which would just get inlined +} + +// TODO(thepudds): it's probably ok that we currently allow this. Could conservatively +// disallow if needed. +func closure2() { + f := func() { + var s []int // declared inside the closure + for i := range 10 { + s = append(s, i) // ERROR "append using non-aliased slice" + } + } + _ = f // avoid calling f, which would just get inlined +} + +// Examples with goto and labels. + +func goto1() { + var s []int +label: + s = append(s, 0) + alias = s + goto label +} + +func goto2() { + var s []int + s = append(s, 0) // ERROR "append using non-aliased slice" + alias = s +label: + goto label +} + +func goto3() { + var s []int +label: + for i := range 10 { + s = append(s, i) + } + goto label +} + +func break1() { + var s []int +label: + for i := range 10 { + s = append(s, i) + break label + } +} + +// Examples with iterators. + +func collect[E any](seq Seq[E]) []E { + var result []E + for v := range seq { + result = append(result, v) + } + return result +} + +func count(yield func(int) bool) { + for i := range 10 { + if !yield(i) { + return + } + } +} + +func iteratorUse1() { + var s []int + s = collect(count) + _ = s +} + +func iteratorUse2() { + var s []int + s = collect(count) + s = append(s, 0) +} + +type Seq[E any] func(yield func(E) bool) From 0ff323143de9d6915a8abec441009cecd803e442 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Thu, 6 Nov 2025 19:52:54 -0800 Subject: [PATCH 088/140] cmd/link: sort allocated ELF section headers by address For an executable, emit the allocated section headers in address order, so that section headers are easier for humans to read. Change-Id: Ib5efb4734101e4a1f6b09d0e045ed643c79c7c0a Reviewed-on: https://go-review.googlesource.com/c/go/+/718620 Reviewed-by: Cherry Mui TryBot-Bypass: David Chase Reviewed-by: David Chase --- src/cmd/link/elf_test.go | 58 ++++++++ src/cmd/link/internal/ld/dwarf.go | 2 +- src/cmd/link/internal/ld/elf.go | 208 ++++++++++++++++++++++------- src/cmd/link/internal/ld/symtab.go | 2 +- 4 files changed, 222 insertions(+), 48 deletions(-) diff --git a/src/cmd/link/elf_test.go b/src/cmd/link/elf_test.go index dc52c091f65..78459d611d7 100644 --- a/src/cmd/link/elf_test.go +++ b/src/cmd/link/elf_test.go @@ -678,3 +678,61 @@ func testFlagDError(t *testing.T, dataAddr string, roundQuantum string, expected t.Errorf("expected error message to contain %q, got:\n%s", expectedError, out) } } + +func TestELFHeadersSorted(t *testing.T) { + testenv.MustHaveGoBuild(t) + + // We can only test this for internal linking mode. + // For external linking the external linker will + // decide how to sort the sections. + testenv.MustInternalLink(t, testenv.NoSpecialBuildTypes) + + t.Parallel() + + tmpdir := t.TempDir() + src := filepath.Join(tmpdir, "x.go") + if err := os.WriteFile(src, []byte(goSourceWithData), 0o444); err != nil { + t.Fatal(err) + } + + exe := filepath.Join(tmpdir, "x.exe") + cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-toolexec", os.Args[0], "-ldflags=-linkmode=internal", "-o", exe, src) + cmd = testenv.CleanCmdEnv(cmd) + cmd.Env = append(cmd.Env, "LINK_TEST_TOOLEXEC=1") + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("build failed: %v, output:\n%s", err, out) + } + + ef, err := elf.Open(exe) + if err != nil { + t.Fatal(err) + } + defer ef.Close() + + // After the first zero section header, + // we should see allocated sections, + // then unallocated sections. + // The allocated sections should be sorted by address. + i := 1 + lastAddr := uint64(0) + for i < len(ef.Sections) { + sec := ef.Sections[i] + if sec.Flags&elf.SHF_ALLOC == 0 { + break + } + if sec.Addr < lastAddr { + t.Errorf("section %d %q address %#x less than previous address %#x", i, sec.Name, sec.Addr, lastAddr) + } + lastAddr = sec.Addr + i++ + } + + firstUnalc := i + for i < len(ef.Sections) { + sec := ef.Sections[i] + if sec.Flags&elf.SHF_ALLOC != 0 { + t.Errorf("allocated section %d %q follows first unallocated section %d %q", i, sec.Name, firstUnalc, ef.Sections[firstUnalc].Name) + } + i++ + } +} diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index 9bab73e7b71..eeac497850f 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -2428,7 +2428,7 @@ func dwarfaddelfsectionsyms(ctxt *Link) { for _, si := range dwarfp { s := si.secSym() sect := ldr.SymSect(si.secSym()) - putelfsectionsym(ctxt, ctxt.Out, s, sect.Elfsect.(*ElfShdr).shnum) + putelfsectionsym(ctxt, ctxt.Out, s, elfShdrShnum(sect.Elfsect.(*ElfShdr))) } } diff --git a/src/cmd/link/internal/ld/elf.go b/src/cmd/link/internal/ld/elf.go index 62736ab94bd..ba0c181daf5 100644 --- a/src/cmd/link/internal/ld/elf.go +++ b/src/cmd/link/internal/ld/elf.go @@ -10,6 +10,7 @@ import ( "cmd/internal/sys" "cmd/link/internal/loader" "cmd/link/internal/sym" + "cmp" "debug/elf" "encoding/binary" "encoding/hex" @@ -73,7 +74,22 @@ type ElfEhdr elf.Header64 // ElfShdr is an ELF section entry, plus the section index. type ElfShdr struct { elf.Section64 + + // The section index, set by elfSortShdrs. + // Don't read this directly, use elfShdrShnum. shnum elf.SectionIndex + + // Because we don't compute the final section number + // until late in the link, when the link and info fields + // hold section indexes, we store pointers, and fetch + // the final section index when we write them out. + link *ElfShdr + info *ElfShdr + + // We compute the section offsets of reloc sections + // after we create the ELF section header. + // This field lets us fetch the section offset and size. + relocSect *sym.Section } // ElfPhdr is the ELF program, or segment, header. @@ -109,9 +125,10 @@ var ( // target platform uses. elfRelType string - ehdr ElfEhdr - phdr = make([]*ElfPhdr, 0, 8) - shdr = make([]*ElfShdr, 0, 64) + ehdr ElfEhdr + phdr = make([]*ElfPhdr, 0, 8) + shdr = make([]*ElfShdr, 0, 64) + shdrSorted bool interp string ) @@ -263,15 +280,72 @@ func elf32phdr(out *OutBuf, e *ElfPhdr) { out.Write32(uint32(e.Align)) } +// elfShdrShnum returns the section index of an ElfShdr. +func elfShdrShnum(e *ElfShdr) elf.SectionIndex { + if e.shnum == -1 { + Errorf("internal error: retrieved section index before it is set") + errorexit() + } + return e.shnum +} + +// elfShdrOff returns the section offset for an ElfShdr. +func elfShdrOff(e *ElfShdr) uint64 { + if e.relocSect != nil { + if e.Off != 0 { + Errorf("internal error: ElfShdr relocSect == %p Off == %d", e.relocSect, e.Off) + errorexit() + } + return e.relocSect.Reloff + } + return e.Off +} + +// elfShdrSize returns the section size for an ElfShdr. +func elfShdrSize(e *ElfShdr) uint64 { + if e.relocSect != nil { + if e.Size != 0 { + Errorf("internal error: ElfShdr relocSect == %p Size == %d", e.relocSect, e.Size) + errorexit() + } + return e.relocSect.Rellen + } + return e.Size +} + +// elfShdrLink returns the link value for an ElfShdr. +func elfShdrLink(e *ElfShdr) uint32 { + if e.link != nil { + if e.Link != 0 { + Errorf("internal error: ElfShdr link == %p Link == %d", e.link, e.Link) + errorexit() + } + return uint32(elfShdrShnum(e.link)) + } + return e.Link +} + +// elfShdrInfo returns the info value for an ElfShdr. +func elfShdrInfo(e *ElfShdr) uint32 { + if e.info != nil { + if e.Info != 0 { + Errorf("internal error: ElfShdr info == %p Info == %d", e.info, e.Info) + errorexit() + } + return uint32(elfShdrShnum(e.info)) + } + return e.Info +} + func elf64shdr(out *OutBuf, e *ElfShdr) { out.Write32(e.Name) out.Write32(e.Type) out.Write64(e.Flags) out.Write64(e.Addr) - out.Write64(e.Off) - out.Write64(e.Size) - out.Write32(e.Link) - out.Write32(e.Info) + out.Write64(elfShdrOff(e)) + out.Write64(elfShdrSize(e)) + out.Write32(elfShdrLink(e)) + out.Write32(elfShdrInfo(e)) out.Write64(e.Addralign) out.Write64(e.Entsize) } @@ -281,10 +355,10 @@ func elf32shdr(out *OutBuf, e *ElfShdr) { out.Write32(e.Type) out.Write32(uint32(e.Flags)) out.Write32(uint32(e.Addr)) - out.Write32(uint32(e.Off)) - out.Write32(uint32(e.Size)) - out.Write32(e.Link) - out.Write32(e.Info) + out.Write32(uint32(elfShdrOff(e))) + out.Write32(uint32(elfShdrSize(e))) + out.Write32(elfShdrLink(e)) + out.Write32(elfShdrInfo(e)) out.Write32(uint32(e.Addralign)) out.Write32(uint32(e.Entsize)) } @@ -303,6 +377,42 @@ func elfwriteshdrs(out *OutBuf) uint32 { return uint32(ehdr.Shnum) * ELF32SHDRSIZE } +// elfSortShdrs sorts the section headers so that allocated sections +// are first, in address order. This isn't required for correctness, +// but it makes the ELF file easier for humans to read. +// We only do this for an executable, not an object file. +func elfSortShdrs(ctxt *Link) { + if ctxt.LinkMode != LinkExternal { + // Use [1:] to leave the empty section header zero in place. + slices.SortStableFunc(shdr[1:], func(a, b *ElfShdr) int { + isAllocated := func(h *ElfShdr) bool { + return elf.SectionFlag(h.Flags)&elf.SHF_ALLOC != 0 + } + if isAllocated(a) { + if isAllocated(b) { + if r := cmp.Compare(a.Addr, b.Addr); r != 0 { + return r + } + // With same address, sort smallest + // section first. + return cmp.Compare(a.Size, b.Size) + } + // Allocated before unallocated. + return -1 + } + if isAllocated(b) { + // Allocated before unallocated. + return 1 + } + return 0 + }) + } + for i, h := range shdr { + h.shnum = elf.SectionIndex(i) + } + shdrSorted = true +} + func elfsetstring(ctxt *Link, s loader.Sym, str string, off int) { if nelfstr >= len(elfstr) { ctxt.Errorf(s, "too many elf strings") @@ -341,9 +451,14 @@ func newElfPhdr() *ElfPhdr { } func newElfShdr(name int64) *ElfShdr { + if shdrSorted { + Errorf("internal error: creating a section header after they were sorted") + errorexit() + } + e := new(ElfShdr) e.Name = uint32(name) - e.shnum = elf.SectionIndex(ehdr.Shnum) + e.shnum = -1 // make invalid for now, set by elfSortShdrs shdr = append(shdr, e) ehdr.Shnum++ return e @@ -1190,7 +1305,7 @@ func elfshreloc(arch *sys.Arch, sect *sym.Section) *ElfShdr { // its own .rela.text. if sect.Name == ".text" { - if sh.Info != 0 && sh.Info != uint32(sect.Elfsect.(*ElfShdr).shnum) { + if sh.info != nil && sh.info != sect.Elfsect.(*ElfShdr) { sh = elfshnamedup(elfRelType + sect.Name) } } @@ -1200,10 +1315,9 @@ func elfshreloc(arch *sys.Arch, sect *sym.Section) *ElfShdr { if typ == elf.SHT_RELA { sh.Entsize += uint64(arch.RegSize) } - sh.Link = uint32(elfshname(".symtab").shnum) - sh.Info = uint32(sect.Elfsect.(*ElfShdr).shnum) - sh.Off = sect.Reloff - sh.Size = sect.Rellen + sh.link = elfshname(".symtab") + sh.info = sect.Elfsect.(*ElfShdr) + sh.relocSect = sect sh.Addralign = uint64(arch.RegSize) return sh } @@ -1710,19 +1824,6 @@ func asmbElf(ctxt *Link) { var symo int64 symo = int64(Segdwarf.Fileoff + Segdwarf.Filelen) symo = Rnd(symo, int64(ctxt.Arch.PtrSize)) - ctxt.Out.SeekSet(symo) - if *FlagS { - ctxt.Out.Write(elfshstrdat) - } else { - ctxt.Out.SeekSet(symo) - asmElfSym(ctxt) - ctxt.Out.Write(elfstrdat) - ctxt.Out.Write(elfshstrdat) - if ctxt.IsExternal() { - elfEmitReloc(ctxt) - } - } - ctxt.Out.SeekSet(0) ldr := ctxt.loader eh := getElfEhdr() @@ -1947,9 +2048,9 @@ func asmbElf(ctxt *Link) { sh.Entsize = ELF32SYMSIZE } sh.Addralign = uint64(ctxt.Arch.RegSize) - sh.Link = uint32(elfshname(".dynstr").shnum) + sh.link = elfshname(".dynstr") - // sh.info is the index of first non-local symbol (number of local symbols) + // sh.Info is the index of first non-local symbol (number of local symbols) s := ldr.Lookup(".dynsym", 0) i := uint32(0) for sub := s; sub != 0; sub = ldr.SubSym(sub) { @@ -1972,7 +2073,7 @@ func asmbElf(ctxt *Link) { sh.Type = uint32(elf.SHT_GNU_VERSYM) sh.Flags = uint64(elf.SHF_ALLOC) sh.Addralign = 2 - sh.Link = uint32(elfshname(".dynsym").shnum) + sh.link = elfshname(".dynsym") sh.Entsize = 2 shsym(sh, ldr, ldr.Lookup(".gnu.version", 0)) @@ -1981,7 +2082,7 @@ func asmbElf(ctxt *Link) { sh.Flags = uint64(elf.SHF_ALLOC) sh.Addralign = uint64(ctxt.Arch.RegSize) sh.Info = uint32(elfverneed) - sh.Link = uint32(elfshname(".dynstr").shnum) + sh.link = elfshname(".dynstr") shsym(sh, ldr, ldr.Lookup(".gnu.version_r", 0)) } @@ -1991,8 +2092,8 @@ func asmbElf(ctxt *Link) { sh.Flags = uint64(elf.SHF_ALLOC) sh.Entsize = ELF64RELASIZE sh.Addralign = uint64(ctxt.Arch.RegSize) - sh.Link = uint32(elfshname(".dynsym").shnum) - sh.Info = uint32(elfshname(".plt").shnum) + sh.link = elfshname(".dynsym") + sh.info = elfshname(".plt") shsym(sh, ldr, ldr.Lookup(".rela.plt", 0)) sh = elfshname(".rela") @@ -2000,7 +2101,7 @@ func asmbElf(ctxt *Link) { sh.Flags = uint64(elf.SHF_ALLOC) sh.Entsize = ELF64RELASIZE sh.Addralign = 8 - sh.Link = uint32(elfshname(".dynsym").shnum) + sh.link = elfshname(".dynsym") shsym(sh, ldr, ldr.Lookup(".rela", 0)) } else { sh := elfshname(".rel.plt") @@ -2008,7 +2109,7 @@ func asmbElf(ctxt *Link) { sh.Flags = uint64(elf.SHF_ALLOC) sh.Entsize = ELF32RELSIZE sh.Addralign = 4 - sh.Link = uint32(elfshname(".dynsym").shnum) + sh.link = elfshname(".dynsym") shsym(sh, ldr, ldr.Lookup(".rel.plt", 0)) sh = elfshname(".rel") @@ -2016,7 +2117,7 @@ func asmbElf(ctxt *Link) { sh.Flags = uint64(elf.SHF_ALLOC) sh.Entsize = ELF32RELSIZE sh.Addralign = 4 - sh.Link = uint32(elfshname(".dynsym").shnum) + sh.link = elfshname(".dynsym") shsym(sh, ldr, ldr.Lookup(".rel", 0)) } @@ -2071,7 +2172,7 @@ func asmbElf(ctxt *Link) { sh.Flags = uint64(elf.SHF_ALLOC) sh.Entsize = 4 sh.Addralign = uint64(ctxt.Arch.RegSize) - sh.Link = uint32(elfshname(".dynsym").shnum) + sh.link = elfshname(".dynsym") shsym(sh, ldr, ldr.Lookup(".hash", 0)) // sh and elf.PT_DYNAMIC for .dynamic section @@ -2081,7 +2182,7 @@ func asmbElf(ctxt *Link) { sh.Flags = uint64(elf.SHF_ALLOC + elf.SHF_WRITE) sh.Entsize = 2 * uint64(ctxt.Arch.RegSize) sh.Addralign = uint64(ctxt.Arch.RegSize) - sh.Link = uint32(elfshname(".dynstr").shnum) + sh.link = elfshname(".dynstr") shsym(sh, ldr, ldr.Lookup(".dynamic", 0)) ph := newElfPhdr() ph.Type = elf.PT_DYNAMIC @@ -2120,11 +2221,8 @@ func asmbElf(ctxt *Link) { } elfobj: - sh := elfshname(".shstrtab") - eh.Shstrndx = uint16(sh.shnum) - if ctxt.IsMIPS() { - sh = elfshname(".MIPS.abiflags") + sh := elfshname(".MIPS.abiflags") sh.Type = uint32(elf.SHT_MIPS_ABIFLAGS) sh.Flags = uint64(elf.SHF_ALLOC) sh.Addralign = 8 @@ -2190,6 +2288,24 @@ elfobj: sh.Flags = 0 } + elfSortShdrs(ctxt) + + sh := elfshname(".shstrtab") + eh.Shstrndx = uint16(elfShdrShnum(sh)) + + ctxt.Out.SeekSet(symo) + if *FlagS { + ctxt.Out.Write(elfshstrdat) + } else { + asmElfSym(ctxt) + ctxt.Out.Write(elfstrdat) + ctxt.Out.Write(elfshstrdat) + if ctxt.IsExternal() { + elfEmitReloc(ctxt) + } + } + ctxt.Out.SeekSet(0) + var shstroff uint64 if !*FlagS { sh := elfshname(".symtab") @@ -2198,7 +2314,7 @@ elfobj: sh.Size = uint64(symSize) sh.Addralign = uint64(ctxt.Arch.RegSize) sh.Entsize = 8 + 2*uint64(ctxt.Arch.RegSize) - sh.Link = uint32(elfshname(".strtab").shnum) + sh.link = elfshname(".strtab") sh.Info = uint32(elfglobalsymndx) sh = elfshname(".strtab") diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go index a0345ca1c7b..eb2a302c05e 100644 --- a/src/cmd/link/internal/ld/symtab.go +++ b/src/cmd/link/internal/ld/symtab.go @@ -101,7 +101,7 @@ func putelfsym(ctxt *Link, x loader.Sym, typ elf.SymType, curbind elf.SymBind) { ldr.Errorf(x, "missing ELF section in putelfsym") return } - elfshnum = xosect.Elfsect.(*ElfShdr).shnum + elfshnum = elfShdrShnum(xosect.Elfsect.(*ElfShdr)) } sname := ldr.SymExtname(x) From b0c278be4072b2aec941e4600852f7a5ff40fe22 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Fri, 7 Nov 2025 11:20:15 -0800 Subject: [PATCH 089/140] cmd/link: use shdr as a slice rather than counting in elfhdr.Shnum Change-Id: I293e50e3a6ab19fb927099e106095d6aa1241b9f Reviewed-on: https://go-review.googlesource.com/c/go/+/718820 Reviewed-by: Junyang Shao LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui --- src/cmd/link/internal/ld/elf.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/cmd/link/internal/ld/elf.go b/src/cmd/link/internal/ld/elf.go index ba0c181daf5..6bda9f0ef55 100644 --- a/src/cmd/link/internal/ld/elf.go +++ b/src/cmd/link/internal/ld/elf.go @@ -365,16 +365,16 @@ func elf32shdr(out *OutBuf, e *ElfShdr) { func elfwriteshdrs(out *OutBuf) uint32 { if elf64 { - for i := 0; i < int(ehdr.Shnum); i++ { - elf64shdr(out, shdr[i]) + for _, sh := range shdr { + elf64shdr(out, sh) } - return uint32(ehdr.Shnum) * ELF64SHDRSIZE + return uint32(len(shdr)) * ELF64SHDRSIZE } - for i := 0; i < int(ehdr.Shnum); i++ { - elf32shdr(out, shdr[i]) + for _, sh := range shdr { + elf32shdr(out, sh) } - return uint32(ehdr.Shnum) * ELF32SHDRSIZE + return uint32(len(shdr)) * ELF32SHDRSIZE } // elfSortShdrs sorts the section headers so that allocated sections @@ -460,7 +460,6 @@ func newElfShdr(name int64) *ElfShdr { e.Name = uint32(name) e.shnum = -1 // make invalid for now, set by elfSortShdrs shdr = append(shdr, e) - ehdr.Shnum++ return e } @@ -1172,8 +1171,7 @@ func elfshname(name string) *ElfShdr { continue } off := elfstr[i].off - for i = 0; i < int(ehdr.Shnum); i++ { - sh := shdr[i] + for _, sh := range shdr { if sh.Name == uint32(off) { return sh } @@ -2380,6 +2378,11 @@ elfobj: pph.Memsz = pph.Filesz } + if len(shdr) >= 0xffff { + Errorf("too many ELF sections") + } + eh.Shnum = uint16(len(shdr)) + ctxt.Out.SeekSet(0) a := int64(0) a += int64(elfwritehdr(ctxt.Out)) From 4bc3410b6cf4f4e4535c6620e2643e0aa36ed99f Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Fri, 7 Nov 2025 12:25:03 -0800 Subject: [PATCH 090/140] cmd/link: build shstrtab from ELF sections Since before Go 1 the Go linker has handled ELF by first building the ELF section string table, and then pointing ELF section headers to it. This duplicates code as sections are effectively created twice, once with the name and then again with the full section header. The code duplication also means that it's easy to create unnecessary section names; for example, every internally linked Go program currently contains the string ".go.fuzzcntrs" although most do not have a section by that name. This CL changes the linker to simply build the section string table after all the sections are known. Change-Id: I27ba15b2af3dc1b8d7436b6c409f818aa8e6bfb4 Reviewed-on: https://go-review.googlesource.com/c/go/+/718840 Reviewed-by: Cherry Mui Reviewed-by: David Chase LUCI-TryBot-Result: Go LUCI --- src/cmd/link/internal/ld/dwarf.go | 22 --- src/cmd/link/internal/ld/elf.go | 240 ++++++++---------------------- 2 files changed, 62 insertions(+), 200 deletions(-) diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index eeac497850f..ff0fa5c377d 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -2394,28 +2394,6 @@ func (d *dwctxt) collectUnitLocs(u *sym.CompilationUnit) []loader.Sym { return syms } -// Add DWARF section names to the section header string table, by calling add -// on each name. ELF only. -func dwarfaddshstrings(ctxt *Link, add func(string)) { - if *FlagW { // disable dwarf - return - } - - secs := []string{"abbrev", "frame", "info", "loc", "line", "gdb_scripts"} - if buildcfg.Experiment.Dwarf5 { - secs = append(secs, "addr", "rnglists", "loclists") - } else { - secs = append(secs, "ranges", "loc") - } - - for _, sec := range secs { - add(".debug_" + sec) - if ctxt.IsExternal() { - add(elfRelType + ".debug_" + sec) - } - } -} - func dwarfaddelfsectionsyms(ctxt *Link) { if *FlagW { // disable dwarf return diff --git a/src/cmd/link/internal/ld/elf.go b/src/cmd/link/internal/ld/elf.go index 6bda9f0ef55..c9480222c30 100644 --- a/src/cmd/link/internal/ld/elf.go +++ b/src/cmd/link/internal/ld/elf.go @@ -71,10 +71,15 @@ import ( // ElfEhdr is the ELF file header. type ElfEhdr elf.Header64 -// ElfShdr is an ELF section entry, plus the section index. +// ElfShdr is an ELF section table entry. type ElfShdr struct { elf.Section64 + // nameString is the section name as a string. + // This is not to be confused with Name, + // inherited from elf.Section64, which is an offset. + nameString string + // The section index, set by elfSortShdrs. // Don't read this directly, use elfShdrShnum. shnum elf.SectionIndex @@ -109,7 +114,7 @@ const ( ELF32RELSIZE = 8 ) -var elfstrdat, elfshstrdat []byte +var elfstrdat []byte // ELFRESERVE is the total amount of space to reserve at the // start of the file for Header, PHeaders, SHeaders, and interp. @@ -158,15 +163,6 @@ type ELFArch struct { DynamicReadOnly bool } -type Elfstring struct { - s string - off int -} - -var elfstr [100]Elfstring - -var nelfstr int - var buildinfo []byte // Elfinit initializes the global ehdr variable that holds the ELF header. @@ -413,15 +409,46 @@ func elfSortShdrs(ctxt *Link) { shdrSorted = true } -func elfsetstring(ctxt *Link, s loader.Sym, str string, off int) { - if nelfstr >= len(elfstr) { - ctxt.Errorf(s, "too many elf strings") - errorexit() +// elfWriteShstrtab writes out the ELF section string table. +// It also sets the Name field of the section headers. +// It returns the length of the string table. +func elfWriteShstrtab(ctxt *Link) uint32 { + // Map from section name to shstrtab offset. + m := make(map[string]uint32, len(shdr)) + + m[""] = 0 + ctxt.Out.WriteByte(0) + off := uint32(1) + + writeString := func(s string) { + m[s] = off + ctxt.Out.WriteString(s) + ctxt.Out.WriteByte(0) + off += uint32(len(s)) + 1 } - elfstr[nelfstr].s = str - elfstr[nelfstr].off = off - nelfstr++ + // As a minor optimization, do the relocation sections first, + // as they may let us reuse the suffix. + // That is, the offset for ".text" can point into ".rel.text". + // We don't do a full suffix search as the relocation sections + // are likely to be the only match. + for _, sh := range shdr { + if suffix, ok := strings.CutPrefix(sh.nameString, elfRelType); ok { + m[suffix] = off + uint32(len(elfRelType)) + writeString(sh.nameString) + } + } + + for _, sh := range shdr { + if shOff, ok := m[sh.nameString]; ok { + sh.Name = shOff + } else { + sh.Name = off + writeString(sh.nameString) + } + } + + return off } func elfwritephdrs(out *OutBuf) uint32 { @@ -450,15 +477,16 @@ func newElfPhdr() *ElfPhdr { return e } -func newElfShdr(name int64) *ElfShdr { +func newElfShdr(name string) *ElfShdr { if shdrSorted { Errorf("internal error: creating a section header after they were sorted") errorexit() } - e := new(ElfShdr) - e.Name = uint32(name) - e.shnum = -1 // make invalid for now, set by elfSortShdrs + e := &ElfShdr{ + nameString: name, + shnum: -1, // make invalid for now, set by elfSortShdrs + } shdr = append(shdr, e) return e } @@ -1165,36 +1193,20 @@ func elfphrelro(seg *sym.Segment) { ph.Align = uint64(*FlagRound) } +// elfshname finds or creates a section given its name. func elfshname(name string) *ElfShdr { - for i := 0; i < nelfstr; i++ { - if name != elfstr[i].s { - continue + for _, sh := range shdr { + if sh.nameString == name { + return sh } - off := elfstr[i].off - for _, sh := range shdr { - if sh.Name == uint32(off) { - return sh - } - } - return newElfShdr(int64(off)) } - Exitf("cannot find elf name %s", name) - return nil + return newElfShdr(name) } -// Create an ElfShdr for the section with name. -// Create a duplicate if one already exists with that name. +// elfshnamedup creates a new section with a given name. +// If there is an existing section with this name, it creates a duplicate. func elfshnamedup(name string) *ElfShdr { - for i := 0; i < nelfstr; i++ { - if name == elfstr[i].s { - off := elfstr[i].off - return newElfShdr(int64(off)) - } - } - - Errorf("cannot find elf name %s", name) - errorexit() - return nil + return newElfShdr(name) } func elfshalloc(sect *sym.Section) *ElfShdr { @@ -1446,140 +1458,11 @@ func addgonote(ctxt *Link, sectionName string, tag uint32, desc []byte) { func (ctxt *Link) doelf() { ldr := ctxt.loader - // Predefine strings we need for section headers. - - addshstr := func(s string) int { - off := len(elfshstrdat) - elfshstrdat = append(elfshstrdat, s...) - elfshstrdat = append(elfshstrdat, 0) - return off - } - - shstrtabAddstring := func(s string) { - off := addshstr(s) - elfsetstring(ctxt, 0, s, off) - } - - shstrtabAddstring("") - shstrtabAddstring(".text") - shstrtabAddstring(".noptrdata") - shstrtabAddstring(".data") - shstrtabAddstring(".bss") - shstrtabAddstring(".noptrbss") - shstrtabAddstring(".go.fuzzcntrs") - shstrtabAddstring(".go.buildinfo") - shstrtabAddstring(".go.fipsinfo") - if ctxt.IsMIPS() { - shstrtabAddstring(".MIPS.abiflags") - shstrtabAddstring(".gnu.attributes") - } - - // generate .tbss section for dynamic internal linker or external - // linking, so that various binutils could correctly calculate - // PT_TLS size. See https://golang.org/issue/5200. - if !*FlagD || ctxt.IsExternal() { - shstrtabAddstring(".tbss") - } - if ctxt.IsNetbsd() { - shstrtabAddstring(".note.netbsd.ident") - if *flagRace { - shstrtabAddstring(".note.netbsd.pax") - } - } - if ctxt.IsOpenbsd() { - shstrtabAddstring(".note.openbsd.ident") - } - if ctxt.IsFreebsd() { - shstrtabAddstring(".note.tag") - } - if len(buildinfo) > 0 { - shstrtabAddstring(".note.gnu.build-id") - } - if *flagBuildid != "" { - shstrtabAddstring(".note.go.buildid") - } - shstrtabAddstring(".elfdata") - shstrtabAddstring(".rodata") - shstrtabAddstring(".gopclntab") - // See the comment about data.rel.ro.FOO section names in data.go. - relro_prefix := "" - if ctxt.UseRelro() { - shstrtabAddstring(".data.rel.ro") - relro_prefix = ".data.rel.ro" - } - shstrtabAddstring(relro_prefix + ".typelink") - shstrtabAddstring(relro_prefix + ".itablink") - if ctxt.IsExternal() { *FlagD = true - - shstrtabAddstring(elfRelType + ".text") - shstrtabAddstring(elfRelType + ".rodata") - shstrtabAddstring(elfRelType + relro_prefix + ".typelink") - shstrtabAddstring(elfRelType + relro_prefix + ".itablink") - shstrtabAddstring(elfRelType + ".noptrdata") - shstrtabAddstring(elfRelType + ".data") - if ctxt.UseRelro() { - shstrtabAddstring(elfRelType + ".data.rel.ro") - } - shstrtabAddstring(elfRelType + ".go.buildinfo") - shstrtabAddstring(elfRelType + ".go.fipsinfo") - if ctxt.IsMIPS() { - shstrtabAddstring(elfRelType + ".MIPS.abiflags") - shstrtabAddstring(elfRelType + ".gnu.attributes") - } - - // add a .note.GNU-stack section to mark the stack as non-executable - shstrtabAddstring(".note.GNU-stack") - - if ctxt.IsShared() { - shstrtabAddstring(".note.go.abihash") - shstrtabAddstring(".note.go.pkg-list") - shstrtabAddstring(".note.go.deps") - } } - hasinitarr := ctxt.linkShared - - // Shared library initializer. - switch ctxt.BuildMode { - case BuildModeCArchive, BuildModeCShared, BuildModeShared, BuildModePlugin: - hasinitarr = true - } - - if hasinitarr { - shstrtabAddstring(".init_array") - shstrtabAddstring(elfRelType + ".init_array") - } - - if !*FlagS { - shstrtabAddstring(".symtab") - shstrtabAddstring(".strtab") - } - if !*FlagW { - dwarfaddshstrings(ctxt, shstrtabAddstring) - } - - shstrtabAddstring(".shstrtab") - if !*FlagD { // -d suppresses dynamic loader format - shstrtabAddstring(".interp") - shstrtabAddstring(".hash") - shstrtabAddstring(".got") - if ctxt.IsPPC64() { - shstrtabAddstring(".glink") - } - shstrtabAddstring(".got.plt") - shstrtabAddstring(".dynamic") - shstrtabAddstring(".dynsym") - shstrtabAddstring(".dynstr") - shstrtabAddstring(elfRelType) - shstrtabAddstring(elfRelType + ".plt") - - shstrtabAddstring(".plt") - shstrtabAddstring(".gnu.version") - shstrtabAddstring(".gnu.version_r") - // dynamic symbol table - first entry all zeros dynsym := ldr.CreateSymForUpdate(".dynsym", 0) @@ -2291,13 +2174,14 @@ elfobj: sh := elfshname(".shstrtab") eh.Shstrndx = uint16(elfShdrShnum(sh)) + var shstrtabLen uint32 ctxt.Out.SeekSet(symo) if *FlagS { - ctxt.Out.Write(elfshstrdat) + shstrtabLen = elfWriteShstrtab(ctxt) } else { asmElfSym(ctxt) ctxt.Out.Write(elfstrdat) - ctxt.Out.Write(elfshstrdat) + shstrtabLen = elfWriteShstrtab(ctxt) if ctxt.IsExternal() { elfEmitReloc(ctxt) } @@ -2328,7 +2212,7 @@ elfobj: sh = elfshname(".shstrtab") sh.Type = uint32(elf.SHT_STRTAB) sh.Off = shstroff - sh.Size = uint64(len(elfshstrdat)) + sh.Size = uint64(shstrtabLen) sh.Addralign = 1 // Main header From b437d5bf36b35d088ec8b15168b17ab427c04f7f Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Mon, 10 Nov 2025 16:08:48 -0800 Subject: [PATCH 091/140] cmd/link: put funcdata symbols in .gopclntab section There is a test for this in CL 721460 later in this series. For #76038 Change-Id: Icd7a52cbabde5162139dbc4b2c61306c0c748545 Reviewed-on: https://go-review.googlesource.com/c/go/+/719440 Reviewed-by: David Chase LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui --- src/cmd/link/internal/ld/data.go | 2 + src/cmd/link/internal/ld/pcln.go | 179 ++++++++++++++++++++++++++--- src/cmd/link/internal/ld/symtab.go | 26 +---- 3 files changed, 165 insertions(+), 42 deletions(-) diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index e7e202fc1f6..f25b6c11da9 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -2128,6 +2128,7 @@ func (state *dodataState) allocateDataSections(ctxt *Link) { ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.filetab", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pctab", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.functab", 0), sect) + ldr.SetSymSect(ldr.LookupOrCreateSym("go:func.*", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.epclntab", 0), sect) setCarrierSize(sym.SPCLNTAB, int64(sect.Length)) if ctxt.HeadType == objabi.Haix { @@ -3066,6 +3067,7 @@ func (ctxt *Link) address() []*sym.Segment { ctxt.defineInternal("runtime.filetab", sym.SRODATA) ctxt.defineInternal("runtime.pctab", sym.SRODATA) ctxt.defineInternal("runtime.functab", sym.SRODATA) + ctxt.defineInternal("go:func.*", sym.SRODATA) ctxt.xdefine("runtime.epclntab", sym.SRODATA, int64(pclntab.Vaddr+pclntab.Length)) ctxt.xdefine("runtime.noptrdata", sym.SNOPTRDATA, int64(noptr.Vaddr)) ctxt.xdefine("runtime.enoptrdata", sym.SNOPTRDATAEND, int64(noptr.Vaddr+noptr.Length)) diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go index 68af94a405a..eef8e810878 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -10,10 +10,12 @@ import ( "cmd/internal/sys" "cmd/link/internal/loader" "cmd/link/internal/sym" + "cmp" "fmt" "internal/abi" "internal/buildcfg" "path/filepath" + "slices" "strings" ) @@ -36,6 +38,7 @@ type pclntab struct { cutab loader.Sym filetab loader.Sym pctab loader.Sym + funcdata loader.Sym // The number of functions + number of TEXT sections - 1. This is such an // unexpected value because platforms that have more than one TEXT section @@ -183,7 +186,7 @@ func genInlTreeSym(ctxt *Link, cu *sym.CompilationUnit, fi loader.FuncInfo, arch // signal to the symtab() phase that it needs to be grouped in with // other similar symbols (gcdata, etc); the dodata() phase will // eventually switch the type back to SRODATA. - inlTreeSym.SetType(sym.SGOFUNC) + inlTreeSym.SetType(sym.SPCLNTAB) ldr.SetAttrReachable(its, true) ldr.SetSymAlign(its, 4) // it has 32-bit fields ninl := fi.NumInlTree() @@ -518,6 +521,157 @@ func (state *pclntab) generatePctab(ctxt *Link, funcs []loader.Sym) { state.pctab = state.addGeneratedSym(ctxt, "runtime.pctab", size, writePctab) } +// generateFuncdata writes out the funcdata information. +func (state *pclntab) generateFuncdata(ctxt *Link, funcs []loader.Sym, inlsyms map[loader.Sym]loader.Sym) { + ldr := ctxt.loader + + // Walk the functions and collect the funcdata. + seen := make(map[loader.Sym]struct{}, len(funcs)) + fdSyms := make([]loader.Sym, 0, len(funcs)) + fd := []loader.Sym{} + for _, s := range funcs { + fi := ldr.FuncInfo(s) + if !fi.Valid() { + continue + } + fi.Preload() + fd := funcData(ldr, s, fi, inlsyms[s], fd) + for j, fdSym := range fd { + if ignoreFuncData(ldr, s, j, fdSym) { + continue + } + + if _, ok := seen[fdSym]; !ok { + fdSyms = append(fdSyms, fdSym) + seen[fdSym] = struct{}{} + } + } + } + seen = nil + + // Sort the funcdata in reverse order by alignment + // to minimize alignment gaps. Use a stable sort + // for reproducible results. + var maxAlign int32 + slices.SortStableFunc(fdSyms, func(a, b loader.Sym) int { + aAlign := symalign(ldr, a) + bAlign := symalign(ldr, b) + + // Remember maximum alignment. + maxAlign = max(maxAlign, aAlign, bAlign) + + // Negate to sort by decreasing alignment. + return -cmp.Compare(aAlign, bAlign) + }) + + // We will output the symbols in the order of fdSyms. + // Set the value of each symbol to its offset in the funcdata. + // This way when writeFuncs writes out the funcdata offset, + // it can simply write out the symbol value. + + // Accumulated size of funcdata info. + size := int64(0) + + for _, fdSym := range fdSyms { + datSize := ldr.SymSize(fdSym) + if datSize == 0 { + ctxt.Errorf(fdSym, "zero size funcdata") + continue + } + + size = Rnd(size, int64(symalign(ldr, fdSym))) + ldr.SetSymValue(fdSym, size) + size += datSize + + // We do not put the funcdata symbols in the symbol table. + ldr.SetAttrNotInSymbolTable(fdSym, true) + + // Mark the symbol as special so that it does not get + // adjusted by the section offset. + ldr.SetAttrSpecial(fdSym, true) + } + + // Funcdata symbols are permitted to have R_ADDROFF relocations, + // which the linker can fully resolve. + resolveRelocs := func(ldr *loader.Loader, fdSym loader.Sym, data []byte) { + relocs := ldr.Relocs(fdSym) + for i := 0; i < relocs.Count(); i++ { + r := relocs.At(i) + if r.Type() != objabi.R_ADDROFF { + ctxt.Errorf(fdSym, "unsupported reloc %d (%s) for funcdata symbol", r.Type(), sym.RelocName(ctxt.Target.Arch, r.Type())) + return + } + if r.Siz() != 4 { + ctxt.Errorf(fdSym, "unsupported ADDROFF reloc size %d for funcdata symbol", r.Siz()) + return + } + rs := r.Sym() + if r.Weak() && !ldr.AttrReachable(rs) { + return + } + sect := ldr.SymSect(rs) + if sect == nil { + ctxt.Errorf(fdSym, "missing section for relocation target %s for funcdata symbol", ldr.SymName(rs)) + } + o := ldr.SymValue(rs) + if sect.Name != ".text" { + o -= int64(sect.Vaddr) + } else { + // With multiple .text sections the offset + // is from the start of the first one. + o -= int64(Segtext.Sections[0].Vaddr) + if ctxt.Target.IsWasm() { + if o&(1<<16-1) != 0 { + ctxt.Errorf(fdSym, "textoff relocation does not target function entry for funcdata symbol: %s %#x", ldr.SymName(rs), o) + } + o >>= 16 + } + } + o += r.Add() + if o != int64(int32(o)) && o != int64(uint32(o)) { + ctxt.Errorf(fdSym, "ADDROFF relocation out of range for funcdata symbol: %#x", o) + } + ctxt.Target.Arch.ByteOrder.PutUint32(data[r.Off():], uint32(o)) + } + } + + writeFuncData := func(ctxt *Link, s loader.Sym) { + ldr := ctxt.loader + sb := ldr.MakeSymbolUpdater(s) + for _, fdSym := range fdSyms { + off := ldr.SymValue(fdSym) + fdSymData := ldr.Data(fdSym) + sb.SetBytesAt(off, fdSymData) + // Resolve any R_ADDROFF relocations. + resolveRelocs(ldr, fdSym, sb.Data()[off:off+int64(len(fdSymData))]) + } + } + + state.funcdata = state.addGeneratedSym(ctxt, "go:func.*", size, writeFuncData) + + // Because the funcdata previously was not in pclntab, + // we need to keep the visible symbol so that tools can find it. + ldr.SetAttrNotInSymbolTable(state.funcdata, false) +} + +// ignoreFuncData reports whether we should ignore a funcdata symbol. +// +// cmd/internal/obj optimistically populates ArgsPointerMaps and +// ArgInfo for assembly functions, hoping that the compiler will +// emit appropriate symbols from their Go stub declarations. If +// it didn't though, just ignore it. +// +// TODO(cherryyz): Fix arg map generation (see discussion on CL 523335). +func ignoreFuncData(ldr *loader.Loader, s loader.Sym, j int, fdSym loader.Sym) bool { + if fdSym == 0 { + return true + } + if (j == abi.FUNCDATA_ArgsPointerMaps || j == abi.FUNCDATA_ArgInfo) && ldr.IsFromAssembly(s) && ldr.Data(fdSym) == nil { + return true + } + return false +} + // numPCData returns the number of PCData syms for the FuncInfo. // NB: Preload must be called on valid FuncInfos before calling this function. func numPCData(ldr *loader.Loader, s loader.Sym, fi loader.FuncInfo) uint32 { @@ -656,8 +810,6 @@ func writePCToFunc(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, sta func writeFuncs(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, inlSyms map[loader.Sym]loader.Sym, startLocations, cuOffsets []uint32, nameOffsets map[loader.Sym]uint32) { ldr := ctxt.loader deferReturnSym := ldr.Lookup("runtime.deferreturn", abiInternalVer) - gofunc := ldr.Lookup("go:func.*", 0) - gofuncBase := ldr.SymValue(gofunc) textStart := ldr.SymValue(ldr.Lookup("runtime.text", 0)) funcdata := []loader.Sym{} var pcsp, pcfile, pcline, pcinline loader.Sym @@ -755,25 +907,12 @@ func writeFuncs(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, inlSym dataoff := off + int64(4*j) fdsym := funcdata[j] - // cmd/internal/obj optimistically populates ArgsPointerMaps and - // ArgInfo for assembly functions, hoping that the compiler will - // emit appropriate symbols from their Go stub declarations. If - // it didn't though, just ignore it. - // - // TODO(cherryyz): Fix arg map generation (see discussion on CL 523335). - if fdsym != 0 && (j == abi.FUNCDATA_ArgsPointerMaps || j == abi.FUNCDATA_ArgInfo) && ldr.IsFromAssembly(s) && ldr.Data(fdsym) == nil { - fdsym = 0 - } - - if fdsym == 0 { + if ignoreFuncData(ldr, s, j, fdsym) { sb.SetUint32(ctxt.Arch, dataoff, ^uint32(0)) // ^0 is a sentinel for "no value" continue } - if outer := ldr.OuterSym(fdsym); outer != gofunc { - panic(fmt.Sprintf("bad carrier sym for symbol %s (funcdata %s#%d), want go:func.* got %s", ldr.SymName(fdsym), ldr.SymName(s), j, ldr.SymName(outer))) - } - sb.SetUint32(ctxt.Arch, dataoff, uint32(ldr.SymValue(fdsym)-gofuncBase)) + sb.SetUint32(ctxt.Arch, dataoff, uint32(ldr.SymValue(fdsym))) } } } @@ -816,6 +955,9 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { // function table, alternating PC and offset to func struct [each entry thearch.ptrsize bytes] // end PC [thearch.ptrsize bytes] // func structures, pcdata offsets, func data. + // + // runtime.funcdata + // []byte of deduplicated funcdata state, compUnits, funcs := makePclntab(ctxt, container) @@ -831,6 +973,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { state.generatePctab(ctxt, funcs) inlSyms := makeInlSyms(ctxt, funcs, nameOffsets) state.generateFunctab(ctxt, funcs, inlSyms, cuOffsets, nameOffsets) + state.generateFuncdata(ctxt, funcs, inlSyms) return state } diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go index eb2a302c05e..f9bc7007eda 100644 --- a/src/cmd/link/internal/ld/symtab.go +++ b/src/cmd/link/internal/ld/symtab.go @@ -496,13 +496,13 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind { } var ( symgostring = groupSym("go:string.*", sym.SGOSTRING) - symgofunc = groupSym("go:func.*", sym.SGOFUNC) + symgofunc = groupSym("go:funcdesc", sym.SGOFUNC) symgcbits = groupSym("runtime.gcbits.*", sym.SGCBITS) ) symgofuncrel := symgofunc if ctxt.UseRelro() { - symgofuncrel = groupSym("go:funcrel.*", sym.SGOFUNCRELRO) + symgofuncrel = groupSym("go:funcdescrel", sym.SGOFUNCRELRO) } // assign specific types so that they sort together. @@ -548,28 +548,6 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind { ldr.SetCarrierSym(s, symgofunc) } - case strings.HasPrefix(name, "gcargs."), - strings.HasPrefix(name, "gclocals."), - strings.HasPrefix(name, "gclocals·"), - ldr.SymType(s) == sym.SGOFUNC && s != symgofunc, // inltree, see pcln.go - strings.HasSuffix(name, ".opendefer"), - strings.HasSuffix(name, ".arginfo0"), - strings.HasSuffix(name, ".arginfo1"), - strings.HasSuffix(name, ".argliveinfo"), - strings.HasSuffix(name, ".wrapinfo"), - strings.HasSuffix(name, ".args_stackmap"), - strings.HasSuffix(name, ".stkobj"): - ldr.SetAttrNotInSymbolTable(s, true) - symGroupType[s] = sym.SGOFUNC - ldr.SetCarrierSym(s, symgofunc) - if ctxt.Debugvlog != 0 { - align := ldr.SymAlign(s) - liveness += (ldr.SymSize(s) + int64(align) - 1) &^ (int64(align) - 1) - } - - // Note: Check for "type:" prefix after checking for .arginfo1 suffix. - // That way symbols like "type:.eq.[2]interface {}.arginfo1" that belong - // in go:func.* end up there. case strings.HasPrefix(name, "type:"): if !ctxt.DynlinkingGo() { ldr.SetAttrNotInSymbolTable(s, true) From 312b2034a4e16583fac00070e698c3d464eca1c8 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Tue, 11 Nov 2025 15:53:24 -0800 Subject: [PATCH 092/140] cmd/link: put runtime.findfunctab in the .gopclntab section There is a test for this in CL 721461 later in this series. For #76038 Change-Id: I15f9a8d0d5bd9424702a9ca7febb2fa76035aaf8 Reviewed-on: https://go-review.googlesource.com/c/go/+/719743 Reviewed-by: David Chase Reviewed-by: Cherry Mui Reviewed-by: Florian Lehner LUCI-TryBot-Result: Go LUCI --- src/cmd/link/internal/ld/pcln.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go index eef8e810878..472fa715c74 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -1075,7 +1075,7 @@ func (ctxt *Link) findfunctab(state *pclntab, container loader.Bitmap) { } } - state.findfunctab = ctxt.createGeneratorSymbol("runtime.findfunctab", 0, sym.SRODATA, size, writeFindFuncTab) + state.findfunctab = ctxt.createGeneratorSymbol("runtime.findfunctab", 0, sym.SPCLNTAB, size, writeFindFuncTab) ldr.SetAttrReachable(state.findfunctab, true) ldr.SetAttrLocal(state.findfunctab, true) } From 43cfd785e72ccd04fe638395aa80029aae23fef6 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Thu, 13 Nov 2025 13:01:14 -0800 Subject: [PATCH 093/140] cmd/link, runtime, debug/gosym: move pclntab magic to internal/abi Change-Id: I2d3c41b0e61b994d7b04bd16a791fd226dc45269 Reviewed-on: https://go-review.googlesource.com/c/go/+/720302 Reviewed-by: Michael Knyszek Reviewed-by: David Chase Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI --- src/cmd/link/internal/ld/pcln.go | 2 +- src/debug/gosym/pclntab.go | 33 ++++++++++++++++---------------- src/internal/abi/symtab.go | 30 +++++++++++++++++++++++++++++ src/runtime/symtab.go | 14 +++++++------- 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go index 472fa715c74..1eb7112e644 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -261,7 +261,7 @@ func (state *pclntab) generatePCHeader(ctxt *Link) { // Write header. // Keep in sync with runtime/symtab.go:pcHeader and package debug/gosym. - header.SetUint32(ctxt.Arch, 0, 0xfffffff1) + header.SetUint32(ctxt.Arch, 0, uint32(abi.CurrentPCLnTabMagic)) header.SetUint8(ctxt.Arch, 6, uint8(ctxt.Arch.MinLC)) header.SetUint8(ctxt.Arch, 7, uint8(ctxt.Arch.PtrSize)) off := header.SetUint(ctxt.Arch, 8, uint64(state.nfunc)) diff --git a/src/debug/gosym/pclntab.go b/src/debug/gosym/pclntab.go index 1d5498e0376..da80c8460d5 100644 --- a/src/debug/gosym/pclntab.go +++ b/src/debug/gosym/pclntab.go @@ -11,6 +11,7 @@ package gosym import ( "bytes" "encoding/binary" + "internal/abi" "sort" "sync" ) @@ -174,13 +175,6 @@ func (t *LineTable) isGo12() bool { return t.version >= ver12 } -const ( - go12magic = 0xfffffffb - go116magic = 0xfffffffa - go118magic = 0xfffffff0 - go120magic = 0xfffffff1 -) - // uintptr returns the pointer-sized value encoded at b. // The pointer size is dictated by the table being read. func (t *LineTable) uintptr(b []byte) uint64 { @@ -220,24 +214,29 @@ func (t *LineTable) parsePclnTab() { } var possibleVersion version - leMagic := binary.LittleEndian.Uint32(t.Data) - beMagic := binary.BigEndian.Uint32(t.Data) + + // The magic numbers are chosen such that reading the value with + // a different endianness does not result in the same value. + // That lets us the magic number to determine the endianness. + leMagic := abi.PCLnTabMagic(binary.LittleEndian.Uint32(t.Data)) + beMagic := abi.PCLnTabMagic(binary.BigEndian.Uint32(t.Data)) + switch { - case leMagic == go12magic: + case leMagic == abi.Go12PCLnTabMagic: t.binary, possibleVersion = binary.LittleEndian, ver12 - case beMagic == go12magic: + case beMagic == abi.Go12PCLnTabMagic: t.binary, possibleVersion = binary.BigEndian, ver12 - case leMagic == go116magic: + case leMagic == abi.Go116PCLnTabMagic: t.binary, possibleVersion = binary.LittleEndian, ver116 - case beMagic == go116magic: + case beMagic == abi.Go116PCLnTabMagic: t.binary, possibleVersion = binary.BigEndian, ver116 - case leMagic == go118magic: + case leMagic == abi.Go118PCLnTabMagic: t.binary, possibleVersion = binary.LittleEndian, ver118 - case beMagic == go118magic: + case beMagic == abi.Go118PCLnTabMagic: t.binary, possibleVersion = binary.BigEndian, ver118 - case leMagic == go120magic: + case leMagic == abi.Go120PCLnTabMagic: t.binary, possibleVersion = binary.LittleEndian, ver120 - case beMagic == go120magic: + case beMagic == abi.Go120PCLnTabMagic: t.binary, possibleVersion = binary.BigEndian, ver120 default: return diff --git a/src/internal/abi/symtab.go b/src/internal/abi/symtab.go index 86d67003886..10033e72779 100644 --- a/src/internal/abi/symtab.go +++ b/src/internal/abi/symtab.go @@ -4,6 +4,36 @@ package abi +// PCLnTabMagic is the version at the start of the PC/line table. +// This is the start of the .pclntab section, and is also runtime.pcHeader. +// The magic numbers are chosen such that reading the value with +// a different endianness does not result in the same value. +// That lets us the magic number to determine the endianness. +type PCLnTabMagic uint32 + +const ( + // Initial PCLnTabMagic value used in Go 1.2 through Go 1.15. + Go12PCLnTabMagic PCLnTabMagic = 0xfffffffb + // PCLnTabMagic value used in Go 1.16 through Go 1.17. + // Several fields added to header (CL 241598). + Go116PCLnTabMagic PCLnTabMagic = 0xfffffffa + // PCLnTabMagic value used in Go 1.18 through Go 1.19. + // Entry PC of func data changed from address to offset (CL 351463). + Go118PCLnTabMagic PCLnTabMagic = 0xfffffff0 + // PCLnTabMagic value used in Go 1.20 and later. + // A ":" was added to generated symbol names (#37762). + Go120PCLnTabMagic PCLnTabMagic = 0xfffffff1 + + // CurrentPCLnTabMagic is the value emitted by the current toolchain. + // This is written by the linker to the pcHeader and read by the + // runtime and debug/gosym (and external tools like Delve). + // + // Change this value when updating the pclntab version. + // Changing this exported value is OK because is an + // internal package. + CurrentPCLnTabMagic = Go120PCLnTabMagic +) + // A FuncFlag records bits about a function, passed to the runtime. type FuncFlag uint8 diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go index 3a814cd2032..c1643c1b396 100644 --- a/src/runtime/symtab.go +++ b/src/runtime/symtab.go @@ -374,12 +374,12 @@ func (f *_func) funcInfo() funcInfo { // pcHeader holds data used by the pclntab lookups. type pcHeader struct { - magic uint32 // 0xFFFFFFF1 - pad1, pad2 uint8 // 0,0 - minLC uint8 // min instruction size - ptrSize uint8 // size of a ptr in bytes - nfunc int // number of functions in the module - nfiles uint // number of entries in the file tab + magic abi.PCLnTabMagic // abi.Go1NNPcLnTabMagic + pad1, pad2 uint8 // 0,0 + minLC uint8 // min instruction size + ptrSize uint8 // size of a ptr in bytes + nfunc int // number of functions in the module + nfiles uint // number of entries in the file tab // The next field used to be textStart. This is no longer stored // as it requires a relocation. Code should use the moduledata text @@ -623,7 +623,7 @@ const debugPcln = false func moduledataverify1(datap *moduledata) { // Check that the pclntab's format is valid. hdr := datap.pcHeader - if hdr.magic != 0xfffffff1 || hdr.pad1 != 0 || hdr.pad2 != 0 || + if hdr.magic != abi.CurrentPCLnTabMagic || hdr.pad1 != 0 || hdr.pad2 != 0 || hdr.minLC != sys.PCQuantum || hdr.ptrSize != goarch.PtrSize { println("runtime: pcHeader: magic=", hex(hdr.magic), "pad1=", hdr.pad1, "pad2=", hdr.pad2, "minLC=", hdr.minLC, "ptrSize=", hdr.ptrSize, "pluginpath=", datap.pluginpath) From 9f5cd43fe612834e78b838c1e704bf7a98011749 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Fri, 14 Nov 2025 11:48:55 -0800 Subject: [PATCH 094/140] cmd/link: put moduledata in its own .go.module section There is a test for this in CL 721480 later in this series. For #76038 Change-Id: Ib7ed1f0b0aed2d929ca0f135b54d6b62112cae30 Reviewed-on: https://go-review.googlesource.com/c/go/+/720660 TryBot-Bypass: David Chase Reviewed-by: Cherry Mui Reviewed-by: David Chase --- src/cmd/link/internal/ld/data.go | 20 +++++ src/cmd/link/internal/ld/lib.go | 4 +- src/cmd/link/internal/ld/xcoff.go | 2 +- src/cmd/link/internal/sym/symkind.go | 3 +- src/cmd/link/internal/sym/symkind_string.go | 89 +++++++++++---------- src/cmd/link/internal/wasm/asm.go | 1 + 6 files changed, 70 insertions(+), 49 deletions(-) diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index f25b6c11da9..5b6dabb62b5 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -1937,6 +1937,26 @@ func (state *dodataState) allocateDataSections(ctxt *Link) { } ldr := ctxt.loader + // SMODULEDATA needs to be writable, but the GC doesn't need to + // look at it. We don't use allocateSingleSymSections because + // the name of the section is not the name of the symbol. + if len(state.data[sym.SMODULEDATA]) > 0 { + if len(state.data[sym.SMODULEDATA]) != 1 { + Errorf("internal error: more than one SMODULEDATA symbol") + } + s := state.data[sym.SMODULEDATA][0] + sect := addsection(ldr, ctxt.Arch, &Segdata, ".go.module", 06) + sect.Align = symalign(ldr, s) + state.datsize = Rnd(state.datsize, int64(sect.Align)) + sect.Vaddr = uint64(state.datsize) + ldr.SetSymSect(s, sect) + state.setSymType(s, sym.SDATA) + ldr.SetSymValue(s, int64(uint64(state.datsize)-sect.Vaddr)) + state.datsize += ldr.SymSize(s) + sect.Length = uint64(state.datsize) - sect.Vaddr + state.checkdatsize(sym.SMODULEDATA) + } + // writable .got (note that for PIE binaries .got goes in relro) if len(state.data[sym.SELFGOT]) > 0 { state.allocateNamedSectionAndAssignSyms(&Segdata, ".got", sym.SELFGOT, sym.SDATA, 06) diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 2c861129b52..22ee13dff87 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -923,9 +923,7 @@ func (ctxt *Link) linksetup() { mdsb = ctxt.loader.MakeSymbolUpdater(moduledata) ctxt.loader.SetAttrLocal(moduledata, true) } - // In all cases way we mark the moduledata as noptrdata to hide it from - // the GC. - mdsb.SetType(sym.SNOPTRDATA) + mdsb.SetType(sym.SMODULEDATA) ctxt.loader.SetAttrReachable(moduledata, true) ctxt.Moduledata = moduledata diff --git a/src/cmd/link/internal/ld/xcoff.go b/src/cmd/link/internal/ld/xcoff.go index 4500a7cb0c9..77ae1236c9c 100644 --- a/src/cmd/link/internal/ld/xcoff.go +++ b/src/cmd/link/internal/ld/xcoff.go @@ -1254,7 +1254,7 @@ func Xcoffadddynrel(target *Target, ldr *loader.Loader, syms *ArchSyms, s loader break } } - } else if t := ldr.SymType(s); t.IsDATA() || t.IsNOPTRDATA() || t == sym.SBUILDINFO || t == sym.SXCOFFTOC { + } else if t := ldr.SymType(s); t.IsDATA() || t.IsNOPTRDATA() || t == sym.SBUILDINFO || t == sym.SXCOFFTOC || t == sym.SMODULEDATA { switch ldr.SymSect(targ).Seg { default: ldr.Errorf(s, "unknown segment for .loader relocation with symbol %s", ldr.SymName(targ)) diff --git a/src/cmd/link/internal/sym/symkind.go b/src/cmd/link/internal/sym/symkind.go index 8709e7b48f8..746a12efd62 100644 --- a/src/cmd/link/internal/sym/symkind.go +++ b/src/cmd/link/internal/sym/symkind.go @@ -99,9 +99,10 @@ const ( SFIPSINFO // go:fipsinfo aka crypto/internal/fips140/check.Linkinfo (why is this writable)? SELFSECT // .got.plt, .plt, .dynamic where appropriate. SMACHO // Used only for .llvmasm? - SMACHOGOT // Mach-O GOT. SWINDOWS // Windows dynamic symbols. + SMODULEDATA // Linker generated moduledata struct. SELFGOT // Writable ELF GOT section. + SMACHOGOT // Mach-O GOT. SNOPTRDATA // Data with no heap pointers. SNOPTRDATAFIPSSTART // Start of FIPS non-pointer writable data. SNOPTRDATAFIPS // FIPS non-pointer writable data. diff --git a/src/cmd/link/internal/sym/symkind_string.go b/src/cmd/link/internal/sym/symkind_string.go index 019e7c746a6..80da278b332 100644 --- a/src/cmd/link/internal/sym/symkind_string.go +++ b/src/cmd/link/internal/sym/symkind_string.go @@ -45,54 +45,55 @@ func _() { _ = x[SFIPSINFO-34] _ = x[SELFSECT-35] _ = x[SMACHO-36] - _ = x[SMACHOGOT-37] - _ = x[SWINDOWS-38] + _ = x[SWINDOWS-37] + _ = x[SMODULEDATA-38] _ = x[SELFGOT-39] - _ = x[SNOPTRDATA-40] - _ = x[SNOPTRDATAFIPSSTART-41] - _ = x[SNOPTRDATAFIPS-42] - _ = x[SNOPTRDATAFIPSEND-43] - _ = x[SNOPTRDATAEND-44] - _ = x[SINITARR-45] - _ = x[SDATA-46] - _ = x[SDATAFIPSSTART-47] - _ = x[SDATAFIPS-48] - _ = x[SDATAFIPSEND-49] - _ = x[SDATAEND-50] - _ = x[SXCOFFTOC-51] - _ = x[SBSS-52] - _ = x[SNOPTRBSS-53] - _ = x[SLIBFUZZER_8BIT_COUNTER-54] - _ = x[SCOVERAGE_COUNTER-55] - _ = x[SCOVERAGE_AUXVAR-56] - _ = x[STLSBSS-57] - _ = x[SFirstUnallocated-58] - _ = x[SXREF-59] - _ = x[SMACHOSYMSTR-60] - _ = x[SMACHOSYMTAB-61] - _ = x[SMACHOINDIRECTPLT-62] - _ = x[SMACHOINDIRECTGOT-63] - _ = x[SDYNIMPORT-64] - _ = x[SHOSTOBJ-65] - _ = x[SUNDEFEXT-66] - _ = x[SDWARFSECT-67] - _ = x[SDWARFCUINFO-68] - _ = x[SDWARFCONST-69] - _ = x[SDWARFFCN-70] - _ = x[SDWARFABSFCN-71] - _ = x[SDWARFTYPE-72] - _ = x[SDWARFVAR-73] - _ = x[SDWARFRANGE-74] - _ = x[SDWARFLOC-75] - _ = x[SDWARFLINES-76] - _ = x[SDWARFADDR-77] - _ = x[SSEHUNWINDINFO-78] - _ = x[SSEHSECT-79] + _ = x[SMACHOGOT-40] + _ = x[SNOPTRDATA-41] + _ = x[SNOPTRDATAFIPSSTART-42] + _ = x[SNOPTRDATAFIPS-43] + _ = x[SNOPTRDATAFIPSEND-44] + _ = x[SNOPTRDATAEND-45] + _ = x[SINITARR-46] + _ = x[SDATA-47] + _ = x[SDATAFIPSSTART-48] + _ = x[SDATAFIPS-49] + _ = x[SDATAFIPSEND-50] + _ = x[SDATAEND-51] + _ = x[SXCOFFTOC-52] + _ = x[SBSS-53] + _ = x[SNOPTRBSS-54] + _ = x[SLIBFUZZER_8BIT_COUNTER-55] + _ = x[SCOVERAGE_COUNTER-56] + _ = x[SCOVERAGE_AUXVAR-57] + _ = x[STLSBSS-58] + _ = x[SFirstUnallocated-59] + _ = x[SXREF-60] + _ = x[SMACHOSYMSTR-61] + _ = x[SMACHOSYMTAB-62] + _ = x[SMACHOINDIRECTPLT-63] + _ = x[SMACHOINDIRECTGOT-64] + _ = x[SDYNIMPORT-65] + _ = x[SHOSTOBJ-66] + _ = x[SUNDEFEXT-67] + _ = x[SDWARFSECT-68] + _ = x[SDWARFCUINFO-69] + _ = x[SDWARFCONST-70] + _ = x[SDWARFFCN-71] + _ = x[SDWARFABSFCN-72] + _ = x[SDWARFTYPE-73] + _ = x[SDWARFVAR-74] + _ = x[SDWARFRANGE-75] + _ = x[SDWARFLOC-76] + _ = x[SDWARFLINES-77] + _ = x[SDWARFADDR-78] + _ = x[SSEHUNWINDINFO-79] + _ = x[SSEHSECT-80] } -const _SymKind_name = "SxxxSTEXTSTEXTFIPSSTARTSTEXTFIPSSTEXTFIPSENDSTEXTENDSELFRXSECTSMACHOPLTSTYPESSTRINGSGOSTRINGSGOFUNCSGCBITSSRODATASRODATAFIPSSTARTSRODATAFIPSSRODATAFIPSENDSRODATAENDSFUNCTABSPCLNTABSELFROSECTSTYPERELROSSTRINGRELROSGOSTRINGRELROSGOFUNCRELROSGCBITSRELROSRODATARELROSFUNCTABRELROSELFRELROSECTSMACHORELROSECTSTYPELINKSITABLINKSFirstWritableSBUILDINFOSFIPSINFOSELFSECTSMACHOSMACHOGOTSWINDOWSSELFGOTSNOPTRDATASNOPTRDATAFIPSSTARTSNOPTRDATAFIPSSNOPTRDATAFIPSENDSNOPTRDATAENDSINITARRSDATASDATAFIPSSTARTSDATAFIPSSDATAFIPSENDSDATAENDSXCOFFTOCSBSSSNOPTRBSSSLIBFUZZER_8BIT_COUNTERSCOVERAGE_COUNTERSCOVERAGE_AUXVARSTLSBSSSFirstUnallocatedSXREFSMACHOSYMSTRSMACHOSYMTABSMACHOINDIRECTPLTSMACHOINDIRECTGOTSDYNIMPORTSHOSTOBJSUNDEFEXTSDWARFSECTSDWARFCUINFOSDWARFCONSTSDWARFFCNSDWARFABSFCNSDWARFTYPESDWARFVARSDWARFRANGESDWARFLOCSDWARFLINESSDWARFADDRSSEHUNWINDINFOSSEHSECT" +const _SymKind_name = "SxxxSTEXTSTEXTFIPSSTARTSTEXTFIPSSTEXTFIPSENDSTEXTENDSELFRXSECTSMACHOPLTSTYPESSTRINGSGOSTRINGSGOFUNCSGCBITSSRODATASRODATAFIPSSTARTSRODATAFIPSSRODATAFIPSENDSRODATAENDSFUNCTABSPCLNTABSELFROSECTSTYPERELROSSTRINGRELROSGOSTRINGRELROSGOFUNCRELROSGCBITSRELROSRODATARELROSFUNCTABRELROSELFRELROSECTSMACHORELROSECTSTYPELINKSITABLINKSFirstWritableSBUILDINFOSFIPSINFOSELFSECTSMACHOSWINDOWSSMODULEDATASELFGOTSMACHOGOTSNOPTRDATASNOPTRDATAFIPSSTARTSNOPTRDATAFIPSSNOPTRDATAFIPSENDSNOPTRDATAENDSINITARRSDATASDATAFIPSSTARTSDATAFIPSSDATAFIPSENDSDATAENDSXCOFFTOCSBSSSNOPTRBSSSLIBFUZZER_8BIT_COUNTERSCOVERAGE_COUNTERSCOVERAGE_AUXVARSTLSBSSSFirstUnallocatedSXREFSMACHOSYMSTRSMACHOSYMTABSMACHOINDIRECTPLTSMACHOINDIRECTGOTSDYNIMPORTSHOSTOBJSUNDEFEXTSDWARFSECTSDWARFCUINFOSDWARFCONSTSDWARFFCNSDWARFABSFCNSDWARFTYPESDWARFVARSDWARFRANGESDWARFLOCSDWARFLINESSDWARFADDRSSEHUNWINDINFOSSEHSECT" -var _SymKind_index = [...]uint16{0, 4, 9, 23, 32, 44, 52, 62, 71, 76, 83, 92, 99, 106, 113, 129, 140, 154, 164, 172, 180, 190, 200, 212, 226, 238, 250, 262, 275, 288, 303, 312, 321, 335, 345, 354, 362, 368, 377, 385, 392, 402, 421, 435, 452, 465, 473, 478, 492, 501, 513, 521, 530, 534, 543, 566, 583, 599, 606, 623, 628, 640, 652, 669, 686, 696, 704, 713, 723, 735, 746, 755, 767, 777, 786, 797, 806, 817, 827, 841, 849} +var _SymKind_index = [...]uint16{0, 4, 9, 23, 32, 44, 52, 62, 71, 76, 83, 92, 99, 106, 113, 129, 140, 154, 164, 172, 180, 190, 200, 212, 226, 238, 250, 262, 275, 288, 303, 312, 321, 335, 345, 354, 362, 368, 376, 387, 394, 403, 413, 432, 446, 463, 476, 484, 489, 503, 512, 524, 532, 541, 545, 554, 577, 594, 610, 617, 634, 639, 651, 663, 680, 697, 707, 715, 724, 734, 746, 757, 766, 778, 788, 797, 808, 817, 828, 838, 852, 860} func (i SymKind) String() string { if i >= SymKind(len(_SymKind_index)-1) { diff --git a/src/cmd/link/internal/wasm/asm.go b/src/cmd/link/internal/wasm/asm.go index 65f79c80120..947f2da2b5d 100644 --- a/src/cmd/link/internal/wasm/asm.go +++ b/src/cmd/link/internal/wasm/asm.go @@ -127,6 +127,7 @@ func asmb(ctxt *ld.Link, ldr *loader.Loader) { ldr.SymSect(ldr.Lookup("runtime.rodata", 0)), ldr.SymSect(ldr.Lookup("runtime.typelink", 0)), ldr.SymSect(ldr.Lookup("runtime.itablink", 0)), + ldr.SymSect(ldr.Lookup("runtime.firstmoduledata", 0)), ldr.SymSect(ldr.Lookup("runtime.pclntab", 0)), ldr.SymSect(ldr.Lookup("runtime.noptrdata", 0)), ldr.SymSect(ldr.Lookup("runtime.data", 0)), From c03e25a263dfa240bca13ebc45d44b5c7bc1fbc1 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Mon, 17 Nov 2025 11:54:48 -0800 Subject: [PATCH 095/140] cmd/link: always run current linker in tests This ensures that the tests are testing the current linker sources, not the installed linker. Change-Id: I14a2ca9d413e1af57c7b5a00657c72023626a651 Reviewed-on: https://go-review.googlesource.com/c/go/+/721220 Reviewed-by: David Chase LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui --- src/cmd/link/cgo_test.go | 4 +- src/cmd/link/dwarf_test.go | 62 ++----------- src/cmd/link/elf_test.go | 41 ++++----- src/cmd/link/link_test.go | 163 ++++++++++++++++++++++++++--------- src/cmd/link/linkbig_test.go | 4 +- 5 files changed, 151 insertions(+), 123 deletions(-) diff --git a/src/cmd/link/cgo_test.go b/src/cmd/link/cgo_test.go index 52db70e1ad8..a684edd5f3b 100644 --- a/src/cmd/link/cgo_test.go +++ b/src/cmd/link/cgo_test.go @@ -115,10 +115,10 @@ func testCGOLTO(t *testing.T, cc, cgoCflags string, test int) { t.Fatalf("bad case %d", test) } - cmd := testenv.Command(t, testenv.GoToolPath(t), "build") + cmd := goCmd(t, "build") cmd.Dir = dir cgoCflags += " -flto" - cmd.Env = append(cmd.Environ(), "CGO_CFLAGS="+cgoCflags) + cmd.Env = append(cmd.Env, "CGO_CFLAGS="+cgoCflags) t.Logf("CGO_CFLAGS=%q %v", cgoCflags, cmd) out, err := cmd.CombinedOutput() diff --git a/src/cmd/link/dwarf_test.go b/src/cmd/link/dwarf_test.go index 56a076002a9..d9916ae0799 100644 --- a/src/cmd/link/dwarf_test.go +++ b/src/cmd/link/dwarf_test.go @@ -21,56 +21,6 @@ import ( "testing" ) -// TestMain allows this test binary to run as a -toolexec wrapper for -// the 'go' command. If LINK_TEST_TOOLEXEC is set, TestMain runs the -// binary as if it were cmd/link, and otherwise runs the requested -// tool as a subprocess. -// -// This allows the test to verify the behavior of the current contents of the -// cmd/link package even if the installed cmd/link binary is stale. -func TestMain(m *testing.M) { - // Are we running as a toolexec wrapper? If so then run either - // the correct tool or this executable itself (for the linker). - // Running as toolexec wrapper. - if os.Getenv("LINK_TEST_TOOLEXEC") != "" { - if strings.TrimSuffix(filepath.Base(os.Args[1]), ".exe") == "link" { - // Running as a -toolexec linker, and the tool is cmd/link. - // Substitute this test binary for the linker. - os.Args = os.Args[1:] - main() - os.Exit(0) - } - // Running some other tool. - cmd := exec.Command(os.Args[1], os.Args[2:]...) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - os.Exit(1) - } - os.Exit(0) - } - - // Are we being asked to run as the linker (without toolexec)? - // If so then kick off main. - if os.Getenv("LINK_TEST_EXEC_LINKER") != "" { - main() - os.Exit(0) - } - - if testExe, err := os.Executable(); err == nil { - // on wasm, some phones, we expect an error from os.Executable() - testLinker = testExe - } - - // Not running as a -toolexec wrapper or as a linker executable. - // Just run the tests. - os.Exit(m.Run()) -} - -// Path of the test executable being run. -var testLinker string - func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string) { testenv.MustHaveCGO(t) testenv.MustHaveGoBuild(t) @@ -106,14 +56,13 @@ func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string) exe := filepath.Join(tmpDir, prog+".exe") dir := "../../runtime/testdata/" + prog - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-toolexec", os.Args[0], "-o", exe) + cmd := goCmd(t, "build", "-o", exe) if buildmode != "" { cmd.Args = append(cmd.Args, "-buildmode", buildmode) } cmd.Args = append(cmd.Args, dir) - cmd.Env = append(os.Environ(), env...) + cmd.Env = append(cmd.Env, env...) cmd.Env = append(cmd.Env, "CGO_CFLAGS=") // ensure CGO_CFLAGS does not contain any flags. Issue #35459 - cmd.Env = append(cmd.Env, "LINK_TEST_TOOLEXEC=1") out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("go build -o %v %v: %v\n%s", exe, dir, err, out) @@ -282,9 +231,8 @@ func TestDWARFLocationList(t *testing.T) { exe := filepath.Join(tmpDir, "issue65405.exe") dir := "./testdata/dwarf/issue65405" - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-toolexec", os.Args[0], "-gcflags=all=-N -l", "-o", exe, dir) - cmd.Env = append(os.Environ(), "CGO_CFLAGS=") - cmd.Env = append(cmd.Env, "LINK_TEST_TOOLEXEC=1") + cmd := goCmd(t, "build", "-gcflags=all=-N -l", "-o", exe, dir) + cmd.Env = append(cmd.Env, "CGO_CFLAGS=") out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("go build -o %v %v: %v\n%s", exe, dir, err, out) @@ -402,7 +350,7 @@ func TestFlagW(t *testing.T) { t.Run(name, func(t *testing.T) { ldflags := "-ldflags=" + test.flag exe := filepath.Join(t.TempDir(), "a.exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", ldflags, "-o", exe, src) + cmd := goCmd(t, "build", ldflags, "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("build failed: %v\n%s", err, out) diff --git a/src/cmd/link/elf_test.go b/src/cmd/link/elf_test.go index 78459d611d7..f11cc4bcf8c 100644 --- a/src/cmd/link/elf_test.go +++ b/src/cmd/link/elf_test.go @@ -80,7 +80,8 @@ func TestSectionsWithSameName(t *testing.T) { dir := t.TempDir() gopath := filepath.Join(dir, "GOPATH") - env := append(os.Environ(), "GOPATH="+gopath) + gopathEnv := "GOPATH=" + gopath + env := append(os.Environ(), gopathEnv) if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module elf_test\n"), 0666); err != nil { t.Fatal(err) @@ -91,7 +92,6 @@ func TestSectionsWithSameName(t *testing.T) { t.Fatal(err) } - goTool := testenv.GoToolPath(t) cc, cflags := getCCAndCCFLAGS(t, env) asmObj := filepath.Join(dir, "x.o") @@ -119,10 +119,10 @@ func TestSectionsWithSameName(t *testing.T) { t.Fatal(err) } - cmd := testenv.Command(t, goTool, "build") + cmd := goCmd(t, "build") cmd.Dir = dir - cmd.Env = env - t.Logf("%s build", goTool) + cmd.Env = append(cmd.Env, gopathEnv) + t.Logf("%s build", testenv.GoToolPath(t)) if out, err := cmd.CombinedOutput(); err != nil { t.Logf("%s", out) t.Fatal(err) @@ -150,13 +150,13 @@ func TestMinusRSymsWithSameName(t *testing.T) { dir := t.TempDir() gopath := filepath.Join(dir, "GOPATH") - env := append(os.Environ(), "GOPATH="+gopath) + gopathEnv := "GOPATH=" + gopath + env := append(os.Environ(), gopathEnv) if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module elf_test\n"), 0666); err != nil { t.Fatal(err) } - goTool := testenv.GoToolPath(t) cc, cflags := getCCAndCCFLAGS(t, env) objs := []string{} @@ -198,10 +198,10 @@ func TestMinusRSymsWithSameName(t *testing.T) { t.Fatal(err) } - t.Logf("%s build", goTool) - cmd := testenv.Command(t, goTool, "build") + t.Logf("%s build", testenv.GoToolPath(t)) + cmd := goCmd(t, "build") cmd.Dir = dir - cmd.Env = env + cmd.Env = append(cmd.Env, gopathEnv) if out, err := cmd.CombinedOutput(); err != nil { t.Logf("%s", out) t.Fatal(err) @@ -243,7 +243,7 @@ func TestGNUBuildID(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { exe := filepath.Join(tmpdir, test.name) - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-buildid="+gobuildid+" "+test.ldflags, "-o", exe, goFile) + cmd := goCmd(t, "build", "-ldflags=-buildid="+gobuildid+" "+test.ldflags, "-o", exe, goFile) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("%v: %v:\n%s", cmd.Args, err, out) } @@ -277,11 +277,9 @@ func TestMergeNoteSections(t *testing.T) { t.Fatal(err) } outFile := filepath.Join(t.TempDir(), "notes.exe") - goTool := testenv.GoToolPath(t) // sha1sum of "gopher" id := "0xf4e8cd51ce8bae2996dc3b74639cdeaa1f7fee5f" - cmd := testenv.Command(t, goTool, "build", "-o", outFile, "-ldflags", - "-B "+id, goFile) + cmd := goCmd(t, "build", "-o", outFile, "-ldflags", "-B "+id, goFile) cmd.Dir = t.TempDir() if out, err := cmd.CombinedOutput(); err != nil { t.Logf("%s", out) @@ -383,7 +381,7 @@ func TestPIESize(t *testing.T) { binpie += linkmode build := func(bin, mode string) error { - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", bin, "-buildmode="+mode, "-ldflags=-linkmode="+linkmode) + cmd := goCmd(t, "build", "-o", bin, "-buildmode="+mode, "-ldflags=-linkmode="+linkmode) cmd.Args = append(cmd.Args, "pie.go") cmd.Dir = dir t.Logf("%v", cmd.Args) @@ -532,8 +530,7 @@ func TestIssue51939(t *testing.T) { t.Fatal(err) } outFile := filepath.Join(td, "issue51939.exe") - goTool := testenv.GoToolPath(t) - cmd := testenv.Command(t, goTool, "build", "-o", outFile, goFile) + cmd := goCmd(t, "build", "-o", outFile, goFile) if out, err := cmd.CombinedOutput(); err != nil { t.Logf("%s", out) t.Fatal(err) @@ -565,7 +562,7 @@ func TestFlagR(t *testing.T) { } exe := filepath.Join(tmpdir, "x.exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-R=0x100000", "-o", exe, src) + cmd := goCmd(t, "build", "-ldflags=-R=0x100000", "-o", exe, src) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("build failed: %v, output:\n%s", err, out) } @@ -610,7 +607,7 @@ func testFlagD(t *testing.T, dataAddr string, roundQuantum string, expectedAddr ldflags += " -R=" + roundQuantum } - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags="+ldflags, "-o", exe, src) + cmd := goCmd(t, "build", "-ldflags="+ldflags, "-o", exe, src) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("build failed: %v, output:\n%s", err, out) } @@ -669,7 +666,7 @@ func testFlagDError(t *testing.T, dataAddr string, roundQuantum string, expected ldflags += " -R=" + roundQuantum } - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags="+ldflags, "-o", exe, src) + cmd := goCmd(t, "build", "-ldflags="+ldflags, "-o", exe, src) out, err := cmd.CombinedOutput() if err == nil { t.Fatalf("expected build to fail with unaligned data address, but it succeeded") @@ -696,9 +693,7 @@ func TestELFHeadersSorted(t *testing.T) { } exe := filepath.Join(tmpdir, "x.exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-toolexec", os.Args[0], "-ldflags=-linkmode=internal", "-o", exe, src) - cmd = testenv.CleanCmdEnv(cmd) - cmd.Env = append(cmd.Env, "LINK_TEST_TOOLEXEC=1") + cmd := goCmd(t, "build", "-ldflags=-linkmode=internal", "-o", exe, src) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("build failed: %v, output:\n%s", err, out) } diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go index 6ab1246c814..72d8e1d97d1 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -25,6 +25,83 @@ import ( "cmd/internal/sys" ) +// TestMain allows this test binary to run as a -toolexec wrapper for +// the 'go' command. If LINK_TEST_TOOLEXEC is set, TestMain runs the +// binary as if it were cmd/link, and otherwise runs the requested +// tool as a subprocess. +// +// This allows the test to verify the behavior of the current contents of the +// cmd/link package even if the installed cmd/link binary is stale. +func TestMain(m *testing.M) { + // Are we running as a toolexec wrapper? If so then run either + // the correct tool or this executable itself (for the linker). + // Running as toolexec wrapper. + if os.Getenv("LINK_TEST_TOOLEXEC") != "" { + if strings.TrimSuffix(filepath.Base(os.Args[1]), ".exe") == "link" { + // Running as a -toolexec linker, and the tool is cmd/link. + // Substitute this test binary for the linker. + os.Args = os.Args[1:] + main() + os.Exit(0) + } + // Running some other tool. + cmd := exec.Command(os.Args[1], os.Args[2:]...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + os.Exit(1) + } + os.Exit(0) + } + + // Are we being asked to run as the linker (without toolexec)? + // If so then kick off main. + if os.Getenv("LINK_TEST_EXEC_LINKER") != "" { + main() + os.Exit(0) + } + + if testExe, err := os.Executable(); err == nil { + // on wasm, some phones, we expect an error from os.Executable() + testLinker = testExe + } + + // Not running as a -toolexec wrapper or as a linker executable. + // Just run the tests. + os.Exit(m.Run()) +} + +// testLinker is the path of the test executable being run. +// This is used by [TestScript]. +var testLinker string + +// goCmd returns a [*exec.Cmd] that runs the go tool using +// the current linker sources rather than the installed linker. +// The first element of the args parameter should be the go subcommand +// to run, such as "build" or "run". It must be a subcommand that +// takes the go command's build flags. +func goCmd(t *testing.T, args ...string) *exec.Cmd { + goArgs := []string{args[0], "-toolexec", testenv.Executable(t)} + args = append(goArgs, args[1:]...) + cmd := testenv.Command(t, testenv.GoToolPath(t), args...) + cmd = testenv.CleanCmdEnv(cmd) + cmd.Env = append(cmd.Env, "LINK_TEST_TOOLEXEC=1") + return cmd +} + +// linkCmd returns a [*exec.Cmd] that runs the linker built from +// the current sources. This is like "go tool link", but runs the +// current linker rather than the installed one. +func linkCmd(t *testing.T, args ...string) *exec.Cmd { + // Set up the arguments that TestMain looks for. + args = append([]string{"link"}, args...) + cmd := testenv.Command(t, testenv.Executable(t), args...) + cmd = testenv.CleanCmdEnv(cmd) + cmd.Env = append(cmd.Env, "LINK_TEST_TOOLEXEC=1") + return cmd +} + var AuthorPaidByTheColumnInch struct { fog int `text:"London. Michaelmas term lately over, and the Lord Chancellor sitting in Lincoln’s Inn Hall. Implacable November weather. As much mud in the streets as if the waters had but newly retired from the face of the earth, and it would not be wonderful to meet a Megalosaurus, forty feet long or so, waddling like an elephantine lizard up Holborn Hill. Smoke lowering down from chimney-pots, making a soft black drizzle, with flakes of soot in it as big as full-grown snowflakes—gone into mourning, one might imagine, for the death of the sun. Dogs, undistinguishable in mire. Horses, scarcely better; splashed to their very blinkers. Foot passengers, jostling one another’s umbrellas in a general infection of ill temper, and losing their foot-hold at street-corners, where tens of thousands of other foot passengers have been slipping and sliding since the day broke (if this day ever broke), adding new deposits to the crust upon crust of mud, sticking at those points tenaciously to the pavement, and accumulating at compound interest. Fog everywhere. Fog up the river, where it flows among green aits and meadows; fog down the river, where it rolls defiled among the tiers of shipping and the waterside pollutions of a great (and dirty) city. Fog on the Essex marshes, fog on the Kentish heights. Fog creeping into the cabooses of collier-brigs; fog lying out on the yards and hovering in the rigging of great ships; fog drooping on the gunwales of barges and small boats. Fog in the eyes and throats of ancient Greenwich pensioners, wheezing by the firesides of their wards; fog in the stem and bowl of the afternoon pipe of the wrathful skipper, down in his close cabin; fog cruelly pinching the toes and fingers of his shivering little ‘prentice boy on deck. Chance people on the bridges peeping over the parapets into a nether sky of fog, with fog all round them, as if they were up in a balloon and hanging in the misty clouds. Gas looming through the fog in divers places in the streets, much as the sun may, from the spongey fields, be seen to loom by husbandman and ploughboy. Most of the shops lighted two hours before their time—as the gas seems to know, for it has a haggard and unwilling look. The raw afternoon is rawest, and the dense fog is densest, and the muddy streets are muddiest near that leaden-headed old obstruction, appropriate ornament for the threshold of a leaden-headed old corporation, Temple Bar. And hard by Temple Bar, in Lincoln’s Inn Hall, at the very heart of the fog, sits the Lord High Chancellor in his High Court of Chancery."` @@ -74,7 +151,7 @@ func main() {} t.Fatalf("failed to compile main.go: %v, output: %s\n", err, out) } - cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "link", "-importcfg="+importcfgfile, "main.o") + cmd = linkCmd(t, "-importcfg="+importcfgfile, "main.o") cmd.Dir = tmpdir out, err = cmd.CombinedOutput() if err != nil { @@ -111,9 +188,6 @@ func TestIssue28429(t *testing.T) { cmd.Dir = tmpdir out, err := cmd.CombinedOutput() if err != nil { - if len(args) >= 2 && args[1] == "link" && runtime.GOOS == "android" && runtime.GOARCH == "arm64" { - testenv.SkipFlaky(t, 58806) - } t.Fatalf("'go %s' failed: %v, output: %s", strings.Join(args, " "), err, out) } @@ -133,7 +207,15 @@ func TestIssue28429(t *testing.T) { // Verify that the linker does not attempt // to compile the extra section. - runGo("tool", "link", "-importcfg="+importcfgfile, "main.a") + cmd := linkCmd(t, "-importcfg="+importcfgfile, "main.a") + cmd.Dir = tmpdir + out, err := cmd.CombinedOutput() + if err != nil { + if runtime.GOOS == "android" && runtime.GOARCH == "arm64" { + testenv.SkipFlaky(t, 58806) + } + t.Fatalf("linker failed: %v, output %s", err, out) + } } func TestUnresolved(t *testing.T) { @@ -171,9 +253,9 @@ TEXT ·x(SB),0,$0 MOVD ·zero(SB), AX RET `) - cmd := testenv.Command(t, testenv.GoToolPath(t), "build") + cmd := goCmd(t, "build") cmd.Dir = tmpdir - cmd.Env = append(os.Environ(), + cmd.Env = append(cmd.Env, "GOARCH=amd64", "GOOS=linux", "GOPATH="+filepath.Join(tmpdir, "_gopath")) out, err := cmd.CombinedOutput() if err == nil { @@ -260,7 +342,7 @@ void foo() { runGo("tool", "pack", "c", "x.a", "x1.o", "x2.o", "x3.o") // Now attempt to link using the internal linker. - cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "link", "-importcfg="+importcfgfile, "-linkmode=internal", "x.a") + cmd := linkCmd(t, "-importcfg="+importcfgfile, "-linkmode=internal", "x.a") cmd.Dir = tmpdir out, err := cmd.CombinedOutput() if err == nil { @@ -306,7 +388,7 @@ func TestBuildForTvOS(t *testing.T) { tmpDir := t.TempDir() ar := filepath.Join(tmpDir, "lib.a") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-buildmode=c-archive", "-o", ar, lib) + cmd := goCmd(t, "build", "-buildmode=c-archive", "-o", ar, lib) env := []string{ "CGO_ENABLED=1", "GOOS=ios", @@ -315,7 +397,7 @@ func TestBuildForTvOS(t *testing.T) { "CGO_CFLAGS=", // ensure CGO_CFLAGS does not contain any flags. Issue #35459 "CGO_LDFLAGS=" + strings.Join(CGO_LDFLAGS, " "), } - cmd.Env = append(os.Environ(), env...) + cmd.Env = append(cmd.Env, env...) t.Logf("%q %v", env, cmd) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("%v: %v:\n%s", cmd.Args, err, out) @@ -351,7 +433,7 @@ func TestXFlag(t *testing.T) { t.Fatal(err) } - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-X=main.X=meow", "-o", filepath.Join(tmpdir, "main"), src) + cmd := goCmd(t, "build", "-ldflags=-X=main.X=meow", "-o", filepath.Join(tmpdir, "main"), src) if out, err := cmd.CombinedOutput(); err != nil { t.Errorf("%v: %v:\n%s", cmd.Args, err, out) } @@ -376,8 +458,8 @@ func TestMachOBuildVersion(t *testing.T) { } exe := filepath.Join(tmpdir, "main") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode=internal", "-o", exe, src) - cmd.Env = append(os.Environ(), + cmd := goCmd(t, "build", "-ldflags=-linkmode=internal", "-o", exe, src) + cmd.Env = append(cmd.Env, "CGO_ENABLED=0", "GOOS=darwin", "GOARCH=amd64", @@ -469,7 +551,7 @@ func TestMachOUUID(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { exe := filepath.Join(tmpdir, test.name) - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags="+test.ldflags, "-o", exe, src) + cmd := goCmd(t, "build", "-ldflags="+test.ldflags, "-o", exe, src) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("%v: %v:\n%s", cmd.Args, err, out) } @@ -610,7 +692,7 @@ func TestStrictDup(t *testing.T) { t.Fatal(err) } - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-strictdups=1") + cmd := goCmd(t, "build", "-ldflags=-strictdups=1") cmd.Dir = tmpdir out, err := cmd.CombinedOutput() if err != nil { @@ -620,7 +702,7 @@ func TestStrictDup(t *testing.T) { t.Errorf("unexpected output:\n%s", out) } - cmd = testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-strictdups=2") + cmd = goCmd(t, "build", "-ldflags=-strictdups=2") cmd.Dir = tmpdir out, err = cmd.CombinedOutput() if err == nil { @@ -708,7 +790,7 @@ func TestFuncAlign(t *testing.T) { t.Fatal(err) } - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "falign") + cmd := goCmd(t, "build", "-o", "falign") cmd.Dir = tmpdir out, err := cmd.CombinedOutput() if err != nil { @@ -758,7 +840,7 @@ func TestFuncAlignOption(t *testing.T) { alignTest := func(align uint64) { exeName := "falign.exe" - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-funcalign="+strconv.FormatUint(align, 10), "-o", exeName, "falign.go") + cmd := goCmd(t, "build", "-ldflags=-funcalign="+strconv.FormatUint(align, 10), "-o", exeName, "falign.go") cmd.Dir = tmpdir out, err := cmd.CombinedOutput() if err != nil { @@ -853,7 +935,7 @@ func TestTrampoline(t *testing.T) { exe := filepath.Join(tmpdir, "hello.exe") for _, mode := range buildmodes { - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-buildmode="+mode, "-ldflags=-debugtramp=2", "-o", exe, src) + cmd := goCmd(t, "build", "-buildmode="+mode, "-ldflags=-debugtramp=2", "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("build (%s) failed: %v\n%s", mode, err, out) @@ -919,7 +1001,7 @@ func TestTrampolineCgo(t *testing.T) { exe := filepath.Join(tmpdir, "hello.exe") for _, mode := range buildmodes { - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-buildmode="+mode, "-ldflags=-debugtramp=2", "-o", exe, src) + cmd := goCmd(t, "build", "-buildmode="+mode, "-ldflags=-debugtramp=2", "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("build (%s) failed: %v\n%s", mode, err, out) @@ -938,7 +1020,7 @@ func TestTrampolineCgo(t *testing.T) { if !testenv.CanInternalLink(true) { continue } - cmd = testenv.Command(t, testenv.GoToolPath(t), "build", "-buildmode="+mode, "-ldflags=-debugtramp=2 -linkmode=internal", "-o", exe, src) + cmd = goCmd(t, "build", "-buildmode="+mode, "-ldflags=-debugtramp=2 -linkmode=internal", "-o", exe, src) out, err = cmd.CombinedOutput() if err != nil { t.Fatalf("build (%s) failed: %v\n%s", mode, err, out) @@ -992,7 +1074,7 @@ func TestIndexMismatch(t *testing.T) { if err != nil { t.Fatalf("compiling main.go failed: %v\n%s", err, out) } - cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "link", "-importcfg="+importcfgWithAFile, "-L", tmpdir, "-o", exe, mObj) + cmd = linkCmd(t, "-importcfg="+importcfgWithAFile, "-L", tmpdir, "-o", exe, mObj) t.Log(cmd) out, err = cmd.CombinedOutput() if err != nil { @@ -1010,7 +1092,7 @@ func TestIndexMismatch(t *testing.T) { if err != nil { t.Fatalf("compiling a.go failed: %v\n%s", err, out) } - cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "link", "-importcfg="+importcfgWithAFile, "-L", tmpdir, "-o", exe, mObj) + cmd = linkCmd(t, "-importcfg="+importcfgWithAFile, "-L", tmpdir, "-o", exe, mObj) t.Log(cmd) out, err = cmd.CombinedOutput() if err == nil { @@ -1036,7 +1118,7 @@ func TestPErsrcBinutils(t *testing.T) { pkgdir := filepath.Join("testdata", "pe-binutils") exe := filepath.Join(tmpdir, "a.exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe) + cmd := goCmd(t, "build", "-o", exe) cmd.Dir = pkgdir // cmd.Env = append(os.Environ(), "GOOS=windows", "GOARCH=amd64") // uncomment if debugging in a cross-compiling environment out, err := cmd.CombinedOutput() @@ -1068,7 +1150,7 @@ func TestPErsrcLLVM(t *testing.T) { pkgdir := filepath.Join("testdata", "pe-llvm") exe := filepath.Join(tmpdir, "a.exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe) + cmd := goCmd(t, "build", "-o", exe) cmd.Dir = pkgdir // cmd.Env = append(os.Environ(), "GOOS=windows", "GOARCH=amd64") // uncomment if debugging in a cross-compiling environment out, err := cmd.CombinedOutput() @@ -1093,7 +1175,7 @@ func TestContentAddressableSymbols(t *testing.T) { t.Parallel() src := filepath.Join("testdata", "testHashedSyms", "p.go") - cmd := testenv.Command(t, testenv.GoToolPath(t), "run", src) + cmd := goCmd(t, "run", src) out, err := cmd.CombinedOutput() if err != nil { t.Errorf("command %s failed: %v\n%s", cmd, err, out) @@ -1107,7 +1189,7 @@ func TestReadOnly(t *testing.T) { t.Parallel() src := filepath.Join("testdata", "testRO", "x.go") - cmd := testenv.Command(t, testenv.GoToolPath(t), "run", src) + cmd := goCmd(t, "run", src) out, err := cmd.CombinedOutput() if err == nil { t.Errorf("running test program did not fail. output:\n%s", out) @@ -1143,7 +1225,7 @@ func TestIssue38554(t *testing.T) { t.Fatalf("failed to write source file: %v", err) } exe := filepath.Join(tmpdir, "x.exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, src) + cmd := goCmd(t, "build", "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("build failed: %v\n%s", err, out) @@ -1193,7 +1275,7 @@ func TestIssue42396(t *testing.T) { t.Fatalf("failed to write source file: %v", err) } exe := filepath.Join(tmpdir, "main.exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-gcflags=-race", "-o", exe, src) + cmd := goCmd(t, "build", "-gcflags=-race", "-o", exe, src) out, err := cmd.CombinedOutput() if err == nil { t.Fatalf("build unexpectedly succeeded") @@ -1263,14 +1345,14 @@ func TestLargeReloc(t *testing.T) { if err != nil { t.Fatalf("failed to write source file: %v", err) } - cmd := testenv.Command(t, testenv.GoToolPath(t), "run", src) + cmd := goCmd(t, "run", src) out, err := cmd.CombinedOutput() if err != nil { t.Errorf("build failed: %v. output:\n%s", err, out) } if testenv.HasCGO() { // currently all targets that support cgo can external link - cmd = testenv.Command(t, testenv.GoToolPath(t), "run", "-ldflags=-linkmode=external", src) + cmd = goCmd(t, "run", "-ldflags=-linkmode=external", src) out, err = cmd.CombinedOutput() if err != nil { t.Fatalf("build failed: %v. output:\n%s", err, out) @@ -1314,7 +1396,7 @@ func TestUnlinkableObj(t *testing.T) { if err != nil { t.Fatalf("compile x.go failed: %v. output:\n%s", err, out) } - cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "link", "-importcfg="+importcfgfile, "-o", exe, xObj) + cmd = linkCmd(t, "-importcfg="+importcfgfile, "-o", exe, xObj) out, err = cmd.CombinedOutput() if err == nil { t.Fatalf("link did not fail") @@ -1335,7 +1417,7 @@ func TestUnlinkableObj(t *testing.T) { t.Fatalf("compile failed: %v. output:\n%s", err, out) } - cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "link", "-importcfg="+importcfgfile, "-o", exe, xObj) + cmd = linkCmd(t, "-importcfg="+importcfgfile, "-o", exe, xObj) out, err = cmd.CombinedOutput() if err != nil { t.Errorf("link failed: %v. output:\n%s", err, out) @@ -1380,7 +1462,7 @@ func main() {} ldflags := "-ldflags=-v -linkmode=external -tmpdir=" + linktmp var out0 []byte for i := 0; i < 5; i++ { - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", ldflags, "-o", exe, src) + cmd := goCmd(t, "build", ldflags, "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("build failed: %v, output:\n%s", err, out) @@ -1440,6 +1522,9 @@ func TestResponseFile(t *testing.T) { t.Fatal(err) } + // We don't use goCmd here, as -toolexec doesn't use response files. + // This test is more for the go command than the linker anyhow. + cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "output", "x.go") cmd.Dir = tmpdir @@ -1482,7 +1567,7 @@ func TestDynimportVar(t *testing.T) { src := filepath.Join("testdata", "dynimportvar", "main.go") for _, mode := range []string{"internal", "external"} { - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode="+mode, "-o", exe, src) + cmd := goCmd(t, "build", "-ldflags=-linkmode="+mode, "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("build (linkmode=%s) failed: %v\n%s", mode, err, out) @@ -1525,7 +1610,7 @@ func TestFlagS(t *testing.T) { syms := []string{"main.main", "main.X", "main.Y"} for _, mode := range modes { - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-s -linkmode="+mode, "-o", exe, src) + cmd := goCmd(t, "build", "-ldflags=-s -linkmode="+mode, "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("build (linkmode=%s) failed: %v\n%s", mode, err, out) @@ -1566,7 +1651,7 @@ func TestRandLayout(t *testing.T) { var syms [2]string for i, seed := range []string{"123", "456"} { exe := filepath.Join(tmpdir, "hello"+seed+".exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-randlayout="+seed, "-o", exe, src) + cmd := goCmd(t, "build", "-ldflags=-randlayout="+seed, "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("seed=%v: build failed: %v\n%s", seed, err, out) @@ -1627,7 +1712,7 @@ func TestCheckLinkname(t *testing.T) { t.Parallel() src := "./testdata/linkname/" + test.src exe := filepath.Join(tmpdir, test.src+".exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, src) + cmd := goCmd(t, "build", "-o", exe, src) out, err := cmd.CombinedOutput() if test.ok && err != nil { t.Errorf("build failed unexpectedly: %v:\n%s", err, out) @@ -1649,7 +1734,7 @@ func TestLinknameBSS(t *testing.T) { src := filepath.Join("testdata", "linkname", "sched.go") exe := filepath.Join(tmpdir, "sched.exe") - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, src) + cmd := goCmd(t, "build", "-o", exe, src) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("build failed unexpectedly: %v:\n%s", err, out) diff --git a/src/cmd/link/linkbig_test.go b/src/cmd/link/linkbig_test.go index ae9a38fa7ba..36c01c3b4af 100644 --- a/src/cmd/link/linkbig_test.go +++ b/src/cmd/link/linkbig_test.go @@ -82,7 +82,7 @@ func TestLargeText(t *testing.T) { } // Build and run with internal linking. - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "bigtext") + cmd := goCmd(t, "build", "-o", "bigtext") cmd.Dir = tmpdir out, err := cmd.CombinedOutput() if err != nil { @@ -96,7 +96,7 @@ func TestLargeText(t *testing.T) { } // Build and run with external linking - cmd = testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "bigtext", "-ldflags", "-linkmode=external") + cmd = goCmd(t, "build", "-o", "bigtext", "-ldflags", "-linkmode=external") cmd.Dir = tmpdir out, err = cmd.CombinedOutput() if err != nil { From 21b6ab57d572e1712112caadf79160200ddb7d1b Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Mon, 17 Nov 2025 18:29:42 -0800 Subject: [PATCH 096/140] cmd/link: test that funcdata values are in gopclntab section This is a test for CL 719440. For #76038 Change-Id: I8fc55118b3c7dea39a36e04ffb060fcb6150af54 Reviewed-on: https://go-review.googlesource.com/c/go/+/721460 Reviewed-by: Cherry Mui Reviewed-by: David Chase LUCI-TryBot-Result: Go LUCI Auto-Submit: Ian Lance Taylor --- src/cmd/link/link_test.go | 337 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 337 insertions(+) diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go index 72d8e1d97d1..96e5d5f1569 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -7,10 +7,14 @@ package main import ( "bufio" "bytes" + "debug/elf" "debug/macho" + "debug/pe" "errors" + "internal/abi" "internal/platform" "internal/testenv" + "internal/xcoff" "os" "os/exec" "path/filepath" @@ -19,6 +23,7 @@ import ( "strconv" "strings" "testing" + "unsafe" imacho "cmd/internal/macho" "cmd/internal/objfile" @@ -1773,3 +1778,335 @@ func TestLinknameBSS(t *testing.T) { t.Errorf("executable failed to run: %v\n%s", err, out) } } + +// setValueFromBytes copies from a []byte to a variable. +// This is used to get correctly aligned values in TestFuncdataPlacement. +func setValueFromBytes[T any](p *T, s []byte) { + copy(unsafe.Slice((*byte)(unsafe.Pointer(p)), unsafe.Sizeof(*p)), s) +} + +// Test that all funcdata values are stored in the .gopclntab section. +// This is pretty ugly as there is no API for accessing this data. +// This test will have to be updated when the data formats change. +func TestFuncdataPlacement(t *testing.T) { + testenv.MustHaveGoBuild(t) + t.Parallel() + + tmpdir := t.TempDir() + src := filepath.Join(tmpdir, "x.go") + if err := os.WriteFile(src, []byte(trivialSrc), 0o444); err != nil { + t.Fatal(err) + } + + exe := filepath.Join(tmpdir, "x.exe") + cmd := goCmd(t, "build", "-o", exe, src) + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("build failed; %v, output:\n%s", err, out) + } + + // We want to find the funcdata in the executable. + // We look at the section table to find the .gopclntab section, + // which starts with the pcHeader. + // That will give us the table of functions, + // which we can use to find the funcdata. + + ef, _ := elf.Open(exe) + mf, _ := macho.Open(exe) + pf, _ := pe.Open(exe) + xf, _ := xcoff.Open(exe) + // TODO: plan9 + if ef == nil && mf == nil && pf == nil && xf == nil { + t.Skip("unrecognized executable file format") + } + + const moddataSymName = "runtime.firstmoduledata" + const gofuncSymName = "go:func.*" + var ( + pclntab []byte + pclntabAddr uint64 + pclntabEnd uint64 + moddataAddr uint64 + moddataBytes []byte + gofuncAddr uint64 + imageBase uint64 + ) + switch { + case ef != nil: + defer ef.Close() + + syms, err := ef.Symbols() + if err != nil { + t.Fatal(err) + } + for _, sym := range syms { + switch sym.Name { + case moddataSymName: + moddataAddr = sym.Value + case gofuncSymName: + gofuncAddr = sym.Value + } + } + + for _, sec := range ef.Sections { + if sec.Name == ".gopclntab" { + data, err := sec.Data() + if err != nil { + t.Fatal(err) + } + pclntab = data + pclntabAddr = sec.Addr + pclntabEnd = sec.Addr + sec.Size + } + if sec.Flags&elf.SHF_ALLOC != 0 && moddataAddr >= sec.Addr && moddataAddr < sec.Addr+sec.Size { + data, err := sec.Data() + if err != nil { + t.Fatal(err) + } + moddataBytes = data[moddataAddr-sec.Addr:] + } + } + + case mf != nil: + defer mf.Close() + + for _, sym := range mf.Symtab.Syms { + switch sym.Name { + case moddataSymName: + moddataAddr = sym.Value + case gofuncSymName: + gofuncAddr = sym.Value + } + } + + for _, sec := range mf.Sections { + if sec.Name == "__gopclntab" { + data, err := sec.Data() + if err != nil { + t.Fatal(err) + } + pclntab = data + pclntabAddr = sec.Addr + pclntabEnd = sec.Addr + sec.Size + } + if moddataAddr >= sec.Addr && moddataAddr < sec.Addr+sec.Size { + data, err := sec.Data() + if err != nil { + t.Fatal(err) + } + moddataBytes = data[moddataAddr-sec.Addr:] + } + } + + case pf != nil: + defer pf.Close() + + switch ohdr := pf.OptionalHeader.(type) { + case *pe.OptionalHeader32: + imageBase = uint64(ohdr.ImageBase) + case *pe.OptionalHeader64: + imageBase = ohdr.ImageBase + } + + var moddataSym, gofuncSym, pclntabSym, epclntabSym *pe.Symbol + for _, sym := range pf.Symbols { + switch sym.Name { + case moddataSymName: + moddataSym = sym + case gofuncSymName: + gofuncSym = sym + case "runtime.pclntab": + pclntabSym = sym + case "runtime.epclntab": + epclntabSym = sym + } + } + + if moddataSym == nil { + t.Fatalf("could not find symbol %s", moddataSymName) + } + if gofuncSym == nil { + t.Fatalf("could not find symbol %s", gofuncSymName) + } + if pclntabSym == nil { + t.Fatal("could not find symbol runtime.pclntab") + } + if epclntabSym == nil { + t.Fatal("could not find symbol runtime.epclntab") + } + + sec := pf.Sections[moddataSym.SectionNumber-1] + data, err := sec.Data() + if err != nil { + t.Fatal(err) + } + moddataBytes = data[moddataSym.Value:] + moddataAddr = uint64(sec.VirtualAddress + moddataSym.Value) + + sec = pf.Sections[gofuncSym.SectionNumber-1] + gofuncAddr = uint64(sec.VirtualAddress + gofuncSym.Value) + + if pclntabSym.SectionNumber != epclntabSym.SectionNumber { + t.Fatalf("runtime.pclntab section %d != runtime.epclntab section %d", pclntabSym.SectionNumber, epclntabSym.SectionNumber) + } + sec = pf.Sections[pclntabSym.SectionNumber-1] + data, err = sec.Data() + if err != nil { + t.Fatal(err) + } + pclntab = data[pclntabSym.Value:epclntabSym.Value] + pclntabAddr = uint64(sec.VirtualAddress + pclntabSym.Value) + pclntabEnd = uint64(sec.VirtualAddress + epclntabSym.Value) + + case xf != nil: + defer xf.Close() + + for _, sym := range xf.Symbols { + switch sym.Name { + case moddataSymName: + moddataAddr = sym.Value + case gofuncSymName: + gofuncAddr = sym.Value + } + } + + for _, sec := range xf.Sections { + if sec.Name == ".go.pclntab" { + data, err := sec.Data() + if err != nil { + t.Fatal(err) + } + pclntab = data + pclntabAddr = sec.VirtualAddress + pclntabEnd = sec.VirtualAddress + sec.Size + } + if moddataAddr >= sec.VirtualAddress && moddataAddr < sec.VirtualAddress+sec.Size { + data, err := sec.Data() + if err != nil { + t.Fatal(err) + } + moddataBytes = data[moddataAddr-sec.VirtualAddress:] + } + } + + default: + panic("can't happen") + } + + if len(pclntab) == 0 { + t.Fatal("could not find pclntab section") + } + if moddataAddr == 0 { + t.Fatalf("could not find %s symbol", moddataSymName) + } + if gofuncAddr == 0 { + t.Fatalf("could not find %s symbol", gofuncSymName) + } + if gofuncAddr < pclntabAddr || gofuncAddr >= pclntabEnd { + t.Fatalf("%s out of range: value %#x not between %#x and %#x", gofuncSymName, gofuncAddr, pclntabAddr, pclntabEnd) + } + if len(moddataBytes) == 0 { + t.Fatal("could not find module data") + } + + // What a slice looks like in the object file. + type moddataSlice struct { + addr uintptr + len int + cap int + } + + // This needs to match the struct defined in runtime/symtab.go, + // and written out by (*Link).symtab. + // This is not the complete moddata struct, only what we need here. + type moddataType struct { + pcHeader uintptr + funcnametab moddataSlice + cutab moddataSlice + filetab moddataSlice + pctab moddataSlice + pclntable moddataSlice + ftab moddataSlice + findfunctab uintptr + minpc, maxpc uintptr + + text, etext uintptr + noptrdata, enoptrdata uintptr + data, edata uintptr + bss, ebss uintptr + noptrbss, enoptrbss uintptr + covctrs, ecovctrs uintptr + end, gcdata, gcbss uintptr + types, etypes uintptr + rodata uintptr + gofunc uintptr + } + + // The executable is on the same system as we are running, + // so the sizes and alignments should match. + // But moddataBytes itself may not be aligned as needed. + // Copy to a variable to ensure alignment. + var moddata moddataType + setValueFromBytes(&moddata, moddataBytes) + + ftabAddr := uint64(moddata.ftab.addr) - imageBase + if ftabAddr < pclntabAddr || ftabAddr >= pclntabEnd { + t.Fatalf("ftab address %#x not between %#x and %#x", ftabAddr, pclntabAddr, pclntabEnd) + } + + // From runtime/symtab.go and the linker function writePCToFunc. + type functab struct { + entryoff uint32 + funcoff uint32 + } + // The ftab slice in moddata has one extra entry used to record + // the final PC. + ftabLen := moddata.ftab.len - 1 + ftab := make([]functab, ftabLen) + copy(ftab, unsafe.Slice((*functab)(unsafe.Pointer(&pclntab[ftabAddr-pclntabAddr])), ftabLen)) + + ftabBase := uint64(moddata.pclntable.addr) - imageBase + + // From runtime/runtime2.go _func and the linker function writeFuncs. + type funcEntry struct { + entryOff uint32 + nameOff int32 + + args int32 + deferreturn uint32 + + pcsp uint32 + pcfile uint32 + pcln uint32 + npcdata uint32 + cuOffset uint32 + startLine int32 + funcID abi.FuncID + flag abi.FuncFlag + _ [1]byte + nfuncdata uint8 + } + + for i, ftabEntry := range ftab { + funcAddr := ftabBase + uint64(ftabEntry.funcoff) + if funcAddr < pclntabAddr || funcAddr >= pclntabEnd { + t.Errorf("ftab entry %d address %#x not between %#x and %#x", i, funcAddr, pclntabAddr, pclntabEnd) + continue + } + + var fe funcEntry + setValueFromBytes(&fe, pclntab[funcAddr-pclntabAddr:]) + + funcdataVals := funcAddr + uint64(unsafe.Sizeof(fe)) + uint64(fe.npcdata*4) + for j := range fe.nfuncdata { + var funcdataVal uint32 + setValueFromBytes(&funcdataVal, pclntab[funcdataVals+uint64(j)*4-pclntabAddr:]) + if funcdataVal == ^uint32(0) { + continue + } + funcdataAddr := gofuncAddr + uint64(funcdataVal) + if funcdataAddr < pclntabAddr || funcdataAddr >= pclntabEnd { + t.Errorf("ftab entry %d funcdata %d address %#x not between %#x and %#x", i, j, funcdataAddr, pclntabAddr, pclntabEnd) + } + } + } +} From 003f52407a8a32575084ffc4b15486aa8757ec74 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Mon, 17 Nov 2025 18:35:50 -0800 Subject: [PATCH 097/140] cmd/link: test that findfunctab is in gopclntab section This is a test for CL 719743. Change-Id: I2d7b9d00d2d4dd63a21ca00f09eb7c9378ec70f8 Reviewed-on: https://go-review.googlesource.com/c/go/+/721461 Reviewed-by: David Chase LUCI-TryBot-Result: Go LUCI Auto-Submit: Ian Lance Taylor Reviewed-by: Cherry Mui --- src/cmd/link/link_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go index 96e5d5f1569..77bbc3c1f8a 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -2109,4 +2109,8 @@ func TestFuncdataPlacement(t *testing.T) { } } } + + if uint64(moddata.findfunctab)-imageBase < pclntabAddr || uint64(moddata.findfunctab)-imageBase >= pclntabEnd { + t.Errorf("findfunctab address %#x not between %#x and %#x", moddata.findfunctab, pclntabAddr, pclntabEnd) + } } From f1bbc66a10a545811a21dcf9f9431e783aafcb42 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Mon, 17 Nov 2025 20:20:26 -0800 Subject: [PATCH 098/140] cmd/link: test that moduledata is in its own section This is a test for CL 720660. For #76038 Change-Id: I2f630b738ddb5a9c48e3c5d4374c1e995910541a Reviewed-on: https://go-review.googlesource.com/c/go/+/721480 Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI Auto-Submit: Ian Lance Taylor Reviewed-by: David Chase --- src/cmd/link/link_test.go | 97 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go index 77bbc3c1f8a..0c4cde0399f 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -2114,3 +2114,100 @@ func TestFuncdataPlacement(t *testing.T) { t.Errorf("findfunctab address %#x not between %#x and %#x", moddata.findfunctab, pclntabAddr, pclntabEnd) } } + +// Test that moduledata winds up in its own .go.module section. +func TestModuledataPlacement(t *testing.T) { + testenv.MustHaveGoBuild(t) + t.Parallel() + + tmpdir := t.TempDir() + src := filepath.Join(tmpdir, "x.go") + if err := os.WriteFile(src, []byte(trivialSrc), 0o444); err != nil { + t.Fatal(err) + } + + exe := filepath.Join(tmpdir, "x.exe") + cmd := goCmd(t, "build", "-o", exe, src) + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("build failed; %v, output:\n%s", err, out) + } + + ef, _ := elf.Open(exe) + mf, _ := macho.Open(exe) + pf, _ := pe.Open(exe) + xf, _ := xcoff.Open(exe) + // TODO: plan9 + if ef == nil && mf == nil && pf == nil && xf == nil { + t.Skip("unrecognized executable file format") + } + + const moddataSymName = "runtime.firstmoduledata" + switch { + case ef != nil: + defer ef.Close() + + syms, err := ef.Symbols() + if err != nil { + t.Fatal(err) + } + for _, sym := range syms { + if sym.Name == moddataSymName { + sec := ef.Sections[sym.Section] + if sec.Name != ".go.module" { + t.Errorf("moduledata in section %s, not .go.module", sec.Name) + } + if sym.Value != sec.Addr { + t.Errorf("moduledata address %#x != section start address %#x", sym.Value, sec.Addr) + } + break + } + } + + case mf != nil: + defer mf.Close() + + for _, sym := range mf.Symtab.Syms { + if sym.Name == moddataSymName { + if sym.Sect == 0 { + t.Error("moduledata not in a section") + } else { + sec := mf.Sections[sym.Sect-1] + if sec.Name != "__go_module" { + t.Errorf("moduledata in section %s, not __go.module", sec.Name) + } + if sym.Value != sec.Addr { + t.Errorf("moduledata address %#x != section start address %#x", sym.Value, sec.Addr) + } + } + break + } + } + + case pf != nil: + defer pf.Close() + + // On Windows all the Go specific sections seem to + // get stuffed into a few Windows sections, + // so there is nothing to test here. + + case xf != nil: + defer xf.Close() + + for _, sym := range xf.Symbols { + if sym.Name == moddataSymName { + if sym.SectionNumber == 0 { + t.Errorf("moduledata not in a section") + } else { + sec := xf.Sections[sym.SectionNumber-1] + if sec.Name != ".go.module" { + t.Errorf("moduledata in section %s, not .go.module", sec.Name) + } + if sym.Value != sec.VirtualAddress { + t.Errorf("moduledata address %#x != section start address %#x", sym.Value, sec.VirtualAddress) + } + } + break + } + } + } +} From cec4d4303f6475475d1a632cca506e8a82072d25 Mon Sep 17 00:00:00 2001 From: Dave Vasilevsky Date: Tue, 25 Nov 2025 03:55:45 +0000 Subject: [PATCH 099/140] os: allow direntries to have zero inodes on Linux Some Linux filesystems have been known to return valid enties with zero inodes. This new behavior also puts Go in agreement with recent glibc. Fixes #76428 Change-Id: Ieaf50739a294915a3ea2ef8c5a3bb2a91a186881 GitHub-Last-Rev: 8f83d009ef0320fd3fe7cf03e55d5d24df57f015 GitHub-Pull-Request: golang/go#76448 Reviewed-on: https://go-review.googlesource.com/c/go/+/724220 Reviewed-by: Cherry Mui Reviewed-by: Ian Lance Taylor Auto-Submit: Ian Lance Taylor Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: Go LUCI --- src/os/dir_unix.go | 3 ++- src/syscall/dirent.go | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/os/dir_unix.go b/src/os/dir_unix.go index 6a0135b70b0..87df3122d4e 100644 --- a/src/os/dir_unix.go +++ b/src/os/dir_unix.go @@ -112,7 +112,8 @@ func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEn // or might expose a remote file system which does not have the concept // of inodes. Therefore, we cannot make the assumption that it is safe // to skip entries with zero inodes. - if ino == 0 && runtime.GOOS != "wasip1" { + // Some Linux filesystems (old XFS, FUSE) can return valid files with zero inodes. + if ino == 0 && runtime.GOOS != "linux" && runtime.GOOS != "wasip1" { continue } const namoff = uint64(unsafe.Offsetof(syscall.Dirent{}.Name)) diff --git a/src/syscall/dirent.go b/src/syscall/dirent.go index c12b1193356..00946412703 100644 --- a/src/syscall/dirent.go +++ b/src/syscall/dirent.go @@ -73,8 +73,8 @@ func ParseDirent(buf []byte, max int, names []string) (consumed int, count int, break } // See src/os/dir_unix.go for the reason why this condition is - // excluded on wasip1. - if ino == 0 && runtime.GOOS != "wasip1" { // File absent in directory. + // excluded on wasip1 and linux. + if ino == 0 && runtime.GOOS != "linux" && runtime.GOOS != "wasip1" { // File absent in directory. continue } const namoff = uint64(unsafe.Offsetof(Dirent{}.Name)) From 481c6df7b9006e59febbb24689ab8bf686695e9d Mon Sep 17 00:00:00 2001 From: thepudds Date: Thu, 20 Nov 2025 14:43:50 -0500 Subject: [PATCH 100/140] io: reduce intermediate allocations in ReadAll and have a smaller final result MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, io.ReadAll allocates a significant amount of intermediate memory as it grows its result slice to the size of the input data. This CL aims to reduce the allocated memory. Geomean benchstat results comparing existing io.ReadAll to this CL for a variety of input sizes: │ old | new vs base │ sec/op 132.2µ 66.32µ -49.83% B/op 645.4Ki 324.6Ki -49.70% final-capacity 178.3k 151.3k -15.10% excess-ratio 1.216 1.033 -15.10% The corresponding full benchstat results are below. The input data sizes are a blend of random sizes, power-of-2 sizes, and power-of-10 sizes. This CL reduces intermediate bytes allocated in io.ReadAll by reading via a set of slices of exponentially growing size, and then copying into a final perfectly-sized slice at the end. The current memory allocations impact real uses. For example, in #50774 two real-world reports were ~60% more bytes allocated via io.ReadAll compared to an alternate approach, and also a separate report of ~5x more bytes allocated than the input data size of ~5MiB. Separately, bytes.Buffer.ReadFrom uses a 2x growth strategy, which usually can beat the pre-existing io.ReadAll on total bytes allocated but sometimes not (depending on alignment between exact input data size and growth). That said, bytes.Buffer.ReadFrom usually ends up with more excess memory used in a larger final result than the current io.ReadAll (often significantly more). If we compare bytes.Buffer.ReadFrom to this CL, we also see better geomean overall results reported with this CL: │ bytes.Buffer | io.ReadAll (new) │ sec/op 104.6µ 66.32µ -36.60% B/op 466.9Ki 324.6Ki -30.48% final-capacity 247.4k 151.3k -38.84% excess-ratio 1.688 1.033 -38.84% (Full corresponding benchstat results comparing this CL vs. bytes.Buffer are at https://go.dev/play/p/eqwk2BkaSwJ). One challenge with almost any change of growth strategy for something widely used is there can be a subset of users that benefited more from the old growth approach (e.g., based on their data size aligning particularly well with the old growth), even if the majority of users on average benefit from the new growth approach. To help mitigate that, this CL somewhat follows the old read pattern in its early stages. Here are the full benchstat results comparing the existing io.ReadAll vs. this CL. The standard metrics are included, plus the final result capacity and an excess capacity ratio, which is the final capacity of the result divided by the input data size (so 1.0 is no excess memory present in the result, though due to size class rounding the ratio is usually above 1.0 unless the input data size exactly matches a size class). We consider smaller reported excess capacity to be better for most uses given it means the final allocation puts less pressure on the GC (both in cases when it will almost immediately be garbage in user code, or if for example the final result is held for multiple GC cycles). The input data sizes used in the benchmarks: - Six powers of 10. - Six powers of 2. - Ten random sizes between 1KiB and 100MiB (chosen uniformly on a log scale). - size=300 (so that we have something below 512, which is the initial read size). goos: linux goarch: amd64 pkg: io cpu: AMD EPYC 7B13 │ old │ io.ReadAll (new) │ │ sec/op │ sec/op vs base │ ReadAll/size=300-16 113.0n ± 0% 115.4n ± 2% +2.08% (p=0.005 n=20) ReadAll/size=512-16 295.0n ± 2% 288.7n ± 1% -2.14% (p=0.006 n=20) ReadAll/size=1000-16 549.2n ± 1% 492.8n ± 1% -10.28% (p=0.000 n=20) ReadAll/size=4096-16 3.193µ ± 1% 2.277µ ± 1% -28.70% (p=0.000 n=20) ReadAll/size=6648-16 4.318µ ± 1% 3.100µ ± 1% -28.21% (p=0.000 n=20) ReadAll/size=10000-16 7.771µ ± 1% 4.629µ ± 1% -40.43% (p=0.000 n=20) ReadAll/size=12179-16 7.724µ ± 1% 5.066µ ± 1% -34.42% (p=0.000 n=20) ReadAll/size=16384-16 13.664µ ± 1% 7.309µ ± 1% -46.51% (p=0.000 n=20) ReadAll/size=32768-16 24.07µ ± 2% 14.52µ ± 2% -39.67% (p=0.000 n=20) ReadAll/size=65536-16 43.14µ ± 2% 24.00µ ± 2% -44.37% (p=0.000 n=20) ReadAll/size=80000-16 57.12µ ± 2% 31.28µ ± 2% -45.24% (p=0.000 n=20) ReadAll/size=100000-16 75.08µ ± 2% 38.18µ ± 3% -49.15% (p=0.000 n=20) ReadAll/size=118014-16 76.06µ ± 1% 50.03µ ± 3% -34.22% (p=0.000 n=20) ReadAll/size=131072-16 103.99µ ± 1% 52.31µ ± 2% -49.70% (p=0.000 n=20) ReadAll/size=397601-16 518.1µ ± 6% 204.2µ ± 2% -60.58% (p=0.000 n=20) ReadAll/size=626039-16 934.9µ ± 3% 398.7µ ± 7% -57.35% (p=0.000 n=20) ReadAll/size=1000000-16 1800.3µ ± 8% 651.4µ ± 6% -63.82% (p=0.000 n=20) ReadAll/size=1141838-16 2236.3µ ± 5% 710.2µ ± 5% -68.24% (p=0.000 n=20) ReadAll/size=2414329-16 4.517m ± 3% 1.471m ± 3% -67.43% (p=0.000 n=20) ReadAll/size=5136407-16 8.547m ± 3% 2.060m ± 1% -75.90% (p=0.000 n=20) ReadAll/size=10000000-16 13.303m ± 4% 3.767m ± 4% -71.68% (p=0.000 n=20) ReadAll/size=18285584-16 23.414m ± 2% 6.790m ± 5% -71.00% (p=0.000 n=20) ReadAll/size=67379426-16 55.93m ± 4% 24.50m ± 5% -56.20% (p=0.000 n=20) ReadAll/size=100000000-16 84.61m ± 5% 33.84m ± 5% -60.00% (p=0.000 n=20) geomean 132.2µ 66.32µ -49.83% │ old │ io.ReadAll (new) │ │ B/op │ B/op vs base │ ReadAll/size=300-16 512.0 ± 0% 512.0 ± 0% ~ (p=1.000 n=20) ¹ ReadAll/size=512-16 1.375Ki ± 0% 1.250Ki ± 0% -9.09% (p=0.000 n=20) ReadAll/size=1000-16 2.750Ki ± 0% 2.125Ki ± 0% -22.73% (p=0.000 n=20) ReadAll/size=4096-16 17.00Ki ± 0% 10.12Ki ± 0% -40.44% (p=0.000 n=20) ReadAll/size=6648-16 23.75Ki ± 0% 15.75Ki ± 0% -33.68% (p=0.000 n=20) ReadAll/size=10000-16 45.00Ki ± 0% 23.88Ki ± 0% -46.94% (p=0.000 n=20) ReadAll/size=12179-16 45.00Ki ± 0% 25.88Ki ± 0% -42.50% (p=0.000 n=20) ReadAll/size=16384-16 82.25Ki ± 0% 36.88Ki ± 0% -55.17% (p=0.000 n=20) ReadAll/size=32768-16 150.25Ki ± 0% 78.88Ki ± 0% -47.50% (p=0.000 n=20) ReadAll/size=65536-16 278.3Ki ± 0% 134.9Ki ± 0% -51.53% (p=0.000 n=20) ReadAll/size=80000-16 374.3Ki ± 0% 190.9Ki ± 0% -49.00% (p=0.000 n=20) ReadAll/size=100000-16 502.3Ki ± 0% 214.9Ki ± 0% -57.22% (p=0.000 n=20) ReadAll/size=118014-16 502.3Ki ± 0% 286.9Ki ± 0% -42.88% (p=0.000 n=20) ReadAll/size=131072-16 670.3Ki ± 0% 294.9Ki ± 0% -56.01% (p=0.000 n=20) ReadAll/size=397601-16 1934.3Ki ± 0% 919.8Ki ± 0% -52.45% (p=0.000 n=20) ReadAll/size=626039-16 3.092Mi ± 0% 1.359Mi ± 0% -56.04% (p=0.000 n=20) ReadAll/size=1000000-16 4.998Mi ± 0% 2.086Mi ± 0% -58.27% (p=0.000 n=20) ReadAll/size=1141838-16 6.334Mi ± 0% 2.219Mi ± 0% -64.98% (p=0.000 n=20) ReadAll/size=2414329-16 12.725Mi ± 0% 4.789Mi ± 0% -62.37% (p=0.000 n=20) ReadAll/size=5136407-16 25.28Mi ± 0% 10.44Mi ± 0% -58.71% (p=0.000 n=20) ReadAll/size=10000000-16 49.84Mi ± 0% 21.92Mi ± 0% -56.02% (p=0.000 n=20) ReadAll/size=18285584-16 97.88Mi ± 0% 35.99Mi ± 0% -63.23% (p=0.000 n=20) ReadAll/size=67379426-16 375.2Mi ± 0% 158.0Mi ± 0% -57.91% (p=0.000 n=20) ReadAll/size=100000000-16 586.7Mi ± 0% 235.9Mi ± 0% -59.80% (p=0.000 n=20) geomean 645.4Ki 324.6Ki -49.70% │ old │ io.ReadAll (new) │ │ final-cap │ final-cap vs base │ ReadAll/size=300-16 512.0 ± 0% 512.0 ± 0% ~ (p=1.000 n=20) ¹ ReadAll/size=512-16 896.0 ± 0% 512.0 ± 0% -42.86% (p=0.000 n=20) ReadAll/size=1000-16 1.408k ± 0% 1.024k ± 0% -27.27% (p=0.000 n=20) ReadAll/size=4096-16 5.376k ± 0% 4.096k ± 0% -23.81% (p=0.000 n=20) ReadAll/size=6648-16 6.912k ± 0% 6.784k ± 0% -1.85% (p=0.000 n=20) ReadAll/size=10000-16 12.29k ± 0% 10.24k ± 0% -16.67% (p=0.000 n=20) ReadAll/size=12179-16 12.29k ± 0% 12.29k ± 0% ~ (p=1.000 n=20) ¹ ReadAll/size=16384-16 21.76k ± 0% 16.38k ± 0% -24.71% (p=0.000 n=20) ReadAll/size=32768-16 40.96k ± 0% 32.77k ± 0% -20.00% (p=0.000 n=20) ReadAll/size=65536-16 73.73k ± 0% 65.54k ± 0% -11.11% (p=0.000 n=20) ReadAll/size=80000-16 98.30k ± 0% 81.92k ± 0% -16.67% (p=0.000 n=20) ReadAll/size=100000-16 131.1k ± 0% 106.5k ± 0% -18.75% (p=0.000 n=20) ReadAll/size=118014-16 131.1k ± 0% 122.9k ± 0% -6.25% (p=0.000 n=20) ReadAll/size=131072-16 172.0k ± 0% 131.1k ± 0% -23.81% (p=0.000 n=20) ReadAll/size=397601-16 442.4k ± 0% 401.4k ± 0% -9.26% (p=0.000 n=20) ReadAll/size=626039-16 704.5k ± 0% 630.8k ± 0% -10.47% (p=0.000 n=20) ReadAll/size=1000000-16 1.114M ± 0% 1.008M ± 0% -9.56% (p=0.000 n=20) ReadAll/size=1141838-16 1.401M ± 0% 1.147M ± 0% -18.13% (p=0.000 n=20) ReadAll/size=2414329-16 2.753M ± 0% 2.417M ± 0% -12.20% (p=0.000 n=20) ReadAll/size=5136407-16 5.399M ± 0% 5.145M ± 0% -4.70% (p=0.000 n=20) ReadAll/size=10000000-16 10.56M ± 0% 10.00M ± 0% -5.28% (p=0.000 n=20) ReadAll/size=18285584-16 20.65M ± 0% 18.29M ± 0% -11.42% (p=0.000 n=20) ReadAll/size=67379426-16 78.84M ± 0% 67.39M ± 0% -14.53% (p=0.000 n=20) ReadAll/size=100000000-16 123.2M ± 0% 100.0M ± 0% -18.82% (p=0.000 n=20) geomean 178.3k 151.3k -15.10% │ old │ io.ReadAll (new) │ │ excess-ratio │ excess-ratio vs base │ ReadAll/size=300-16 1.707 ± 0% 1.707 ± 0% ~ (p=1.000 n=20) ¹ ReadAll/size=512-16 1.750 ± 0% 1.000 ± 0% -42.86% (p=0.000 n=20) ReadAll/size=1000-16 1.408 ± 0% 1.024 ± 0% -27.27% (p=0.000 n=20) ReadAll/size=4096-16 1.312 ± 0% 1.000 ± 0% -23.78% (p=0.000 n=20) ReadAll/size=6648-16 1.040 ± 0% 1.020 ± 0% -1.92% (p=0.000 n=20) ReadAll/size=10000-16 1.229 ± 0% 1.024 ± 0% -16.68% (p=0.000 n=20) ReadAll/size=12179-16 1.009 ± 0% 1.009 ± 0% ~ (p=1.000 n=20) ¹ ReadAll/size=16384-16 1.328 ± 0% 1.000 ± 0% -24.70% (p=0.000 n=20) ReadAll/size=32768-16 1.250 ± 0% 1.000 ± 0% -20.00% (p=0.000 n=20) ReadAll/size=65536-16 1.125 ± 0% 1.000 ± 0% -11.11% (p=0.000 n=20) ReadAll/size=80000-16 1.229 ± 0% 1.024 ± 0% -16.68% (p=0.000 n=20) ReadAll/size=100000-16 1.311 ± 0% 1.065 ± 0% -18.76% (p=0.000 n=20) ReadAll/size=118014-16 1.111 ± 0% 1.041 ± 0% -6.30% (p=0.000 n=20) ReadAll/size=131072-16 1.312 ± 0% 1.000 ± 0% -23.78% (p=0.000 n=20) ReadAll/size=397601-16 1.113 ± 0% 1.010 ± 0% -9.25% (p=0.000 n=20) ReadAll/size=626039-16 1.125 ± 0% 1.008 ± 0% -10.40% (p=0.000 n=20) ReadAll/size=1000000-16 1.114 ± 0% 1.008 ± 0% -9.52% (p=0.000 n=20) ReadAll/size=1141838-16 1.227 ± 0% 1.004 ± 0% -18.17% (p=0.000 n=20) ReadAll/size=2414329-16 1.140 ± 0% 1.001 ± 0% -12.19% (p=0.000 n=20) ReadAll/size=5136407-16 1.051 ± 0% 1.002 ± 0% -4.66% (p=0.000 n=20) ReadAll/size=10000000-16 1.056 ± 0% 1.000 ± 0% -5.30% (p=0.000 n=20) ReadAll/size=18285584-16 1.129 ± 0% 1.000 ± 0% -11.43% (p=0.000 n=20) ReadAll/size=67379426-16 1.170 ± 0% 1.000 ± 0% -14.53% (p=0.000 n=20) ReadAll/size=100000000-16 1.232 ± 0% 1.000 ± 0% -18.83% (p=0.000 n=20) geomean 1.216 1.033 -15.10% │ io.ReadAll │ io.ReadAll (new) │ │ allocs/op │ allocs/op vs base │ ReadAll/size=300-16 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=20) ¹ ReadAll/size=512-16 2.000 ± 0% 3.000 ± 0% +50.00% (p=0.000 n=20) ReadAll/size=1000-16 3.000 ± 0% 4.000 ± 0% +33.33% (p=0.000 n=20) ReadAll/size=4096-16 7.000 ± 0% 9.000 ± 0% +28.57% (p=0.000 n=20) ReadAll/size=6648-16 8.000 ± 0% 10.000 ± 0% +25.00% (p=0.000 n=20) ReadAll/size=10000-16 10.00 ± 0% 11.00 ± 0% +10.00% (p=0.000 n=20) ReadAll/size=12179-16 10.00 ± 0% 11.00 ± 0% +10.00% (p=0.000 n=20) ReadAll/size=16384-16 12.00 ± 0% 13.00 ± 0% +8.33% (p=0.000 n=20) ReadAll/size=32768-16 14.00 ± 0% 15.00 ± 0% +7.14% (p=0.000 n=20) ReadAll/size=65536-16 16.00 ± 0% 16.00 ± 0% ~ (p=1.000 n=20) ¹ ReadAll/size=80000-16 17.00 ± 0% 17.00 ± 0% ~ (p=1.000 n=20) ¹ ReadAll/size=100000-16 18.00 ± 0% 17.00 ± 0% -5.56% (p=0.000 n=20) ReadAll/size=118014-16 18.00 ± 0% 18.00 ± 0% ~ (p=1.000 n=20) ¹ ReadAll/size=131072-16 19.00 ± 0% 18.00 ± 0% -5.26% (p=0.000 n=20) ReadAll/size=397601-16 23.00 ± 0% 22.00 ± 0% -4.35% (p=0.000 n=20) ReadAll/size=626039-16 25.00 ± 0% 23.00 ± 0% -8.00% (p=0.000 n=20) ReadAll/size=1000000-16 27.00 ± 0% 24.00 ± 0% -11.11% (p=0.000 n=20) ReadAll/size=1141838-16 28.00 ± 0% 24.00 ± 0% -14.29% (p=0.000 n=20) ReadAll/size=2414329-16 31.00 ± 0% 26.00 ± 0% -16.13% (p=0.000 n=20) ReadAll/size=5136407-16 34.00 ± 0% 28.00 ± 0% -17.65% (p=0.000 n=20) ReadAll/size=10000000-16 37.00 ± 0% 30.00 ± 0% -18.92% (p=0.000 n=20) ReadAll/size=18285584-16 40.00 ± 0% 31.00 ± 0% -22.50% (p=0.000 n=20) ReadAll/size=67379426-16 46.00 ± 0% 35.00 ± 0% -23.91% (p=0.000 n=20) ReadAll/size=100000000-16 48.00 ± 0% 36.00 ± 0% -25.00% (p=0.000 n=20) geomean 14.89 14.65 -1.65% Finally, the read size in this CL currently grows exponentially at a 1.5x growth rate. The old approach had its read size grow at a ~1.25x growth rate once the reads are larger. We could consider for example using a 1.25x read size growth rate here as well. There are perhaps some ~mild trade-offs. One benefit might be something like a ~5% smaller peak live heap contribution (at the end, when copying into the final result) if we used a 1.25x read growth instead of 1.5x read growth. That said, for some systems, larger read sizes can trigger higher throughput behavior further down the stack or elsewhere in a system, such as via better read-ahead behavior, larger transfer sizes, etc. I've observed this effect in various real-world systems, including distributed systems as well as for example with spinning platters (which are still widely used, including backing various "Internet scale" systems). When the effect exists, it is usually substantial. Therefore, my guess is it is better to get to larger read sizes faster, which is one reason the CL is using 1.5x read size growth rate instead of 1.25x. Also, for something like peak live heap contribution, we are already getting substantial wins in this CL for total heap bytes allocated, so maybe that is OK. (I have the actual benchmark in a separate commit, which I can send later, or I can update this CL if preferred). Fixes #50774 Updates #74299 Change-Id: I65eabf1d83a00fbdbe42e4c697116955f8251740 Reviewed-on: https://go-review.googlesource.com/c/go/+/722500 LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Pratt Reviewed-by: Keith Randall Reviewed-by: Keith Randall Reviewed-by: Dmitri Shuralyov --- src/io/io.go | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/io/io.go b/src/io/io.go index 00edcde763a..5b039e98578 100644 --- a/src/io/io.go +++ b/src/io/io.go @@ -707,7 +707,17 @@ func (c nopCloserWriterTo) WriteTo(w Writer) (n int64, err error) { // defined to read from src until EOF, it does not treat an EOF from Read // as an error to be reported. func ReadAll(r Reader) ([]byte, error) { + // Build slices of exponentially growing size, + // then copy into a perfectly-sized slice at the end. b := make([]byte, 0, 512) + // Starting with next equal to 256 (instead of say 512 or 1024) + // allows less memory usage for small inputs that finish in the + // early growth stages, but we grow the read sizes quickly such that + // it does not materially impact medium or large inputs. + next := 256 + chunks := make([][]byte, 0, 4) + // Invariant: finalSize = sum(len(c) for c in chunks) + var finalSize int for { n, err := r.Read(b[len(b):cap(b)]) b = b[:len(b)+n] @@ -715,12 +725,26 @@ func ReadAll(r Reader) ([]byte, error) { if err == EOF { err = nil } - return b, err + if len(chunks) == 0 { + return b, err + } + + // Build our final right-sized slice. + finalSize += len(b) + final := append([]byte(nil), make([]byte, finalSize)...)[:0] + for _, chunk := range chunks { + final = append(final, chunk...) + } + final = append(final, b...) + return final, err } - if len(b) == cap(b) { - // Add more capacity (let append pick how much). - b = append(b, 0)[:len(b)] + if cap(b)-len(b) < cap(b)/16 { + // Move to the next intermediate slice. + chunks = append(chunks, b) + finalSize += len(b) + b = append([]byte(nil), make([]byte, next)...)[:0] + next += next / 2 } } } From 6be5de4bc4e30ac0e2843c781393235d78e384a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E7=8E=AE=E6=96=87?= Date: Sat, 22 Nov 2025 17:15:04 +0800 Subject: [PATCH 101/140] internal/runtime/cgroup: simplify escapePath in test Don't work on rune, kernel does not use utf-8 here. Can be verified like this: # mkdir "$(echo -e "\xff\x20")" # mount -t tmpfs tmpfs "$(echo -e "\xff\x20")" # tail -n 1 /proc/self/mountinfo | xxd 00000000: 3133 3334 2031 3030 2030 3a31 3632 202f 1334 100 0:162 / 00000010: 202f 726f 6f74 2fff 5c30 3430 2072 772c /root/.\040 rw, 00000020: 7265 6c61 7469 6d65 2073 6861 7265 643a relatime shared: 00000030: 3433 3520 2d20 746d 7066 7320 746d 7066 435 - tmpfs tmpf 00000040: 7320 7277 0a s rw. Change-Id: I7468b56eb26f14bc809f8f7580535e6562795c62 Reviewed-on: https://go-review.googlesource.com/c/go/+/723300 Reviewed-by: Michael Pratt Auto-Submit: Michael Pratt LUCI-TryBot-Result: Go LUCI Reviewed-by: Dmitri Shuralyov --- src/internal/runtime/cgroup/cgroup_test.go | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/internal/runtime/cgroup/cgroup_test.go b/src/internal/runtime/cgroup/cgroup_test.go index d47fe420674..a4ffdf3ba17 100644 --- a/src/internal/runtime/cgroup/cgroup_test.go +++ b/src/internal/runtime/cgroup/cgroup_test.go @@ -8,7 +8,6 @@ import ( "fmt" "internal/runtime/cgroup" "io" - "strconv" "strings" "testing" ) @@ -380,21 +379,11 @@ func TestParseCPUMount(t *testing.T) { // That is, '\', ' ', '\t', and '\n' are converted to octal escape sequences, // like '\040' for space. func escapePath(s string) string { - out := make([]rune, 0, len(s)) - for _, c := range s { + out := make([]byte, 0, len(s)) + for _, c := range []byte(s) { switch c { case '\\', ' ', '\t', '\n': - out = append(out, '\\') - cs := strconv.FormatInt(int64(c), 8) - if len(cs) <= 2 { - out = append(out, '0') - } - if len(cs) <= 1 { - out = append(out, '0') - } - for _, csc := range cs { - out = append(out, csc) - } + out = fmt.Appendf(out, "\\%03o", c) default: out = append(out, c) } @@ -444,6 +433,11 @@ b/c`, unescaped: `/a/\`, escaped: `/a/\134`, }, + { + name: "non-utf8", + unescaped: "/a/b\xff\x20/c", + escaped: "/a/b\xff\\040/c", + }, } t.Run("escapePath", func(t *testing.T) { From c2af9f14b429741cfd4fed11a67a52427dec3931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E7=8E=AE=E6=96=87?= Date: Sat, 22 Nov 2025 01:44:14 +0800 Subject: [PATCH 102/140] internal/runtime/cgroup: fix path on non-root mount point We should trim the mount root (4th field in /proc/self/mountinfo) from cgroup path read from /proc/self/cgroup before appending it to the mount point. Non-root mount points are very common in containers with cgroup v1. parseCPURelativePath is renamed to parseCPUCgroup, as it is unclear what it is relative to. cgroups(7) says "This pathname is relative to the mount point of the hierarchy." It should mean the root of the hierarchy, and we cannot concat it to arbirary cgroup mount point. So just use the word cgroup, since it parses /proc/self/cgroup. It now returns errMalformedFile if the cgroup pathname does not start with "/", and errPathTooLong if the pathname can't fit into the buffer. We already rely on this when composing the path, just make this explicit to avoid incorrect paths. We now parse cgroup first then parse the mount point accordingly. We consider the previously read cgroup pathname and version to ensure we got the desired mount point. The out buffer is reused to pass in the cgroup, to avoid extra memory allocation. This should also resolve the race mentioned in the comments, so removing those comments. If our cgroup changed between the two read syscalls, we will stick with the cgroup read from /proc/self/cgroup. This is the same behavior as cgroup change after FindCPU() returns, so nothing special to comment about now. parseCPUMount now returns error when the combined path is too long, to avoid panic or truncation if we got a really long path from mountinfo. cgrouptest is changed to use dev returned from stat() to detect filesystem boundary, since we don't return mount point and sub-path separately now. This also avoid using os.Root since we don't handle untrusted input here. os.Root is too complex, and the performance is bad. Fixes #76390 Change-Id: Ia9cbd7be3e58a2d51caf27a973fbd201dac06afc Reviewed-on: https://go-review.googlesource.com/c/go/+/723241 Reviewed-by: Michael Pratt Reviewed-by: Dmitri Shuralyov Auto-Submit: Michael Knyszek Auto-Submit: Michael Pratt LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Knyszek --- src/internal/cgrouptest/cgrouptest_linux.go | 48 ++-- src/internal/runtime/cgroup/cgroup.go | 201 +++++++++----- src/internal/runtime/cgroup/cgroup_linux.go | 48 ++-- src/internal/runtime/cgroup/cgroup_test.go | 280 +++++++++++++++++--- src/internal/runtime/cgroup/export_test.go | 3 +- 5 files changed, 424 insertions(+), 156 deletions(-) diff --git a/src/internal/cgrouptest/cgrouptest_linux.go b/src/internal/cgrouptest/cgrouptest_linux.go index 8437f992f74..ad9599c9383 100644 --- a/src/internal/cgrouptest/cgrouptest_linux.go +++ b/src/internal/cgrouptest/cgrouptest_linux.go @@ -50,9 +50,8 @@ func (c *CgroupV2) SetCPUMax(quota, period int64) error { // // This must not be used in parallel tests, as it affects the entire process. func InCgroupV2(t *testing.T, fn func(*CgroupV2)) { - mount, rel := findCurrent(t) - parent := findOwnedParent(t, mount, rel) - orig := filepath.Join(mount, rel) + orig := findCurrent(t) + parent := findOwnedParent(t, orig) // Make sure the parent allows children to control cpu. b, err := os.ReadFile(filepath.Join(parent, "cgroup.subtree_control")) @@ -93,34 +92,25 @@ func InCgroupV2(t *testing.T, fn func(*CgroupV2)) { fn(c) } -// Returns the mount and relative directory of the current cgroup the process -// is in. -func findCurrent(t *testing.T) (string, string) { +// Returns the filesystem path to the current cgroup the process is in. +func findCurrent(t *testing.T) string { // Find the path to our current CPU cgroup. Currently this package is // only used for CPU cgroup testing, so the distinction of different // controllers doesn't matter. var scratch [cgroup.ParseSize]byte buf := make([]byte, cgroup.PathSize) - n, err := cgroup.FindCPUMountPoint(buf, scratch[:]) + n, ver, err := cgroup.FindCPU(buf, scratch[:]) if err != nil { t.Skipf("cgroup: unable to find current cgroup mount: %v", err) } - mount := string(buf[:n]) - - n, ver, err := cgroup.FindCPURelativePath(buf, scratch[:]) - if err != nil { - t.Skipf("cgroup: unable to find current cgroup path: %v", err) - } if ver != cgroup.V2 { t.Skipf("cgroup: running on cgroup v%d want v2", ver) } - rel := string(buf[1:n]) // The returned path always starts with /, skip it. - rel = filepath.Join(".", rel) // Make sure this isn't empty string at root. - return mount, rel + return string(buf[:n]) } // Returns a parent directory in which we can create our own cgroup subdirectory. -func findOwnedParent(t *testing.T, mount, rel string) string { +func findOwnedParent(t *testing.T, orig string) string { // There are many ways cgroups may be set up on a system. We don't try // to cover all of them, just common ones. // @@ -142,7 +132,7 @@ func findOwnedParent(t *testing.T, mount, rel string) string { // We want to create our own subdirectory that we can migrate into and // then manipulate at will. It is tempting to create a new subdirectory - // inside the current cgroup we are already in, however that will likey + // inside the current cgroup we are already in, however that will likely // not work. cgroup v2 only allows processes to be in leaf cgroups. Our // current cgroup likely contains multiple processes (at least this one // and the cmd/go test runner). If we make a subdirectory and try to @@ -166,27 +156,29 @@ func findOwnedParent(t *testing.T, mount, rel string) string { // is empty. As far as I tell, the only purpose of this is to allow // reorganizing processes into a new set of subdirectories and then // adding controllers once done. - root, err := os.OpenRoot(mount) + var stat syscall.Stat_t + err := syscall.Stat(orig, &stat) if err != nil { - t.Fatalf("error opening cgroup mount root: %v", err) + t.Fatalf("error stating orig cgroup: %v", err) } uid := os.Getuid() var prev string - for rel != "." { - fi, err := root.Stat(rel) + cur := filepath.Dir(orig) + for cur != "/" { + var curStat syscall.Stat_t + err = syscall.Stat(cur, &curStat) if err != nil { t.Fatalf("error stating cgroup path: %v", err) } - st := fi.Sys().(*syscall.Stat_t) - if int(st.Uid) != uid { - // Stop at first directory we don't own. + if int(curStat.Uid) != uid || curStat.Dev != stat.Dev { + // Stop at first directory we don't own or filesystem boundary. break } - prev = rel - rel = filepath.Join(rel, "..") + prev = cur + cur = filepath.Dir(cur) } if prev == "" { @@ -194,7 +186,7 @@ func findOwnedParent(t *testing.T, mount, rel string) string { } // We actually want the last directory where we were the owner. - return filepath.Join(mount, prev) + return prev } // Migrate the current process to the cgroup directory dst. diff --git a/src/internal/runtime/cgroup/cgroup.go b/src/internal/runtime/cgroup/cgroup.go index 68c31fcbc3b..09519af1e10 100644 --- a/src/internal/runtime/cgroup/cgroup.go +++ b/src/internal/runtime/cgroup/cgroup.go @@ -102,21 +102,23 @@ func parseV2Limit(buf []byte) (float64, bool, error) { return float64(quota) / float64(period), true, nil } -// Finds the path of the current process's CPU cgroup relative to the cgroup -// mount and writes it to out. +// Finds the path of the current process's CPU cgroup and writes it to out. // +// fd is a file descriptor for /proc/self/cgroup. // Returns the number of bytes written and the cgroup version (1 or 2). -func parseCPURelativePath(fd int, read func(fd int, b []byte) (int, uintptr), out []byte, scratch []byte) (int, Version, error) { +func parseCPUCgroup(fd int, read func(fd int, b []byte) (int, uintptr), out []byte, scratch []byte) (int, Version, error) { // The format of each line is // // hierarchy-ID:controller-list:cgroup-path // // controller-list is comma-separated. - // See man 5 cgroup for more details. // // cgroup v2 has hierarchy-ID 0. If a v1 hierarchy contains "cpu", that // is the CPU controller. Otherwise the v2 hierarchy (if any) is the - // CPU controller. + // CPU controller. It is not possible to mount the same controller + // simultaneously under both the v1 and the v2 hierarchies. + // + // See man 7 cgroups for more details. // // hierarchy-ID and controller-list have relatively small maximum // sizes, and the path can be up to _PATH_MAX, so we need a bit more @@ -149,7 +151,7 @@ func parseCPURelativePath(fd int, read func(fd int, b []byte) (int, uintptr), ou // hierarchy-ID:controller-list:cgroup-path // // controller-list is comma-separated. - // See man 5 cgroup for more details. + // See man 7 cgroups for more details. i := bytealg.IndexByte(line, ':') if i < 0 { return 0, 0, errMalformedFile @@ -167,6 +169,15 @@ func parseCPURelativePath(fd int, read func(fd int, b []byte) (int, uintptr), ou line = line[i+1:] path := line + if len(path) == 0 || path[0] != '/' { + // We rely on this when composing the full path. + return 0, 0, errMalformedFile + } + if len(path) > len(out) { + // Should not be possible. If we really get a very long cgroup path, + // read /proc/self/cgroup will fail with ENAMETOOLONG. + return 0, 0, errPathTooLong + } if string(hierarchy) == "0" { // v2 hierarchy. @@ -214,9 +225,11 @@ func containsCPU(b []byte) bool { return false } -// Returns the mount point for the cpu cgroup controller (v1 or v2) from -// /proc/self/mountinfo. -func parseCPUMount(fd int, read func(fd int, b []byte) (int, uintptr), out []byte, scratch []byte) (int, error) { +// Returns the path to the specified cgroup and version with cpu controller +// +// fd is a file descriptor for /proc/self/mountinfo. +// Returns the number of bytes written. +func parseCPUMount(fd int, read func(fd int, b []byte) (int, uintptr), out, cgroup []byte, version Version, scratch []byte) (int, error) { // The format of each line is: // // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue @@ -240,8 +253,13 @@ func parseCPUMount(fd int, read func(fd int, b []byte) (int, uintptr), out []byt // carriage return. Those are escaped. See Linux show_mountinfo -> // show_path. We must unescape before returning. // - // We return the mount point (5) if the filesystem type (9) is cgroup2, - // or cgroup with "cpu" in the super options (11). + // A mount point matches if the filesystem type (9) is cgroup2, + // or cgroup with "cpu" in the super options (11), + // and the cgroup is in the root (4). If there are multiple matches, + // the first one is selected. + // + // We return full cgroup path, which is the mount point (5) + + // cgroup parameter without the root (4) prefix. // // (4), (5), and (10) are up to _PATH_MAX. The remaining fields have a // small fixed maximum size, so 4*_PATH_MAX is plenty of scratch space. @@ -250,11 +268,7 @@ func parseCPUMount(fd int, read func(fd int, b []byte) (int, uintptr), out []byt l := newLineReader(fd, scratch, read) - // Bytes written to out. - n := 0 - for { - //incomplete := false err := l.next() if err == errIncompleteLine { // An incomplete line is fine as long as it doesn't @@ -271,8 +285,8 @@ func parseCPUMount(fd int, read func(fd int, b []byte) (int, uintptr), out []byt line := l.line() - // Skip first four fields. - for range 4 { + // Skip first three fields. + for range 3 { i := bytealg.IndexByte(line, ' ') if i < 0 { return 0, errMalformedFile @@ -280,11 +294,23 @@ func parseCPUMount(fd int, read func(fd int, b []byte) (int, uintptr), out []byt line = line[i+1:] } - // (5) mount point: mount point relative to the process's root + // (4) root: root of the mount within the filesystem i := bytealg.IndexByte(line, ' ') if i < 0 { return 0, errMalformedFile } + root := line[:i] + if len(root) == 0 || root[0] != '/' { + // We rely on this in hasPathPrefix. + return 0, errMalformedFile + } + line = line[i+1:] + + // (5) mount point: mount point relative to the process's root + i = bytealg.IndexByte(line, ' ') + if i < 0 { + return 0, errMalformedFile + } mnt := line[:i] line = line[i+1:] @@ -313,53 +339,103 @@ func parseCPUMount(fd int, read func(fd int, b []byte) (int, uintptr), out []byt ftype := line[:i] line = line[i+1:] - if string(ftype) != "cgroup" && string(ftype) != "cgroup2" { - continue - } - - // As in findCPUPath, cgroup v1 with a CPU controller takes - // precendence over cgroup v2. - if string(ftype) == "cgroup2" { - // v2 hierarchy. - n, err = unescapePath(out, mnt) - if err != nil { - // Don't keep searching on error. The kernel - // should never produce broken escaping. - return n, err + switch version { + case V1: + if string(ftype) != "cgroup" { + continue } - // Keep searching, we might find a v1 hierarchy with a - // CPU controller, which takes precedence. + // (10) mount source: filesystem specific information or "none" + i = bytealg.IndexByte(line, ' ') + if i < 0 { + return 0, errMalformedFile + } + // Don't care about mount source. + line = line[i+1:] + + // (11) super options: per super block options + if !containsCPU(line) { + continue + } + case V2: + if string(ftype) != "cgroup2" { + continue + } + default: + throw("impossible cgroup version") + panic("unreachable") + } + + // Check cgroup is in the root. + // If the cgroup is /sandbox/container, the matching mount point root could be + // /sandbox/container, /sandbox, or / + rootLen, err := unescapePath(root, root) + if err != nil { + return 0, err + } + root = root[:rootLen] + if !hasPathPrefix(cgroup, root) { + continue // not matched, this is not the mount point we're looking for + } + + // Cutoff the root from cgroup, ensure rel starts with '/' or is empty. + rel := cgroup[rootLen:] + if rootLen == 1 && len(cgroup) > 1 { + // root is "/", but cgroup is not. Keep full cgroup path. + rel = cgroup + } + if hasPathPrefix(rel, []byte("/..")) { + // the cgroup is out of current cgroup namespace, and this mount point + // cannot reach that cgroup. + // + // e.g. If the process is in cgroup /init, but in a cgroup namespace + // rooted at /sandbox/container, /proc/self/cgroup will show /../../init. + // we can reach it if the mount point root is + // /../.. or /../../init, but not if it is /.. or / + // While mount point with root /../../.. should able to reach the cgroup, + // we don't know the path to the cgroup within that mount point. continue } - // (10) mount source: filesystem specific information or "none" - i = bytealg.IndexByte(line, ' ') - if i < 0 { - return 0, errMalformedFile + // All conditions met, compose the full path. + // Copy rel to the correct place first, it may overlap with out. + n := unescapedLen(mnt) + if n+len(rel) > len(out) { + return 0, errPathTooLong } - // Don't care about mount source. - line = line[i+1:] - - // (11) super options: per super block options - superOpt := line - - // v1 hierarchy - if containsCPU(superOpt) { - // Found a v1 CPU controller. This must be the - // only one, so we're done. - return unescapePath(out, mnt) + copy(out[n:], rel) + n2, err := unescapePath(out[:n], mnt) + if err != nil { + return 0, err } + if n2 != n { + throw("wrong unescaped len") + } + return n + len(rel), nil } - if n == 0 { - // Found nothing. - return 0, ErrNoCgroup - } - - return n, nil + // Found nothing. + return 0, ErrNoCgroup } -var errInvalidEscape error = stringError("invalid path escape sequence") +func hasPathPrefix(p, prefix []byte) bool { + i := len(prefix) + if i == 1 { + return true // root contains everything + } + if len(p) < i || !bytealg.Equal(prefix, p[:i]) { + return false + } + return len(p) == i || p[i] == '/' // must match at path boundary +} + +var ( + errInvalidEscape error = stringError("invalid path escape sequence") + errPathTooLong error = stringError("path too long") +) + +func unescapedLen(in []byte) int { + return len(in) - bytealg.Count(in, byte('\\'))*3 +} // unescapePath copies in to out, unescaping escape sequences generated by // Linux's show_path. @@ -367,20 +443,21 @@ var errInvalidEscape error = stringError("invalid path escape sequence") // That is, '\', ' ', '\t', and '\n' are converted to octal escape sequences, // like '\040' for space. // -// out must be at least as large as in. +// Caller must ensure that out at least has unescapedLen(in) bytes. +// in and out may alias; in-place unescaping is supported. // // Returns the number of bytes written to out. // // Also see escapePath in cgroup_linux_test.go. func unescapePath(out []byte, in []byte) (int, error) { - // Not strictly necessary, but simplifies the implementation and will - // always hold in users. - if len(out) < len(in) { - throw("output too small") - } - var outi, ini int for ini < len(in) { + if outi >= len(out) { + // given that caller already ensured out is long enough, this + // is only possible if there are malformed escape sequences + // we have not parsed yet. + return outi, errInvalidEscape + } c := in[ini] if c != '\\' { out[outi] = c diff --git a/src/internal/runtime/cgroup/cgroup_linux.go b/src/internal/runtime/cgroup/cgroup_linux.go index 5e3ee0d2c2c..d9add2188ce 100644 --- a/src/internal/runtime/cgroup/cgroup_linux.go +++ b/src/internal/runtime/cgroup/cgroup_linux.go @@ -211,44 +211,26 @@ func FindCPU(out []byte, scratch []byte) (int, Version, error) { checkBufferSize(scratch, ParseSize) // The cgroup path is + . - // - // This is racy if our cgroup is changed while this runs. For example, - // initially there is only a cgroup v2 mount and we are not in a - // cgroup. After, there a cgroup v1 mount with a CPU controller and we - // are placed in a cgroup in this hierarchy. In that case, findCPUMount - // could pick the v2 mount, and findCPURelativePath could find the v2 - // relative path. - // - // In this case we'll later fail to read the cgroup files and fall back - // to assuming no cgroup. + // relative path is the cgroup relative to the mount root. - n, err := FindCPUMountPoint(out, scratch) + n, version, err := FindCPUCgroup(out, scratch) if err != nil { return 0, 0, err } - // The relative path always starts with /, so we can directly append it - // to the mount point. - n2, version, err := FindCPURelativePath(out[n:], scratch) - if err != nil { - return 0, 0, err - } - n += n2 - - return n, version, nil + n, err = FindCPUMountPoint(out, out[:n], version, scratch) + return n, version, err } -// FindCPURelativePath finds the path to the CPU cgroup that this process is a member of -// relative to the root of the cgroup mount and places it in out. scratch is a -// scratch buffer for internal use. +// FindCPUCgroup finds the path to the CPU cgroup that this process is a member of +// and places it in out. scratch is a scratch buffer for internal use. // -// out must have length PathSize minus the size of the cgroup mount root (if -// known). scratch must have length ParseSize. +// out must have length PathSize. scratch must have length ParseSize. // // Returns the number of bytes written to out and the cgroup version (1 or 2). // // Returns ErrNoCgroup if the process is not in a CPU cgroup. -func FindCPURelativePath(out []byte, scratch []byte) (int, Version, error) { +func FindCPUCgroup(out []byte, scratch []byte) (int, Version, error) { path := []byte("/proc/self/cgroup\x00") fd, errno := linux.Open(&path[0], linux.O_RDONLY|linux.O_CLOEXEC, 0) if errno == linux.ENOENT { @@ -259,7 +241,7 @@ func FindCPURelativePath(out []byte, scratch []byte) (int, Version, error) { // The relative path always starts with /, so we can directly append it // to the mount point. - n, version, err := parseCPURelativePath(fd, linux.Read, out[:], scratch) + n, version, err := parseCPUCgroup(fd, linux.Read, out[:], scratch) if err != nil { linux.Close(fd) return 0, 0, err @@ -269,15 +251,17 @@ func FindCPURelativePath(out []byte, scratch []byte) (int, Version, error) { return n, version, nil } -// FindCPUMountPoint finds the root of the CPU cgroup mount places it in out. +// FindCPUMountPoint finds the mount point containing the specified cgroup and +// version with cpu controller, and compose the full path to the cgroup in out. // scratch is a scratch buffer for internal use. // -// out must have length PathSize. scratch must have length ParseSize. +// out must have length PathSize, may overlap with cgroup. +// scratch must have length ParseSize. // // Returns the number of bytes written to out. // -// Returns ErrNoCgroup if the process is not in a CPU cgroup. -func FindCPUMountPoint(out []byte, scratch []byte) (int, error) { +// Returns ErrNoCgroup if no matching mount point is found. +func FindCPUMountPoint(out, cgroup []byte, version Version, scratch []byte) (int, error) { checkBufferSize(out, PathSize) checkBufferSize(scratch, ParseSize) @@ -289,7 +273,7 @@ func FindCPUMountPoint(out []byte, scratch []byte) (int, error) { return 0, errSyscallFailed } - n, err := parseCPUMount(fd, linux.Read, out, scratch) + n, err := parseCPUMount(fd, linux.Read, out, cgroup, version, scratch) if err != nil { linux.Close(fd) return 0, err diff --git a/src/internal/runtime/cgroup/cgroup_test.go b/src/internal/runtime/cgroup/cgroup_test.go index a4ffdf3ba17..79263821c3c 100644 --- a/src/internal/runtime/cgroup/cgroup_test.go +++ b/src/internal/runtime/cgroup/cgroup_test.go @@ -12,8 +12,6 @@ import ( "testing" ) -const _PATH_MAX = 4096 - func TestParseV1Number(t *testing.T) { tests := []struct { name string @@ -156,7 +154,22 @@ func TestParseV2Limit(t *testing.T) { } } -func TestParseCPURelativePath(t *testing.T) { +func readString(contents string) func(fd int, b []byte) (int, uintptr) { + r := strings.NewReader(contents) + return func(fd int, b []byte) (int, uintptr) { + n, err := r.Read(b) + if err != nil && err != io.EOF { + const dummyErrno = 42 + return n, dummyErrno + } + return n, 0 + } +} + +func TestParseCPUCgroup(t *testing.T) { + veryLongPathName := strings.Repeat("a", cgroup.PathSize+10) + evenLongerPathName := strings.Repeat("a", cgroup.ParseSize+10) + tests := []struct { name string contents string @@ -169,6 +182,16 @@ func TestParseCPURelativePath(t *testing.T) { contents: "", wantErr: true, }, + { + name: "too-long", + contents: "0::/" + veryLongPathName + "\n", + wantErr: true, + }, + { + name: "too-long-line", + contents: "0::/" + evenLongerPathName + "\n", + wantErr: true, + }, { name: "v1", contents: `2:cpu,cpuacct:/a/b/cpu @@ -196,19 +219,9 @@ func TestParseCPURelativePath(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - r := strings.NewReader(tc.contents) - read := func(fd int, b []byte) (int, uintptr) { - n, err := r.Read(b) - if err != nil && err != io.EOF { - const dummyErrno = 42 - return n, dummyErrno - } - return n, 0 - } - var got [cgroup.PathSize]byte var scratch [cgroup.ParseSize]byte - n, gotVer, err := cgroup.ParseCPURelativePath(0, read, got[:], scratch[:]) + n, gotVer, err := cgroup.ParseCPUCgroup(0, readString(tc.contents), got[:], scratch[:]) if (err != nil) != tc.wantErr { t.Fatalf("parseCPURelativePath got err %v want %v", err, tc.wantErr) } @@ -224,6 +237,25 @@ func TestParseCPURelativePath(t *testing.T) { } } +func TestParseCPUCgroupMalformed(t *testing.T) { + for _, contents := range []string{ + "\n", + "0\n", + "0:\n", + "0::\n", + "0::a\n", + } { + t.Run("", func(t *testing.T) { + var got [cgroup.PathSize]byte + var scratch [cgroup.ParseSize]byte + n, v, err := cgroup.ParseCPUCgroup(0, readString(contents), got[:], scratch[:]) + if err != cgroup.ErrMalformedFile { + t.Errorf("ParseCPUCgroup got %q (v%d), %v, want ErrMalformedFile", string(got[:n]), v, err) + } + }) + } +} + func TestContainsCPU(t *testing.T) { tests := []struct { in string @@ -279,9 +311,21 @@ func TestParseCPUMount(t *testing.T) { overlayLongLowerDir += fmt.Sprintf(":%s%d", lowerPath, i) } + var longPath [4090]byte + for i := range longPath { + longPath[i] = byte(i) + } + escapedLongPath := escapePath(string(longPath[:])) + if len(escapedLongPath) <= cgroup.PathSize { + // ensure we actually support over PathSize long escaped path + t.Fatalf("escapedLongPath is too short to test") + } + tests := []struct { name string contents string + cgroup string + version cgroup.Version want string wantErr bool }{ @@ -290,6 +334,20 @@ func TestParseCPUMount(t *testing.T) { contents: "", wantErr: true, }, + { + name: "invalid-root", + contents: "56 22 0:40 /\\1 /sys/fs/cgroup/cpu rw - cgroup cgroup rw,cpu,cpuacct\n", + cgroup: "/", + version: cgroup.V1, + wantErr: true, + }, + { + name: "invalid-mount", + contents: "56 22 0:40 / /sys/fs/cgroup/\\1 rw - cgroup cgroup rw,cpu,cpuacct\n", + cgroup: "/", + version: cgroup.V1, + wantErr: true, + }, { name: "v1", contents: `22 1 8:1 / / rw,relatime - ext4 /dev/root rw @@ -301,7 +359,9 @@ func TestParseCPUMount(t *testing.T) { 58 22 0:42 / /sys/fs/cgroup/net rw - cgroup cgroup rw,net 59 22 0:43 / /sys/fs/cgroup/cpuset rw - cgroup cgroup rw,cpuset `, - want: "/sys/fs/cgroup/cpu", + cgroup: "/", + version: cgroup.V1, + want: "/sys/fs/cgroup/cpu", }, { name: "v2", @@ -310,7 +370,9 @@ func TestParseCPUMount(t *testing.T) { 21 22 0:20 / /sys rw,nosuid,nodev,noexec - sysfs sysfs rw 25 21 0:22 / /sys/fs/cgroup rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw `, - want: "/sys/fs/cgroup", + cgroup: "/", + version: cgroup.V2, + want: "/sys/fs/cgroup", }, { name: "mixed", @@ -324,7 +386,25 @@ func TestParseCPUMount(t *testing.T) { 58 22 0:42 / /sys/fs/cgroup/net rw - cgroup cgroup rw,net 59 22 0:43 / /sys/fs/cgroup/cpuset rw - cgroup cgroup rw,cpuset `, - want: "/sys/fs/cgroup/cpu", + cgroup: "/", + version: cgroup.V1, + want: "/sys/fs/cgroup/cpu", + }, + { + name: "mixed-choose-v2", + contents: `22 1 8:1 / / rw,relatime - ext4 /dev/root rw +20 22 0:19 / /proc rw,nosuid,nodev,noexec - proc proc rw +21 22 0:20 / /sys rw,nosuid,nodev,noexec - sysfs sysfs rw +25 21 0:22 / /sys/fs/cgroup rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw +49 22 0:37 / /sys/fs/cgroup/memory rw - cgroup cgroup rw,memory +54 22 0:38 / /sys/fs/cgroup/io rw - cgroup cgroup rw,io +56 22 0:40 / /sys/fs/cgroup/cpu rw - cgroup cgroup rw,cpu,cpuacct +58 22 0:42 / /sys/fs/cgroup/net rw - cgroup cgroup rw,net +59 22 0:43 / /sys/fs/cgroup/cpuset rw - cgroup cgroup rw,cpuset +`, + cgroup: "/", + version: cgroup.V2, + want: "/sys/fs/cgroup", }, { name: "v2-escaped", @@ -333,7 +413,9 @@ func TestParseCPUMount(t *testing.T) { 21 22 0:20 / /sys rw,nosuid,nodev,noexec - sysfs sysfs rw 25 21 0:22 / /sys/fs/cgroup/tab\011tab rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw `, - want: `/sys/fs/cgroup/tab tab`, + cgroup: "/", + version: cgroup.V2, + want: `/sys/fs/cgroup/tab tab`, }, { // Overly long line on a different mount doesn't matter. @@ -344,25 +426,125 @@ func TestParseCPUMount(t *testing.T) { 262 31 0:72 / /tmp/overlay2/0143e063b02f4801de9c847ad1c5ddc21fd2ead00653064d0c72ea967b248870/merged rw,relatime shared:729 - overlay overlay rw,lowerdir=` + overlayLongLowerDir + `,upperdir=/tmp/diff,workdir=/tmp/work 25 21 0:22 / /sys/fs/cgroup rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw `, - want: "/sys/fs/cgroup", + cgroup: "/", + version: cgroup.V2, + want: "/sys/fs/cgroup", + }, + { + name: "long-escaped-path", + contents: `22 1 8:1 / / rw,relatime - ext4 /dev/root rw +20 22 0:19 / /proc rw,nosuid,nodev,noexec - proc proc rw +21 22 0:20 / /sys rw,nosuid,nodev,noexec - sysfs sysfs rw +25 21 0:22 / /sys/` + escapedLongPath + ` rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw +`, + cgroup: "/", + version: cgroup.V2, + want: "/sys/" + string(longPath[:]), + }, + { + name: "too-long-escaped-path", + contents: `22 1 8:1 / / rw,relatime - ext4 /dev/root rw +20 22 0:19 / /proc rw,nosuid,nodev,noexec - proc proc rw +21 22 0:20 / /sys rw,nosuid,nodev,noexec - sysfs sysfs rw +25 21 0:22 / /sys/` + escapedLongPath + ` rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw +`, + cgroup: "/container", // compared to above, this makes the path too long + version: cgroup.V2, + wantErr: true, + }, + { + name: "non-root_mount", + contents: `22 1 8:1 / / rw,relatime - ext4 /dev/root rw +20 22 0:19 / /proc rw,nosuid,nodev,noexec - proc proc rw +21 22 0:20 / /sys rw,nosuid,nodev,noexec - sysfs sysfs rw +25 21 0:22 /sand /unrelated/cgroup1 rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw +25 21 0:22 /stone /unrelated/cgroup2 rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw +25 21 0:22 /sandbox/container/group /sys/fs/cgroup/mygroup rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw +25 21 0:22 /sandbox /sys/fs/cgroup rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw +25 21 0:22 / /ignored/second/match rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw +`, + cgroup: "/sandbox/container", + version: cgroup.V2, + want: "/sys/fs/cgroup/container", + }, + { + name: "v2-escaped-root", + contents: `22 1 8:1 / / rw,relatime - ext4 /dev/root rw +20 22 0:19 / /proc rw,nosuid,nodev,noexec - proc proc rw +21 22 0:20 / /sys rw,nosuid,nodev,noexec - sysfs sysfs rw +25 21 0:22 /tab\011tab /sys/fs/cgroup rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw +`, + cgroup: "/tab tab/container", + version: cgroup.V2, + want: `/sys/fs/cgroup/container`, + }, + { + name: "non-root_cgroup", + contents: `22 1 8:1 / / rw,relatime - ext4 /dev/root rw +20 22 0:19 / /proc rw,nosuid,nodev,noexec - proc proc rw +21 22 0:20 / /sys rw,nosuid,nodev,noexec - sysfs sysfs rw +25 21 0:22 / /sys/fs/cgroup rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw +`, + cgroup: "/sandbox/container", + version: cgroup.V2, + want: "/sys/fs/cgroup/sandbox/container", + }, + { + name: "mixed_non-root", + contents: `22 1 8:1 / / rw,relatime - ext4 /dev/root rw +20 22 0:19 / /proc rw,nosuid,nodev,noexec - proc proc rw +21 22 0:20 / /sys rw,nosuid,nodev,noexec - sysfs sysfs rw +25 21 0:22 /sandbox /sys/fs/cgroup rw,nosuid,nodev,noexec - cgroup2 cgroup2 rw +49 22 0:37 /sandbox /sys/fs/cgroup/memory rw - cgroup cgroup rw,memory +54 22 0:38 /sandbox /sys/fs/cgroup/io rw - cgroup cgroup rw,io +56 22 0:40 /sand /unrelated/cgroup1 rw - cgroup cgroup rw,cpu,cpuacct +56 22 0:40 /stone /unrelated/cgroup2 rw - cgroup cgroup rw,cpu,cpuacct +56 22 0:40 /sandbox /sys/fs/cgroup/cpu rw - cgroup cgroup rw,cpu,cpuacct +56 22 0:40 /sandbox/container/group /sys/fs/cgroup/cpu/mygroup rw - cgroup cgroup rw,cpu,cpuacct +56 22 0:40 / /ignored/second/match rw - cgroup cgroup rw,cpu,cpuacct +58 22 0:42 /sandbox /sys/fs/cgroup/net rw - cgroup cgroup rw,net +59 22 0:43 /sandbox /sys/fs/cgroup/cpuset rw - cgroup cgroup rw,cpuset +`, + cgroup: "/sandbox/container", + version: cgroup.V1, + want: "/sys/fs/cgroup/cpu/container", + }, + { + // to see an example of this, for a PID in a cgroup namespace, run: + // nsenter -t -C -- cat /proc/self/cgroup + // nsenter -t -C -- grep cgroup /proc/self/mountinfo + // /mnt can be generated with `mount --bind /sys/fs/cgroup/kubepods.slice /mnt`, + // assuming PID is in cgroup /kubepods.slice + name: "out_of_namespace", + contents: `22 1 8:1 / / rw,relatime - ext4 /dev/root rw +20 22 0:19 / /proc rw,nosuid,nodev,noexec - proc proc rw +21 22 0:20 / /sys rw,nosuid,nodev,noexec - sysfs sysfs rw +1243 61 0:26 /../../.. /mnt rw,nosuid,nodev,noexec,relatime shared:4 - cgroup2 cgroup2 rw +29 22 0:26 /../../../.. /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime shared:4 - cgroup2 cgroup2 rw`, + cgroup: "/../../../../init.scope", + version: cgroup.V2, + want: "/sys/fs/cgroup/init.scope", + }, + { + name: "out_of_namespace-root", // the process is directly in the root cgroup + contents: `22 1 8:1 / / rw,relatime - ext4 /dev/root rw +20 22 0:19 / /proc rw,nosuid,nodev,noexec - proc proc rw +21 22 0:20 / /sys rw,nosuid,nodev,noexec - sysfs sysfs rw +1243 61 0:26 /../../.. /mnt rw,nosuid,nodev,noexec,relatime shared:4 - cgroup2 cgroup2 rw +29 22 0:26 /../../../.. /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime shared:4 - cgroup2 cgroup2 rw`, + cgroup: "/../../../..", + version: cgroup.V2, + want: "/sys/fs/cgroup", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - r := strings.NewReader(tc.contents) - read := func(fd int, b []byte) (int, uintptr) { - n, err := r.Read(b) - if err != nil && err != io.EOF { - const dummyErrno = 42 - return n, dummyErrno - } - return n, 0 - } - var got [cgroup.PathSize]byte var scratch [cgroup.ParseSize]byte - n, err := cgroup.ParseCPUMount(0, read, got[:], scratch[:]) + n := copy(got[:], tc.cgroup) + n, err := cgroup.ParseCPUMount(0, readString(tc.contents), got[:], + got[:n], tc.version, scratch[:]) if (err != nil) != tc.wantErr { t.Fatalf("parseCPUMount got err %v want %v", err, tc.wantErr) } @@ -374,6 +556,31 @@ func TestParseCPUMount(t *testing.T) { } } +func TestParseCPUMountMalformed(t *testing.T) { + for _, contents := range []string{ + "\n", + "22\n", + "22 1 8:1\n", + "22 1 8:1 /\n", + "22 1 8:1 / /cgroup\n", + "22 1 8:1 / /cgroup rw\n", + "22 1 8:1 / /cgroup rw -\n", + "22 1 8:1 / /cgroup rw - \n", + "22 1 8:1 / /cgroup rw - cgroup\n", + "22 1 8:1 / /cgroup rw - cgroup cgroup\n", + "22 1 8:1 a /cgroup rw - cgroup cgroup cpu\n", + } { + t.Run("", func(t *testing.T) { + var got [cgroup.PathSize]byte + var scratch [cgroup.ParseSize]byte + n, err := cgroup.ParseCPUMount(0, readString(contents), got[:], []byte("/"), cgroup.V1, scratch[:]) + if err != cgroup.ErrMalformedFile { + t.Errorf("parseCPUMount got %q, %v, want ErrMalformedFile", string(got[:n]), err) + } + }) + } +} + // escapePath performs escaping equivalent to Linux's show_path. // // That is, '\', ' ', '\t', and '\n' are converted to octal escape sequences, @@ -453,9 +660,7 @@ b/c`, t.Run("unescapePath", func(t *testing.T) { for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - in := []byte(tc.escaped) - out := make([]byte, len(in)) + runTest := func(in, out []byte) { n, err := cgroup.UnescapePath(out, in) if err != nil { t.Errorf("unescapePath got err %v want nil", err) @@ -464,6 +669,15 @@ b/c`, if got != tc.unescaped { t.Errorf("unescapePath got %q want %q", got, tc.escaped) } + } + t.Run(tc.name, func(t *testing.T) { + in := []byte(tc.escaped) + out := make([]byte, len(in)) + runTest(in, out) + }) + t.Run("inplace/"+tc.name, func(t *testing.T) { + in := []byte(tc.escaped) + runTest(in, in) }) } }) diff --git a/src/internal/runtime/cgroup/export_test.go b/src/internal/runtime/cgroup/export_test.go index 55acdc0877e..d2eac001575 100644 --- a/src/internal/runtime/cgroup/export_test.go +++ b/src/internal/runtime/cgroup/export_test.go @@ -21,6 +21,7 @@ func NewLineReader(fd int, scratch []byte, read func(fd int, b []byte) (int, uin var ( ErrEOF = errEOF ErrIncompleteLine = errIncompleteLine + ErrMalformedFile = errMalformedFile ) var ContainsCPU = containsCPU @@ -28,7 +29,7 @@ var ContainsCPU = containsCPU var ParseV1Number = parseV1Number var ParseV2Limit = parseV2Limit -var ParseCPURelativePath = parseCPURelativePath +var ParseCPUCgroup = parseCPUCgroup var ParseCPUMount = parseCPUMount var UnescapePath = unescapePath From cead111a772c2852c870fb140029d89152da4d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E7=8E=AE=E6=96=87?= Date: Sat, 22 Nov 2025 11:00:47 +0800 Subject: [PATCH 103/140] internal/runtime/cgroup: stricter unescapePath 8 and 9 in escape sequence is invalid now, it should be octal. Escape sequence larger than \377 is invalid now, it does not fit one byte. Change-Id: I3fdebce1d054c44919f0e66a33c778b5a2b099e2 Reviewed-on: https://go-review.googlesource.com/c/go/+/723242 Reviewed-by: Dmitri Shuralyov Reviewed-by: Michael Pratt LUCI-TryBot-Result: Go LUCI Auto-Submit: Dmitri Shuralyov --- src/internal/runtime/cgroup/cgroup.go | 11 +++++++---- src/internal/runtime/cgroup/cgroup_test.go | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/internal/runtime/cgroup/cgroup.go b/src/internal/runtime/cgroup/cgroup.go index 09519af1e10..46a25ad28b3 100644 --- a/src/internal/runtime/cgroup/cgroup.go +++ b/src/internal/runtime/cgroup/cgroup.go @@ -474,18 +474,21 @@ func unescapePath(out []byte, in []byte) (int, error) { return outi, errInvalidEscape } - var outc byte + var outc int for i := range 3 { c := in[ini+1+i] - if c < '0' || c > '9' { + if c < '0' || c > '7' { return outi, errInvalidEscape } outc *= 8 - outc += c - '0' + outc += int(c - '0') } - out[outi] = outc + if outc > 0xFF { + return outi, errInvalidEscape + } + out[outi] = byte(outc) outi++ ini += 4 diff --git a/src/internal/runtime/cgroup/cgroup_test.go b/src/internal/runtime/cgroup/cgroup_test.go index 79263821c3c..a82c7b3bf4e 100644 --- a/src/internal/runtime/cgroup/cgroup_test.go +++ b/src/internal/runtime/cgroup/cgroup_test.go @@ -682,3 +682,23 @@ b/c`, } }) } + +func TestUnescapeInvalidPath(t *testing.T) { + for _, in := range []string{ + `/a/b\c`, + `/a/b\01`, + `/a/b\018`, + `/a/b\01c`, + `/a/b\777`, + `01234567890123456789`, // too long + `\001\002\003\004\005\006\007\010\011`, // too long + } { + out := make([]byte, 8) + t.Run(in, func(t *testing.T) { + _, err := cgroup.UnescapePath(out, []byte(in)) + if err == nil { + t.Errorf("unescapePath got nil err, want non-nil") + } + }) + } +} From 2947cb0469aa89aac70cc6e3968f00f4c625671a Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Thu, 27 Nov 2025 11:42:27 +0100 Subject: [PATCH 104/140] runtime/_mkmalloc: fix log.Fatal formatting directive Change-Id: I9b9b9dbde440c3a24599efd55ef6f85a6a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/724281 Reviewed-by: David Chase Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: Go LUCI Auto-Submit: Filippo Valsorda --- src/runtime/_mkmalloc/mkmalloc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/_mkmalloc/mkmalloc.go b/src/runtime/_mkmalloc/mkmalloc.go index 8032983da8c..434eaad7677 100644 --- a/src/runtime/_mkmalloc/mkmalloc.go +++ b/src/runtime/_mkmalloc/mkmalloc.go @@ -288,7 +288,7 @@ func inline(config generatorConfig) []byte { case foldCondition: stamped = foldIfCondition(stamped, repl.from, repl.to) default: - log.Fatal("unknown op kind %v", repl.kind) + log.Fatalf("unknown op kind %v", repl.kind) } } From c079dd13c0b0e977d607cf2775bc2f16dd3d106e Mon Sep 17 00:00:00 2001 From: Daniel Morsing Date: Thu, 27 Nov 2025 06:45:20 +0000 Subject: [PATCH 105/140] runtime/secret: reorganize tests to fix -buildmode=shared The testing assembly methods had a linkname that was implicitly satisfied during the regular build but not there during the shared build. Fix by moving the testing routine into the package itself. For good measure, section off the assembly files from the non-experiment build. Should prevent further build failures as we work on this. Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest,gotip-linux-arm64-longtest Change-Id: I2b45668e44641ae7880ff14f6402d982c7eaedd7 Reviewed-on: https://go-review.googlesource.com/c/go/+/724001 Reviewed-by: David Chase Auto-Submit: Filippo Valsorda Reviewed-by: Filippo Valsorda LUCI-TryBot-Result: Go LUCI Reviewed-by: Dmitri Shuralyov --- src/runtime/secret/asm_amd64.s | 4 +++- src/runtime/secret/asm_arm64.s | 2 ++ src/runtime/secret/stubs.go | 13 ++++++++++++- src/runtime/secret/testdata/crash.go | 7 ------- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/runtime/secret/asm_amd64.s b/src/runtime/secret/asm_amd64.s index 7011afc5eb7..0f2a4747b46 100644 --- a/src/runtime/secret/asm_amd64.s +++ b/src/runtime/secret/asm_amd64.s @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build goexperiment.runtimesecret + // Note: this assembly file is used for testing only. // We need to access registers directly to properly test // that secrets are erased and go test doesn't like to conditionally @@ -208,6 +210,6 @@ noavx512: // registers contain secrets. // It also tests the path from G stack to M stack // to scheduler and back. - CALL ·delay(SB) + CALL runtime∕secret·delay(SB) RET diff --git a/src/runtime/secret/asm_arm64.s b/src/runtime/secret/asm_arm64.s index 1d7f7c1c924..6fa625adf6f 100644 --- a/src/runtime/secret/asm_arm64.s +++ b/src/runtime/secret/asm_arm64.s @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build goexperiment.runtimesecret + // Note: this assembly file is used for testing only. // We need to access registers directly to properly test // that secrets are erased and go test doesn't like to conditionally diff --git a/src/runtime/secret/stubs.go b/src/runtime/secret/stubs.go index ec66ef2729a..dd9ed04df3a 100644 --- a/src/runtime/secret/stubs.go +++ b/src/runtime/secret/stubs.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build arm64 || amd64 +//go:build goexperiment.runtimesecret && (arm64 || amd64) // testing stubs, these are implemented in assembly in // asm_$GOARCH.s @@ -30,3 +30,14 @@ func spillRegisters(p unsafe.Pointer) uintptr // //go:noescape func useSecret(secret []byte) + +// callback from assembly +func delay() { + sleep(1_000_000) +} + +// linknamed to avoid package importing time +// for just testing code +// +//go:linkname sleep time.Sleep +func sleep(int64) diff --git a/src/runtime/secret/testdata/crash.go b/src/runtime/secret/testdata/crash.go index cf48fb7d44c..1ee1ea6b8e0 100644 --- a/src/runtime/secret/testdata/crash.go +++ b/src/runtime/secret/testdata/crash.go @@ -18,13 +18,6 @@ import ( "weak" ) -// callback from assembly -// -//go:linkname delay main.delay -func delay() { - time.Sleep(1 * time.Millisecond) -} - // Same secret as in ../../crash_test.go var secretStore = [8]byte{ 0x00, From 67d4a28707fe948b4d5fe3e171717ab887730c2b Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Fri, 18 Jul 2025 14:57:38 -0400 Subject: [PATCH 106/140] fmt: document space behavior of Append Also, introduce the {Print,Fprint,Sprint,Append}{,f,ln} cross product of functions at the top of the docs. Fixes #74656 Change-Id: I85a156cd545ca866e579d8020ddf165cd4bcb26f Reviewed-on: https://go-review.googlesource.com/c/go/+/688877 Reviewed-by: Rob Pike LUCI-TryBot-Result: Go LUCI Reviewed-by: Damien Neil --- src/fmt/doc.go | 14 +++++++++++++- src/fmt/print.go | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/fmt/doc.go b/src/fmt/doc.go index fa0ffa7f00c..46f30b44e9a 100644 --- a/src/fmt/doc.go +++ b/src/fmt/doc.go @@ -9,6 +9,18 @@ are simpler. # Printing +There are four families of printing functions defined by their output destination. +[Print], [Println] and [Printf] write to [os.Stdout]; +[Sprint], [Sprintln] and [Sprintf] return a string; +[Fprint], [Fprintln] and [Fprintf] write to an [io.Writer]; and +[Append], [Appendln] and [Appendf] append the output to a byte slice. + +The functions within each family do the formatting according to the end of the name. +Print, Sprint, Fprint and Append use the default format for each argument, +adding a space between operands when neither is a string. +Println, Sprintln, Fprintln and Appendln always add spaces and append a newline. +Printf, Sprintf, Fprintf and Appendf use a sequence of "verbs" to control the formatting. + The verbs: General: @@ -222,7 +234,7 @@ formatting methods such as Error or String on unexported fields. # Explicit argument indexes -In [Printf], [Sprintf], and [Fprintf], the default behavior is for each +In [Printf], [Sprintf], [Fprintf], and [Appendf], the default behavior is for each formatting verb to format successive arguments passed in the call. However, the notation [n] immediately before the verb indicates that the nth one-indexed argument is to be formatted instead. The same notation diff --git a/src/fmt/print.go b/src/fmt/print.go index 01cfa1a1c7d..2340ceed8f2 100644 --- a/src/fmt/print.go +++ b/src/fmt/print.go @@ -284,6 +284,7 @@ func Sprint(a ...any) string { // Append formats using the default formats for its operands, appends the result to // the byte slice, and returns the updated slice. +// Spaces are added between operands when neither is a string. func Append(b []byte, a ...any) []byte { p := newPrinter() p.doPrint(a) From de456450e7a49789de63c515cb78a1e38e88440e Mon Sep 17 00:00:00 2001 From: Daniel Morsing Date: Fri, 28 Nov 2025 09:40:00 +0000 Subject: [PATCH 107/140] runtime/secret: disable tests under memory validating modes These tests rely on reading memory that has been freed, so any of the modes that validate memory accesses are going to fail. Disable them for now. Fixes #76586. Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-msan-clang15,gotip-linux-amd64-asan-clang15,gotip-linux-amd64-race Change-Id: I14ee5dfccbafa0e4da684a95ee42acf54499b013 Reviewed-on: https://go-review.googlesource.com/c/go/+/725140 Auto-Submit: Keith Randall LUCI-TryBot-Result: Go LUCI Reviewed-by: Keith Randall Reviewed-by: Keith Randall Reviewed-by: Carlos Amedee --- src/runtime/secret/secret_test.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/runtime/secret/secret_test.go b/src/runtime/secret/secret_test.go index 7651a93ca5e..98d67cf8a41 100644 --- a/src/runtime/secret/secret_test.go +++ b/src/runtime/secret/secret_test.go @@ -2,10 +2,14 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// the race detector does not like our pointer shenanigans -// while checking the stack. +// these tests rely on inspecting freed memory, so they +// can't be run under any of the memory validating modes. +// TODO: figure out just which test violate which condition +// and split this file out by individual test cases. +// There could be some value to running some of these +// under validation -//go:build goexperiment.runtimesecret && (arm64 || amd64) && linux && !race +//go:build goexperiment.runtimesecret && (arm64 || amd64) && linux && !race && !asan && !msan package secret From 2ac1f9cbc3eb2b97ad82730569199ba6ffea956d Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 28 Nov 2025 17:19:42 +0700 Subject: [PATCH 108/140] cmd/compile: avoid unnecessary interface conversion in bloop Fixes #76482 Change-Id: I076568d8ae92ad6c9e0a5797cfe5bbfb615f63d2 Reviewed-on: https://go-review.googlesource.com/c/go/+/725180 LUCI-TryBot-Result: Go LUCI Reviewed-by: Keith Randall Reviewed-by: Keith Randall Reviewed-by: Dmitri Shuralyov Auto-Submit: Cuong Manh Le --- src/cmd/compile/internal/bloop/bloop.go | 4 ++-- test/bloop.go | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/cmd/compile/internal/bloop/bloop.go b/src/cmd/compile/internal/bloop/bloop.go index e4a86d89142..761b07abd4d 100644 --- a/src/cmd/compile/internal/bloop/bloop.go +++ b/src/cmd/compile/internal/bloop/bloop.go @@ -74,7 +74,7 @@ func getNameFromNode(n ir.Node) *ir.Name { } // keepAliveAt returns a statement that is either curNode, or a -// block containing curNode followed by a call to runtime.keepAlive for each +// block containing curNode followed by a call to runtime.KeepAlive for each // node in ns. These calls ensure that nodes in ns will be live until // after curNode's execution. func keepAliveAt(ns []ir.Node, curNode ir.Node) ir.Node { @@ -94,7 +94,7 @@ func keepAliveAt(ns []ir.Node, curNode ir.Node) ir.Node { if n.Sym().IsBlank() { continue } - arg := ir.NewConvExpr(pos, ir.OCONV, types.Types[types.TINTER], n) + arg := ir.NewConvExpr(pos, ir.OCONV, types.Types[types.TUNSAFEPTR], typecheck.NodAddr(n)) if !n.Type().IsInterface() { srcRType0 := reflectdata.TypePtrAt(pos, n.Type()) arg.TypeWord = srcRType0 diff --git a/test/bloop.go b/test/bloop.go index a19d8345b00..fd22132dbfa 100644 --- a/test/bloop.go +++ b/test/bloop.go @@ -31,23 +31,23 @@ func test(b *testing.B, localsink, cond int) { // ERROR ".*" } somethingptr := &something for b.Loop() { // ERROR "inlining call to testing\.\(\*B\)\.Loop" - caninline(1) // ERROR "inlining call to caninline" "function result will be kept alive" ".* does not escape" - caninlineNoRet(1) // ERROR "inlining call to caninlineNoRet" "function arg will be kept alive" ".* does not escape" + caninline(1) // ERROR "inlining call to caninline" "function result will be kept alive" + caninlineNoRet(1) // ERROR "inlining call to caninlineNoRet" "function arg will be kept alive" caninlineVariadic(1) // ERROR "inlining call to caninlineVariadic" "function arg will be kept alive" ".* does not escape" caninlineVariadic(localsink) // ERROR "inlining call to caninlineVariadic" "localsink will be kept alive" ".* does not escape" - localsink = caninline(1) // ERROR "inlining call to caninline" "localsink will be kept alive" ".* does not escape" - localsink += 5 // ERROR "localsink will be kept alive" ".* does not escape" - localsink, cond = 1, 2 // ERROR "localsink will be kept alive" "cond will be kept alive" ".* does not escape" + localsink = caninline(1) // ERROR "inlining call to caninline" "localsink will be kept alive" + localsink += 5 // ERROR "localsink will be kept alive" + localsink, cond = 1, 2 // ERROR "localsink will be kept alive" "cond will be kept alive" *somethingptr = 1 // ERROR "dereference will be kept alive" if cond > 0 { - caninline(1) // ERROR "inlining call to caninline" "function result will be kept alive" ".* does not escape" + caninline(1) // ERROR "inlining call to caninline" "function result will be kept alive" } switch cond { case 2: - caninline(1) // ERROR "inlining call to caninline" "function result will be kept alive" ".* does not escape" + caninline(1) // ERROR "inlining call to caninline" "function result will be kept alive" } { - caninline(1) // ERROR "inlining call to caninline" "function result will be kept alive" ".* does not escape" + caninline(1) // ERROR "inlining call to caninline" "function result will be kept alive" } } } From 3f94f3d4b2f03a913de3f5a737bad793418e751f Mon Sep 17 00:00:00 2001 From: Joel Sing Date: Sun, 23 Nov 2025 01:10:41 +1100 Subject: [PATCH 109/140] test/codegen: fix shift tests on riscv64 These were broken by CL 721206, which changes Rsh to RshU for positive inputs. Change-Id: I9e38c3c428fb8aeb70cf51e7e76f4711c864f027 Reviewed-on: https://go-review.googlesource.com/c/go/+/723340 Reviewed-by: Meng Zhuo Reviewed-by: Mark Ryan Reviewed-by: Keith Randall Auto-Submit: Jorropo Reviewed-by: Dmitri Shuralyov Reviewed-by: Jorropo LUCI-TryBot-Result: Go LUCI --- test/codegen/shift.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/codegen/shift.go b/test/codegen/shift.go index 1877247af4d..4222b0b845d 100644 --- a/test/codegen/shift.go +++ b/test/codegen/shift.go @@ -661,7 +661,7 @@ func rsh64to32(v int64) int32 { x := int32(v) // riscv64:"MOVW" if x > 8 { - // riscv64:"SRAIW" -"MOVW" -"SLLI" + // riscv64:"SRLIW" -"MOVW" -"SLLI" x >>= 2 } return x @@ -671,7 +671,7 @@ func rsh64to16(v int64) int16 { x := int16(v) // riscv64:"MOVH" if x > 8 { - // riscv64:"SLLI" "SRAI" + // riscv64:"SLLI" "SRLI" x >>= 2 } return x @@ -681,7 +681,7 @@ func rsh64to8(v int64) int8 { x := int8(v) // riscv64:"MOVB" if x > 8 { - // riscv64:"SLLI" "SRAI" + // riscv64:"SLLI" "SRLI" x >>= 2 } return x From eec1afeb28522df37c78c29506ae89233bbce4e9 Mon Sep 17 00:00:00 2001 From: Aditya Sirish A Yelgundhalli Date: Wed, 10 Sep 2025 02:34:03 +0000 Subject: [PATCH 110/140] debug/elf: make check for empty symbol section consistent for 64-bit and 32-bit binaries The check for whether a binary's symbols section is empty is inconsistent across the 32-bit and 64-bit flows. Change-Id: I1abc235320a53cf957cfb83c9e7bcad6e52bc529 GitHub-Last-Rev: f264915ca2964ad8f34ce1deee4f42c2f9dc21bf GitHub-Pull-Request: golang/go#75334 Reviewed-on: https://go-review.googlesource.com/c/go/+/702195 LUCI-TryBot-Result: Go LUCI Auto-Submit: Ian Lance Taylor Reviewed-by: Dmitri Shuralyov Reviewed-by: Ian Lance Taylor Reviewed-by: Keith Randall --- src/debug/elf/file.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/debug/elf/file.go b/src/debug/elf/file.go index 1d56a06c3fb..1fc10a56869 100644 --- a/src/debug/elf/file.go +++ b/src/debug/elf/file.go @@ -641,7 +641,7 @@ func (f *File) getSymbols32(typ SectionType) ([]Symbol, []byte, error) { return nil, nil, fmt.Errorf("cannot load symbol section: %w", err) } if len(data) == 0 { - return nil, nil, errors.New("symbol section is empty") + return nil, nil, ErrNoSymbols } if len(data)%Sym32Size != 0 { return nil, nil, errors.New("length of symbol section is not a multiple of SymSize") @@ -690,12 +690,12 @@ func (f *File) getSymbols64(typ SectionType) ([]Symbol, []byte, error) { if err != nil { return nil, nil, fmt.Errorf("cannot load symbol section: %w", err) } - if len(data)%Sym64Size != 0 { - return nil, nil, errors.New("length of symbol section is not a multiple of Sym64Size") - } if len(data) == 0 { return nil, nil, ErrNoSymbols } + if len(data)%Sym64Size != 0 { + return nil, nil, errors.New("length of symbol section is not a multiple of Sym64Size") + } strdata, err := f.stringTable(symtabSection.Link) if err != nil { From 1555fad47ddfe149ae5f2eb593124fd7371968a9 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Mon, 1 Dec 2025 10:26:29 -0500 Subject: [PATCH 111/140] vendor/golang.org/x/tools: update to 1ad6f3d cmd$ go get golang.org/x/tools@1ad6f3d cmd$ GOWORK=off go mod tidy cmd$ GOWORK=off go mod vendor This merge pulls in the following commits, which include several fixes needed for go1.26, marked by an asterisk. None of the unmarked commits affects vendored packages, so it is safe (and simpler) to merge rather than cherrypick via a release branch. tools$ git log --oneline 68724afed209...1ad6f3d02713 *4a3f2f81eb go/analysis/passes/printf: panic when function literal is assigned to the blank identifier *d5d7d21fe7 gopls/internal/cache: fix %q verb use with wrong type *92a094998a go/analysis/passes/modernize: rangeint: handle usages of loop label *ffbdcac342 go/analysis/passes/modernize: stditerators: add reflect iters *2e3e83a050 internal/refactor/inline: preserve local package name used by callee d32ec34454 gopls/internal/protocol/generate: move injections to tables.go 98d172d8bd gopls/internal/protocol: add form field in type CodeAction e1317381e4 go/packages: suppress test on (e.g.) wasm *e31ed53b51 internal/stdlib: regenerate *6f1f89817d internal/analysis/driverutil: include end positions in -json output 7839abf5e8 gopls/internal/metadata: document when Module can be nil 98aa9a7d0b gopls/internal/cache: make unimported completions deterministic 4c5faddb0f internal/modindex: unescape import paths c2c902c441 gopls/completion: avoid nil dereference *4bf3169c8a go/analysis/passes/modernize: waitgroup: highlight "go func" part ba5189b063 gopls/internal/template: fix printf mistake in test *a7d12506a0 go/analysis/passes/printf: clarify checkForward c7a1a29f93 internal/pkgbits: fix printf mistake in test af205c0a29 gopls/doc/release/v0.21.0.md: tweaks Change-Id: I23c991987afeb2db3e0f98f76f8ee5000c8a6e02 Reviewed-on: https://go-review.googlesource.com/c/go/+/725460 Auto-Submit: Alan Donovan TryBot-Bypass: Alan Donovan Reviewed-by: Dmitri Shuralyov Reviewed-by: Dmitri Shuralyov Commit-Queue: Alan Donovan --- src/cmd/go.mod | 2 +- src/cmd/go.sum | 4 +- .../tools/go/analysis/passes/inline/inline.go | 29 +- .../go/analysis/passes/modernize/forvar.go | 2 +- .../go/analysis/passes/modernize/rangeint.go | 17 +- .../analysis/passes/modernize/stditerators.go | 68 +- .../go/analysis/passes/modernize/waitgroup.go | 6 +- .../tools/go/analysis/passes/printf/printf.go | 133 ++-- .../tools/internal/analysis/driverutil/fix.go | 7 + .../internal/analysis/driverutil/print.go | 9 +- .../x/tools/internal/refactor/delete.go | 39 +- .../x/tools/internal/refactor/edit.go | 15 + .../x/tools/internal/refactor/imports.go | 58 +- .../tools/internal/refactor/inline/inline.go | 404 ++++------- .../x/tools/internal/refactor/refactor.go | 3 +- .../x/tools/internal/stdlib/deps.go | 626 +++++++++--------- .../x/tools/internal/stdlib/manifest.go | 549 ++++++++++++++- .../x/tools/internal/stdlib/stdlib.go | 2 +- src/cmd/vendor/modules.txt | 2 +- 19 files changed, 1210 insertions(+), 765 deletions(-) create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/refactor/edit.go diff --git a/src/cmd/go.mod b/src/cmd/go.mod index 3915c16da33..a23387699df 100644 --- a/src/cmd/go.mod +++ b/src/cmd/go.mod @@ -11,7 +11,7 @@ require ( golang.org/x/sys v0.38.0 golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 golang.org/x/term v0.34.0 - golang.org/x/tools v0.39.1-0.20251120214200-68724afed209 + golang.org/x/tools v0.39.1-0.20251130212600-1ad6f3d02713 ) require ( diff --git a/src/cmd/go.sum b/src/cmd/go.sum index 100ea28a7fe..5a49e61a4a3 100644 --- a/src/cmd/go.sum +++ b/src/cmd/go.sum @@ -22,7 +22,7 @@ golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -golang.org/x/tools v0.39.1-0.20251120214200-68724afed209 h1:BGuEUnbWU1H+VhF4Z52lwCvzRT8Q/Z7kJC3okSME58w= -golang.org/x/tools v0.39.1-0.20251120214200-68724afed209/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.39.1-0.20251130212600-1ad6f3d02713 h1:i4GzAuZW4RuKXltwKyLYAfk7E1TSKQBxRAI7XKfLjSk= +golang.org/x/tools v0.39.1-0.20251130212600-1ad6f3d02713/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8= rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ= diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go index c0b75202589..9049145e225 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go @@ -7,7 +7,6 @@ package inline import ( "fmt" "go/ast" - "go/token" "go/types" "slices" "strings" @@ -23,7 +22,6 @@ import ( "golang.org/x/tools/internal/analysis/analyzerutil" typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex" "golang.org/x/tools/internal/astutil" - "golang.org/x/tools/internal/diff" "golang.org/x/tools/internal/moreiters" "golang.org/x/tools/internal/packagepath" "golang.org/x/tools/internal/refactor" @@ -204,19 +202,12 @@ func (a *analyzer) inlineCall(call *ast.CallExpr, cur inspector.Cursor) { var edits []analysis.TextEdit if !lazyEdits { // Inline the call. - content, err := a.readFile(call) - if err != nil { - a.pass.Reportf(call.Lparen, "invalid inlining candidate: cannot read source file: %v", err) - return - } - curFile := astutil.EnclosingFile(cur) caller := &inline.Caller{ - Fset: a.pass.Fset, - Types: a.pass.Pkg, - Info: a.pass.TypesInfo, - File: curFile, - Call: call, - Content: content, + Fset: a.pass.Fset, + Types: a.pass.Pkg, + Info: a.pass.TypesInfo, + File: astutil.EnclosingFile(cur), + Call: call, CountUses: func(pkgname *types.PkgName) int { return moreiters.Len(a.index.Uses(pkgname)) }, @@ -245,15 +236,7 @@ func (a *analyzer) inlineCall(call *ast.CallExpr, cur inspector.Cursor) { // The flag allows them to decline such fixes. return } - got := res.Content - - for _, edit := range diff.Bytes(content, got) { - edits = append(edits, analysis.TextEdit{ - Pos: curFile.FileStart + token.Pos(edit.Start), - End: curFile.FileStart + token.Pos(edit.End), - NewText: []byte(edit.New), - }) - } + edits = res.Edits } a.pass.Report(analysis.Diagnostic{ diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/forvar.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/forvar.go index 67f60acaaf3..ba54daebbfc 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/forvar.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/forvar.go @@ -35,7 +35,7 @@ var ForVarAnalyzer = &analysis.Analyzer{ // where the two idents are the same, // and the ident is defined (:=) as a variable in the for statement. // (Note that this 'fix' does not work for three clause loops -// because the Go specfilesUsingGoVersionsays "The variable used by each subsequent iteration +// because the Go spec says "The variable used by each subsequent iteration // is declared implicitly before executing the post statement and initialized to the // value of the previous iteration's variable at that moment.") // diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go index 6b1edf38b37..c42ec58ec3a 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go @@ -161,7 +161,22 @@ func rangeint(pass *analysis.Pass) (any, error) { // don't offer a fix, as a range loop // leaves i with a different final value (limit-1). if init.Tok == token.ASSIGN { - for curId := range curLoop.Parent().Preorder((*ast.Ident)(nil)) { + // Find the nearest ancestor that is not a label. + // Otherwise, checking for i usage outside of a for + // loop might not function properly further below. + // This is because the i usage might be a child of + // the loop's parent's parent, for example: + // var i int + // Loop: + // for i = 0; i < 10; i++ { break loop } + // // i is in the sibling of the label, not the loop + // fmt.Println(i) + // + ancestor := curLoop.Parent() + for is[*ast.LabeledStmt](ancestor.Node()) { + ancestor = ancestor.Parent() + } + for curId := range ancestor.Preorder((*ast.Ident)(nil)) { id := curId.Node().(*ast.Ident) if info.Uses[id] == v { // Is i used after loop? diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go index cc595806714..f7318b123da 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go @@ -43,23 +43,29 @@ func init() { // iter.Seq. var stditeratorsTable = [...]struct { pkgpath, typename, lenmethod, atmethod, itermethod, elemname string + + seqn int // 1 or 2 => "for x" or "for _, x" }{ // Example: in go/types, (*Tuple).Variables returns an // iterator that replaces a loop over (*Tuple).{Len,At}. // The loop variable is named "v". - {"go/types", "Interface", "NumEmbeddeds", "EmbeddedType", "EmbeddedTypes", "etyp"}, - {"go/types", "Interface", "NumExplicitMethods", "ExplicitMethod", "ExplicitMethods", "method"}, - {"go/types", "Interface", "NumMethods", "Method", "Methods", "method"}, - {"go/types", "MethodSet", "Len", "At", "Methods", "method"}, - {"go/types", "Named", "NumMethods", "Method", "Methods", "method"}, - {"go/types", "Scope", "NumChildren", "Child", "Children", "child"}, - {"go/types", "Struct", "NumFields", "Field", "Fields", "field"}, - {"go/types", "Tuple", "Len", "At", "Variables", "v"}, - {"go/types", "TypeList", "Len", "At", "Types", "t"}, - {"go/types", "TypeParamList", "Len", "At", "TypeParams", "tparam"}, - {"go/types", "Union", "Len", "Term", "Terms", "term"}, - // TODO(adonovan): support Seq2. Bonus: transform uses of both key and value. - // {"reflect", "Value", "NumFields", "Field", "Fields", "field"}, + {"go/types", "Interface", "NumEmbeddeds", "EmbeddedType", "EmbeddedTypes", "etyp", 1}, + {"go/types", "Interface", "NumExplicitMethods", "ExplicitMethod", "ExplicitMethods", "method", 1}, + {"go/types", "Interface", "NumMethods", "Method", "Methods", "method", 1}, + {"go/types", "MethodSet", "Len", "At", "Methods", "method", 1}, + {"go/types", "Named", "NumMethods", "Method", "Methods", "method", 1}, + {"go/types", "Scope", "NumChildren", "Child", "Children", "child", 1}, + {"go/types", "Struct", "NumFields", "Field", "Fields", "field", 1}, + {"go/types", "Tuple", "Len", "At", "Variables", "v", 1}, + {"go/types", "TypeList", "Len", "At", "Types", "t", 1}, + {"go/types", "TypeParamList", "Len", "At", "TypeParams", "tparam", 1}, + {"go/types", "Union", "Len", "Term", "Terms", "term", 1}, + {"reflect", "Type", "NumField", "Field", "Fields", "field", 1}, + {"reflect", "Type", "NumMethod", "Method", "Methods", "method", 1}, + {"reflect", "Type", "NumIn", "In", "Ins", "in", 1}, + {"reflect", "Type", "NumOut", "Out", "Outs", "out", 1}, + {"reflect", "Value", "NumField", "Field", "Fields", "field", 2}, + {"reflect", "Value", "NumMethod", "Method", "Methods", "method", 2}, } // stditerators suggests fixes to replace loops using Len/At-style @@ -86,6 +92,19 @@ var stditeratorsTable = [...]struct { // the user hasn't intentionally chosen not to use an // iterator for that reason? We don't want to go fix to // undo optimizations. Do we need a suppression mechanism? +// +// TODO(adonovan): recognize the more complex patterns that +// could make full use of both components of an iter.Seq2, e.g. +// +// for i := 0; i < v.NumField(); i++ { +// use(v.Field(i), v.Type().Field(i)) +// } +// +// => +// +// for structField, field := range v.Fields() { +// use(structField, field) +// } func stditerators(pass *analysis.Pass) (any, error) { var ( index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index) @@ -228,15 +247,17 @@ func stditerators(pass *analysis.Pass) (any, error) { indexVar = v curBody = curFor.ChildAt(edge.ForStmt_Body, -1) elem, elemVar = chooseName(curBody, lenSel.X, indexVar) + elemPrefix := cond(row.seqn == 2, "_, ", "") - // for i := 0; i < x.Len(); i++ { - // ---- ------- --- ----- - // for elem := range x.All() { + // for i := 0; i < x.Len(); i++ { + // ---- ------- --- ----- + // for elem := range x.All() { + // or for _, elem := ... edits = []analysis.TextEdit{ { Pos: v.Pos(), End: v.Pos() + token.Pos(len(v.Name())), - NewText: []byte(elem), + NewText: []byte(elemPrefix + elem), }, { Pos: loop.Init.(*ast.AssignStmt).Rhs[0].Pos(), @@ -271,6 +292,7 @@ func stditerators(pass *analysis.Pass) (any, error) { indexVar = info.Defs[id].(*types.Var) curBody = curRange.ChildAt(edge.RangeStmt_Body, -1) elem, elemVar = chooseName(curBody, lenSel.X, indexVar) + elemPrefix := cond(row.seqn == 2, "_, ", "") // for i := range x.Len() { // ---- --- @@ -279,7 +301,7 @@ func stditerators(pass *analysis.Pass) (any, error) { { Pos: loop.Key.Pos(), End: loop.Key.End(), - NewText: []byte(elem), + NewText: []byte(elemPrefix + elem), }, { Pos: lenSel.Sel.Pos(), @@ -344,8 +366,8 @@ func stditerators(pass *analysis.Pass) (any, error) { // (In the long run, version filters are not highly selective, // so there's no need to do them first, especially as this check // may be somewhat expensive.) - if v, ok := methodGoVersion(row.pkgpath, row.typename, row.itermethod); !ok { - panic("no version found") + if v, err := methodGoVersion(row.pkgpath, row.typename, row.itermethod); err != nil { + panic(err) } else if !analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(curLenCall), v.String()) { continue nextCall } @@ -371,7 +393,7 @@ func stditerators(pass *analysis.Pass) (any, error) { // methodGoVersion reports the version at which the method // (pkgpath.recvtype).method appeared in the standard library. -func methodGoVersion(pkgpath, recvtype, method string) (stdlib.Version, bool) { +func methodGoVersion(pkgpath, recvtype, method string) (stdlib.Version, error) { // TODO(adonovan): opt: this might be inefficient for large packages // like go/types. If so, memoize using a map (and kill two birds with // one stone by also memoizing the 'within' check above). @@ -379,9 +401,9 @@ func methodGoVersion(pkgpath, recvtype, method string) (stdlib.Version, bool) { if sym.Kind == stdlib.Method { _, recv, name := sym.SplitMethod() if recv == recvtype && name == method { - return sym.Version, true + return sym.Version, nil } } } - return 0, false + return 0, fmt.Errorf("methodGoVersion: %s.%s.%s missing from stdlib manifest", pkgpath, recvtype, method) } diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroup.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroup.go index 19564c69b60..abf5885cee2 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroup.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroup.go @@ -137,8 +137,10 @@ func waitgroup(pass *analysis.Pass) (any, error) { } pass.Report(analysis.Diagnostic{ - Pos: addCall.Pos(), - End: goStmt.End(), + // go func() { + // ~~~~~~~~~ + Pos: goStmt.Pos(), + End: lit.Type.End(), Message: "Goroutine creation can be simplified using WaitGroup.Go", SuggestedFixes: []analysis.SuggestedFix{{ Message: "Simplify by using WaitGroup.Go", diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go index fd9fe164723..1afb07c452b 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go @@ -137,6 +137,7 @@ type wrapper struct { callers []printfCaller } +// printfCaller is a candidate print{,f} forwarding call from candidate wrapper w. type printfCaller struct { w *wrapper call *ast.CallExpr // forwarding call (nil for implicit interface method -> impl calls) @@ -246,7 +247,7 @@ func findPrintLike(pass *analysis.Pass, res *Result) { switch lhs := lhs.(type) { case *ast.Ident: // variable: wrapf = func(...) - v = info.ObjectOf(lhs).(*types.Var) + v, _ = info.ObjectOf(lhs).(*types.Var) case *ast.SelectorExpr: if sel, ok := info.Selections[lhs]; ok { // struct field: x.wrapf = func(...) @@ -291,35 +292,35 @@ func findPrintLike(pass *analysis.Pass, res *Result) { // var _ Logger = myLogger{} impls := methodImplementations(pass) + // doCall records a call from one wrapper to another. + doCall := func(w *wrapper, callee types.Object, call *ast.CallExpr) { + // Call from one wrapper candidate to another? + // Record the edge so that if callee is found to be + // a true wrapper, w will be too. + if w2, ok := byObj[callee]; ok { + w2.callers = append(w2.callers, printfCaller{w, call}) + } + + // Is the candidate a true wrapper, because it calls + // a known print{,f}-like function from the allowlist + // or an imported fact, or another wrapper found + // to be a true wrapper? + // If so, convert all w's callers to kind. + kind := callKind(pass, callee, res) + if kind != KindNone { + propagate(pass, w, call, kind, res) + } + } + // Pass 2: scan the body of each wrapper function // for calls to other printf-like functions. for _, w := range wrappers { - // doCall records a call from one wrapper to another. - doCall := func(callee types.Object, call *ast.CallExpr) { - // Call from one wrapper candidate to another? - // Record the edge so that if callee is found to be - // a true wrapper, w will be too. - if w2, ok := byObj[callee]; ok { - w2.callers = append(w2.callers, printfCaller{w, call}) - } - - // Is the candidate a true wrapper, because it calls - // a known print{,f}-like function from the allowlist - // or an imported fact, or another wrapper found - // to be a true wrapper? - // If so, convert all w's callers to kind. - kind := callKind(pass, callee, res) - if kind != KindNone { - checkForward(pass, w, call, kind, res) - } - } - // An interface method has no body, but acts // like an implicit call to each implementing method. if w.curBody.Inspector() == nil { for impl := range impls[w.obj.(*types.Func)] { - doCall(impl, nil) + doCall(w, impl, nil) } continue // (no body) } @@ -360,7 +361,7 @@ func findPrintLike(pass *analysis.Pass, res *Result) { case *ast.CallExpr: if len(n.Args) > 0 && match(info, n.Args[len(n.Args)-1], w.args) { if callee := typeutil.Callee(pass.TypesInfo, n); callee != nil { - doCall(callee, n) + doCall(w, callee, n) } } } @@ -414,44 +415,15 @@ func match(info *types.Info, arg ast.Expr, param *types.Var) bool { return ok && info.ObjectOf(id) == param } -// checkForward checks whether a forwarding wrapper is forwarding correctly. -// If so, it propagates changes in wrapper kind information backwards -// through through the wrapper.callers graph of forwarding calls. -// -// If not, it reports a diagnostic that the user wrote -// fmt.Printf(format, args) instead of fmt.Printf(format, args...). -func checkForward(pass *analysis.Pass, w *wrapper, call *ast.CallExpr, kind Kind, res *Result) { +// propagate propagates changes in wrapper (non-None) kind information backwards +// through through the wrapper.callers graph of well-formed forwarding calls. +func propagate(pass *analysis.Pass, w *wrapper, call *ast.CallExpr, kind Kind, res *Result) { // Check correct call forwarding. - // (Interface methods forward correctly by construction.) - if call != nil { - matched := kind == KindPrint || - kind != KindNone && len(call.Args) >= 2 && match(pass.TypesInfo, call.Args[len(call.Args)-2], w.format) - if !matched { - return - } - - if !call.Ellipsis.IsValid() { - typ, ok := pass.TypesInfo.Types[call.Fun].Type.(*types.Signature) - if !ok { - return - } - if len(call.Args) > typ.Params().Len() { - // If we're passing more arguments than what the - // print/printf function can take, adding an ellipsis - // would break the program. For example: - // - // func foo(arg1 string, arg2 ...interface{}) { - // fmt.Printf("%s %v", arg1, arg2) - // } - return - } - desc := "printf" - if kind == KindPrint { - desc = "print" - } - pass.ReportRangef(call, "missing ... in args forwarded to %s-like function", desc) - return - } + // + // Interface methods (call==nil) forward + // correctly by construction. + if call != nil && !checkForward(pass, w, call, kind) { + return } // If the candidate's print{,f} status becomes known, @@ -471,11 +443,50 @@ func checkForward(pass *analysis.Pass, w *wrapper, call *ast.CallExpr, kind Kind // Propagate kind back to known callers. for _, caller := range w.callers { - checkForward(pass, caller.w, caller.call, kind, res) + propagate(pass, caller.w, caller.call, kind, res) } } } +// checkForward checks whether a call from wrapper w is a well-formed +// forwarding call of the specified (non-None) kind. +// +// If not, it reports a diagnostic that the user wrote +// fmt.Printf(format, args) instead of fmt.Printf(format, args...). +func checkForward(pass *analysis.Pass, w *wrapper, call *ast.CallExpr, kind Kind) bool { + // Printf/Errorf calls must delegate the format string. + switch kind { + case KindPrintf, KindErrorf: + if len(call.Args) < 2 || !match(pass.TypesInfo, call.Args[len(call.Args)-2], w.format) { + return false + } + } + + // The args... delegation must be variadic. + // (That args is actually delegated was + // established before the root call to doCall.) + if !call.Ellipsis.IsValid() { + typ, ok := pass.TypesInfo.Types[call.Fun].Type.(*types.Signature) + if !ok { + return false + } + if len(call.Args) > typ.Params().Len() { + // If we're passing more arguments than what the + // print/printf function can take, adding an ellipsis + // would break the program. For example: + // + // func foo(arg1 string, arg2 ...interface{}) { + // fmt.Printf("%s %v", arg1, arg2) + // } + return false + } + pass.ReportRangef(call, "missing ... in args forwarded to %s-like function", kind) + return false + } + + return true +} + func origin(obj types.Object) types.Object { switch obj := obj.(type) { case *types.Func: diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/fix.go b/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/fix.go index ef06cf9bde2..37b09588a7a 100644 --- a/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/fix.go +++ b/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/fix.go @@ -339,6 +339,9 @@ fixloop: // information for the fixed file and thus cannot accurately tell // whether k is among the free names of T{k: 0}, which requires // knowledge of whether T is a struct type. +// +// Like [imports.Process] (the core of x/tools/cmd/goimports), it also +// merges import decls. func FormatSourceRemoveImports(pkg *types.Package, src []byte) ([]byte, error) { // This function was reduced from the "strict entire file" // path through [format.Source]. @@ -353,6 +356,10 @@ func FormatSourceRemoveImports(pkg *types.Package, src []byte) ([]byte, error) { removeUnneededImports(fset, pkg, file) + // TODO(adonovan): to generate cleaner edits when adding an import, + // consider adding a call to imports.mergeImports; however, it does + // cause comments to migrate. + // printerNormalizeNumbers means to canonicalize number literal prefixes // and exponents while printing. See https://golang.org/doc/go1.13#gofmt. // diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/print.go b/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/print.go index 7fc42a5ef7b..5458846857d 100644 --- a/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/print.go +++ b/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/print.go @@ -7,6 +7,7 @@ package driverutil // This file defined output helpers common to all drivers. import ( + "cmp" "encoding/json" "fmt" "go/token" @@ -76,11 +77,10 @@ type JSONSuggestedFix struct { } // A JSONDiagnostic describes the JSON schema of an analysis.Diagnostic. -// -// TODO(matloob): include End position if present. type JSONDiagnostic struct { Category string `json:"category,omitempty"` Posn string `json:"posn"` // e.g. "file.go:line:column" + End string `json:"end"` // (ditto) Message string `json:"message"` SuggestedFixes []JSONSuggestedFix `json:"suggested_fixes,omitempty"` Related []JSONRelatedInformation `json:"related,omitempty"` @@ -88,10 +88,9 @@ type JSONDiagnostic struct { // A JSONRelated describes a secondary position and message related to // a primary diagnostic. -// -// TODO(adonovan): include End position if present. type JSONRelatedInformation struct { Posn string `json:"posn"` // e.g. "file.go:line:column" + End string `json:"end"` // (ditto) Message string `json:"message"` } @@ -127,12 +126,14 @@ func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis. for _, r := range f.Related { related = append(related, JSONRelatedInformation{ Posn: fset.Position(r.Pos).String(), + End: fset.Position(cmp.Or(r.End, r.Pos)).String(), Message: r.Message, }) } jdiag := JSONDiagnostic{ Category: f.Category, Posn: fset.Position(f.Pos).String(), + End: fset.Position(cmp.Or(f.End, f.Pos)).String(), Message: f.Message, SuggestedFixes: fixes, Related: related, diff --git a/src/cmd/vendor/golang.org/x/tools/internal/refactor/delete.go b/src/cmd/vendor/golang.org/x/tools/internal/refactor/delete.go index 9b96b1dbf1f..54d0b5f0386 100644 --- a/src/cmd/vendor/golang.org/x/tools/internal/refactor/delete.go +++ b/src/cmd/vendor/golang.org/x/tools/internal/refactor/delete.go @@ -13,7 +13,6 @@ import ( "go/types" "slices" - "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/edge" "golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/internal/astutil" @@ -32,7 +31,7 @@ import ( // // If it cannot make the necessary edits, such as for a function // parameter or result, it returns nil. -func DeleteVar(tokFile *token.File, info *types.Info, curId inspector.Cursor) []analysis.TextEdit { +func DeleteVar(tokFile *token.File, info *types.Info, curId inspector.Cursor) []Edit { switch ek, _ := curId.ParentEdge(); ek { case edge.ValueSpec_Names: return deleteVarFromValueSpec(tokFile, info, curId) @@ -52,7 +51,7 @@ func DeleteVar(tokFile *token.File, info *types.Info, curId inspector.Cursor) [] // Precondition: curId is Ident beneath ValueSpec.Names beneath GenDecl. // // See also [deleteVarFromAssignStmt], which has parallel structure. -func deleteVarFromValueSpec(tokFile *token.File, info *types.Info, curIdent inspector.Cursor) []analysis.TextEdit { +func deleteVarFromValueSpec(tokFile *token.File, info *types.Info, curIdent inspector.Cursor) []Edit { var ( id = curIdent.Node().(*ast.Ident) curSpec = curIdent.Parent() @@ -95,7 +94,7 @@ func deleteVarFromValueSpec(tokFile *token.File, info *types.Info, curIdent insp pos = spec.Names[index].Pos() end = spec.Names[index+1].Pos() } - return []analysis.TextEdit{{ + return []Edit{{ Pos: pos, End: end, }} @@ -111,7 +110,7 @@ func deleteVarFromValueSpec(tokFile *token.File, info *types.Info, curIdent insp // // var _, lhs1 = rhs0, rhs1 // ------ ------ - return []analysis.TextEdit{ + return []Edit{ { Pos: spec.Names[index-1].End(), End: spec.Names[index].End(), @@ -126,7 +125,7 @@ func deleteVarFromValueSpec(tokFile *token.File, info *types.Info, curIdent insp // // var lhs0, _ = rhs0, rhs1 // ------ ------ - return []analysis.TextEdit{ + return []Edit{ { Pos: spec.Names[index].Pos(), End: spec.Names[index+1].Pos(), @@ -141,7 +140,7 @@ func deleteVarFromValueSpec(tokFile *token.File, info *types.Info, curIdent insp // We cannot delete the RHS. // Blank out the LHS. - return []analysis.TextEdit{{ + return []Edit{{ Pos: id.Pos(), End: id.End(), NewText: []byte("_"), @@ -151,7 +150,7 @@ func deleteVarFromValueSpec(tokFile *token.File, info *types.Info, curIdent insp // Precondition: curId is Ident beneath AssignStmt.Lhs. // // See also [deleteVarFromValueSpec], which has parallel structure. -func deleteVarFromAssignStmt(tokFile *token.File, info *types.Info, curIdent inspector.Cursor) []analysis.TextEdit { +func deleteVarFromAssignStmt(tokFile *token.File, info *types.Info, curIdent inspector.Cursor) []Edit { var ( id = curIdent.Node().(*ast.Ident) curStmt = curIdent.Parent() @@ -192,7 +191,7 @@ func deleteVarFromAssignStmt(tokFile *token.File, info *types.Info, curIdent ins // // _, lhs1 := rhs0, rhs1 // ------ ------ - return []analysis.TextEdit{ + return []Edit{ { Pos: assign.Lhs[index-1].End(), End: assign.Lhs[index].End(), @@ -207,7 +206,7 @@ func deleteVarFromAssignStmt(tokFile *token.File, info *types.Info, curIdent ins // // lhs0, _ := rhs0, rhs1 // ------ ------ - return []analysis.TextEdit{ + return []Edit{ { Pos: assign.Lhs[index].Pos(), End: assign.Lhs[index+1].Pos(), @@ -222,7 +221,7 @@ func deleteVarFromAssignStmt(tokFile *token.File, info *types.Info, curIdent ins // We cannot delete the RHS. // Blank out the LHS. - edits := []analysis.TextEdit{{ + edits := []Edit{{ Pos: id.Pos(), End: id.End(), NewText: []byte("_"), @@ -233,7 +232,7 @@ func deleteVarFromAssignStmt(tokFile *token.File, info *types.Info, curIdent ins // assignment to avoid a "no new variables on left // side of :=" error. if !declaresOtherNames { - edits = append(edits, analysis.TextEdit{ + edits = append(edits, Edit{ Pos: assign.TokPos, End: assign.TokPos + token.Pos(len(":=")), NewText: []byte("="), @@ -246,7 +245,7 @@ func deleteVarFromAssignStmt(tokFile *token.File, info *types.Info, curIdent ins // DeleteSpec returns edits to delete the {Type,Value}Spec identified by curSpec. // // TODO(adonovan): add test suite. Test for consts as well. -func DeleteSpec(tokFile *token.File, curSpec inspector.Cursor) []analysis.TextEdit { +func DeleteSpec(tokFile *token.File, curSpec inspector.Cursor) []Edit { var ( spec = curSpec.Node().(ast.Spec) curDecl = curSpec.Parent() @@ -277,7 +276,7 @@ func DeleteSpec(tokFile *token.File, curSpec inspector.Cursor) []analysis.TextEd // ----- end = decl.Specs[index+1].Pos() } - return []analysis.TextEdit{{ + return []Edit{{ Pos: pos, End: end, }} @@ -286,7 +285,7 @@ func DeleteSpec(tokFile *token.File, curSpec inspector.Cursor) []analysis.TextEd // DeleteDecl returns edits to delete the ast.Decl identified by curDecl. // // TODO(adonovan): add test suite. -func DeleteDecl(tokFile *token.File, curDecl inspector.Cursor) []analysis.TextEdit { +func DeleteDecl(tokFile *token.File, curDecl inspector.Cursor) []Edit { decl := curDecl.Node().(ast.Decl) ek, _ := curDecl.ParentEdge() @@ -321,7 +320,7 @@ func DeleteDecl(tokFile *token.File, curDecl inspector.Cursor) []analysis.TextEd } } - return []analysis.TextEdit{{ + return []Edit{{ Pos: pos, End: end, }} @@ -366,7 +365,7 @@ func filterPos(nds []*ast.Comment, start, end token.Pos) (token.Pos, token.Pos, // it removes whole lines like // // stmt // comment -func DeleteStmt(file *token.File, curStmt inspector.Cursor) []analysis.TextEdit { +func DeleteStmt(file *token.File, curStmt inspector.Cursor) []Edit { // if the stmt is on a line by itself, or a range of lines, delete the whole thing // including comments. Except for the heads of switches, type // switches, and for-statements that's the usual case. Complexity occurs where @@ -516,13 +515,13 @@ Big: } } - return []analysis.TextEdit{{Pos: leftEdit, End: rightEdit}} + return []Edit{{Pos: leftEdit, End: rightEdit}} } // DeleteUnusedVars computes the edits required to delete the // declarations of any local variables whose last uses are in the // curDelend subtree, which is about to be deleted. -func DeleteUnusedVars(index *typeindex.Index, info *types.Info, tokFile *token.File, curDelend inspector.Cursor) []analysis.TextEdit { +func DeleteUnusedVars(index *typeindex.Index, info *types.Info, tokFile *token.File, curDelend inspector.Cursor) []Edit { // TODO(adonovan): we might want to generalize this by // splitting the two phases below, so that we can gather // across a whole sequence of deletions then finally compute the @@ -539,7 +538,7 @@ func DeleteUnusedVars(index *typeindex.Index, info *types.Info, tokFile *token.F } // Delete declaration of each var that became unused. - var edits []analysis.TextEdit + var edits []Edit for v, count := range delcount { if len(slices.Collect(index.Uses(v))) == count { if curDefId, ok := index.Def(v); ok { diff --git a/src/cmd/vendor/golang.org/x/tools/internal/refactor/edit.go b/src/cmd/vendor/golang.org/x/tools/internal/refactor/edit.go new file mode 100644 index 00000000000..42be9a54b41 --- /dev/null +++ b/src/cmd/vendor/golang.org/x/tools/internal/refactor/edit.go @@ -0,0 +1,15 @@ +// Copyright 2025 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.p + +package refactor + +// This is the only file in this package that should import analysis. +// +// TODO(adonovan): consider unaliasing the type to break the +// dependency. (The ergonomics of slice append are unfortunate.) + +import "golang.org/x/tools/go/analysis" + +// An Edit describes a deletion and/or an insertion. +type Edit = analysis.TextEdit diff --git a/src/cmd/vendor/golang.org/x/tools/internal/refactor/imports.go b/src/cmd/vendor/golang.org/x/tools/internal/refactor/imports.go index b5440d896b9..e1860ab0659 100644 --- a/src/cmd/vendor/golang.org/x/tools/internal/refactor/imports.go +++ b/src/cmd/vendor/golang.org/x/tools/internal/refactor/imports.go @@ -7,13 +7,12 @@ package refactor // This file defines operations for computing edits to imports. import ( - "fmt" "go/ast" "go/token" "go/types" pathpkg "path" + "strconv" - "golang.org/x/tools/go/analysis" "golang.org/x/tools/internal/packagepath" ) @@ -35,7 +34,7 @@ import ( // package declares member. // // AddImport does not mutate its arguments. -func AddImport(info *types.Info, file *ast.File, preferredName, pkgpath, member string, pos token.Pos) (prefix string, edits []analysis.TextEdit) { +func AddImport(info *types.Info, file *ast.File, preferredName, pkgpath, member string, pos token.Pos) (prefix string, edits []Edit) { // Find innermost enclosing lexical block. scope := info.Scopes[file].Innermost(pos) if scope == nil { @@ -69,33 +68,53 @@ func AddImport(info *types.Info, file *ast.File, preferredName, pkgpath, member newName := preferredName if preferredName != "_" { newName = FreshName(scope, pos, preferredName) + prefix = newName + "." + } + + // Use a renaming import whenever the preferred name is not + // available, or the chosen name does not match the last + // segment of its path. + if newName == preferredName && newName == pathpkg.Base(pkgpath) { + newName = "" + } + + return prefix, AddImportEdits(file, newName, pkgpath) +} + +// AddImportEdits returns the edits to add an import of the specified +// package, without any analysis of whether this is necessary or safe. +// If name is nonempty, it is used as an explicit [ImportSpec.Name]. +// +// A sequence of calls to AddImportEdits that each add the file's +// first import (or in a file that does not have a grouped import) may +// result in multiple import declarations, rather than a single one +// with multiple ImportSpecs. However, a subsequent run of +// x/tools/cmd/goimports ([imports.Process]) will combine them. +// +// AddImportEdits does not mutate the AST. +func AddImportEdits(file *ast.File, name, pkgpath string) []Edit { + newText := strconv.Quote(pkgpath) + if name != "" { + newText = name + " " + newText } // Create a new import declaration either before the first existing // declaration (which must exist), including its comments; or // inside the declaration, if it is an import group. - // - // Use a renaming import whenever the preferred name is not - // available, or the chosen name does not match the last - // segment of its path. - newText := fmt.Sprintf("%q", pkgpath) - if newName != preferredName || newName != pathpkg.Base(pkgpath) { - newText = fmt.Sprintf("%s %q", newName, pkgpath) - } - decl0 := file.Decls[0] - var before ast.Node = decl0 + before := decl0.Pos() switch decl0 := decl0.(type) { case *ast.GenDecl: if decl0.Doc != nil { - before = decl0.Doc + before = decl0.Doc.Pos() } case *ast.FuncDecl: if decl0.Doc != nil { - before = decl0.Doc + before = decl0.Doc.Pos() } } - if gd, ok := before.(*ast.GenDecl); ok && gd.Tok == token.IMPORT && gd.Rparen.IsValid() { + var pos token.Pos + if gd, ok := decl0.(*ast.GenDecl); ok && gd.Tok == token.IMPORT && gd.Rparen.IsValid() { // Have existing grouped import ( ... ) decl. if packagepath.IsStdPackage(pkgpath) && len(gd.Specs) > 0 { // Add spec for a std package before @@ -116,10 +135,13 @@ func AddImport(info *types.Info, file *ast.File, preferredName, pkgpath, member // No import decl, or non-grouped import. // Add a new import decl before first decl. // (gofmt will merge multiple import decls.) - pos = before.Pos() + // + // TODO(adonovan): do better here; plunder the + // mergeImports logic from [imports.Process]. + pos = before newText = "import " + newText + "\n\n" } - return newName + ".", []analysis.TextEdit{{ + return []Edit{{ Pos: pos, End: pos, NewText: []byte(newText), diff --git a/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go b/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go index af1252cee86..f7e37fd7da8 100644 --- a/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go +++ b/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go @@ -11,14 +11,12 @@ import ( "go/constant" "go/format" "go/parser" - "go/printer" "go/token" "go/types" "maps" pathpkg "path" "reflect" "slices" - "strconv" "strings" "golang.org/x/tools/go/ast/astutil" @@ -26,6 +24,7 @@ import ( internalastutil "golang.org/x/tools/internal/astutil" "golang.org/x/tools/internal/astutil/free" "golang.org/x/tools/internal/packagepath" + "golang.org/x/tools/internal/refactor" "golang.org/x/tools/internal/typeparams" "golang.org/x/tools/internal/typesinternal" "golang.org/x/tools/internal/versions" @@ -35,12 +34,11 @@ import ( // // The client is responsible for populating this struct and passing it to Inline. type Caller struct { - Fset *token.FileSet - Types *types.Package - Info *types.Info - File *ast.File - Call *ast.CallExpr - Content []byte // source of file containing (TODO(adonovan): see comment at Result.Content) + Fset *token.FileSet + Types *types.Package + Info *types.Info + File *ast.File + Call *ast.CallExpr // CountUses is an optional optimized computation of // the number of times pkgname appears in Info.Uses. @@ -61,26 +59,9 @@ type Options struct { // Result holds the result of code transformation. type Result struct { - // TODO(adonovan): the only textual results that should be - // needed are (1) an edit in the vicinity of the call (either - // to the CallExpr or one of its ancestors), and optionally - // (2) an edit to the import declaration. - // Change the inliner API to return a list of edits, - // and not to accept a Caller.Content, as it is only - // temptation to use such algorithmically expensive - // operations as reformatting the entire file, which is - // a significant source of non-linear dynamic behavior; - // see https://go.dev/issue/75773. - // This will require a sequence of changes to the tests - // and the inliner algorithm itself. - Content []byte // formatted, transformed content of caller file - Literalized bool // chosen strategy replaced callee() with func(){...}() - BindingDecl bool // transformation added "var params = args" declaration - - // TODO(adonovan): provide an API for clients that want structured - // output: a list of import additions and deletions plus one or more - // localized diffs (or even AST transformations, though ownership and - // mutation are tricky) near the call site. + Edits []refactor.Edit // edits around CallExpr and imports + Literalized bool // chosen strategy replaced callee() with func(){...}() + BindingDecl bool // transformation added "var params = args" declaration } // Inline inlines the called function (callee) into the function call (caller) @@ -117,14 +98,8 @@ func (st *state) inline() (*Result, error) { debugFormatNode(caller.Fset, caller.Call), caller.Fset.PositionFor(caller.Call.Lparen, false)) - if !consistentOffsets(caller) { - return nil, fmt.Errorf("internal error: caller syntax positions are inconsistent with file content (did you forget to use FileSet.PositionFor when computing the file name?)") - } - - // Break the string literal so we can use inlining in this file. :) - if ast.IsGenerated(caller.File) && - bytes.Contains(caller.Content, []byte("// Code generated by "+"cmd/cgo; DO NOT EDIT.")) { - return nil, fmt.Errorf("cannot inline calls from files that import \"C\"") + if ast.IsGenerated(caller.File) { + return nil, fmt.Errorf("cannot inline calls from generated files") } res, err := st.inlineCall() @@ -224,37 +199,10 @@ func (st *state) inline() (*Result, error) { } } - // File rewriting. This proceeds in multiple passes, in order to maximally - // preserve comment positioning. (This could be greatly simplified once - // comments are stored in the tree.) - // - // Don't call replaceNode(caller.File, res.old, res.new) - // as it mutates the caller's syntax tree. - // Instead, splice the file, replacing the extent of the "old" - // node by a formatting of the "new" node, and re-parse. - // We'll fix up the imports on this new tree, and format again. - // - // Inv: f is the result of parsing content, using fset. - var ( - content = caller.Content - fset = caller.Fset - f *ast.File // parsed below - ) - reparse := func() error { - const mode = parser.ParseComments | parser.SkipObjectResolution | parser.AllErrors - f, err = parser.ParseFile(fset, "callee.go", content, mode) - if err != nil { - // Something has gone very wrong. - logf("failed to reparse <<%s>>: %v", string(content), err) // debugging - return err - } - return nil - } + var edits []refactor.Edit + + // Format the cloned callee. { - start := offsetOf(fset, res.old.Pos()) - end := offsetOf(fset, res.old.End()) - var out bytes.Buffer - out.Write(content[:start]) // TODO(adonovan): might it make more sense to use // callee.Fset when formatting res.new? // The new tree is a mix of (cloned) caller nodes for @@ -269,148 +217,106 @@ func (st *state) inline() (*Result, error) { // Precise comment handling would make this a // non-issue. Formatting wouldn't really need a // FileSet at all. + + var out bytes.Buffer if elideBraces { for i, stmt := range res.new.(*ast.BlockStmt).List { if i > 0 { out.WriteByte('\n') } - if err := format.Node(&out, fset, stmt); err != nil { + if err := format.Node(&out, caller.Fset, stmt); err != nil { return nil, err } } } else { - if err := format.Node(&out, fset, res.new); err != nil { + if err := format.Node(&out, caller.Fset, res.new); err != nil { return nil, err } } - out.Write(content[end:]) - content = out.Bytes() - if err := reparse(); err != nil { - return nil, err - } + + edits = append(edits, refactor.Edit{ + Pos: res.old.Pos(), + End: res.old.End(), + NewText: out.Bytes(), + }) } - // Add new imports that are still used. - newImports := trimNewImports(res.newImports, res.new) - // Insert new imports after last existing import, - // to avoid migration of pre-import comments. - // The imports will be organized below. - if len(newImports) > 0 { - // If we have imports to add, do so independent of the rest of the file. - // Otherwise, the length of the new imports may consume floating comments, - // causing them to be printed inside the imports block. - var ( - importDecl *ast.GenDecl - comments []*ast.CommentGroup // relevant comments. - before, after []byte // pre- and post-amble for the imports block. - ) - if len(f.Imports) > 0 { - // Append specs to existing import decl - importDecl = f.Decls[0].(*ast.GenDecl) - for _, comment := range f.Comments { - // Filter comments. Don't use CommentMap.Filter here, because we don't - // want to include comments that document the import decl itself, for - // example: - // - // // We don't want this comment to be duplicated. - // import ( - // "something" - // ) - if importDecl.Pos() <= comment.Pos() && comment.Pos() < importDecl.End() { - comments = append(comments, comment) - } - } - before = content[:offsetOf(fset, importDecl.Pos())] - importDecl.Doc = nil // present in before - after = content[offsetOf(fset, importDecl.End()):] - } else { - // Insert new import decl. - importDecl = &ast.GenDecl{Tok: token.IMPORT} - f.Decls = prepend[ast.Decl](importDecl, f.Decls...) - - // Make room for the new declaration after the package declaration. - pkgEnd := f.Name.End() - file := fset.File(pkgEnd) - if file == nil { - logf("internal error: missing pkg file") - return nil, fmt.Errorf("missing pkg file for %s", f.Name.Name) - } - // Preserve any comments after the package declaration, by splicing in - // the new import block after the end of the package declaration line. - line := file.Line(pkgEnd) - if line < len(file.Lines()) { // line numbers are 1-based - nextLinePos := file.LineStart(line + 1) - nextLine := offsetOf(fset, nextLinePos) - before = slices.Concat(content[:nextLine], []byte("\n")) - after = slices.Concat([]byte("\n\n"), content[nextLine:]) - } else { - before = slices.Concat(content, []byte("\n\n")) - } - } - // Add new imports. - // Set their position to after the last position of the old imports, to keep - // comments on the old imports from moving. - lastPos := token.NoPos - if lastSpec := last(importDecl.Specs); lastSpec != nil { - lastPos = lastSpec.Pos() - if c := lastSpec.(*ast.ImportSpec).Comment; c != nil { - lastPos = c.Pos() - } - } - for _, imp := range newImports { - // Check that the new imports are accessible. - path, _ := strconv.Unquote(imp.spec.Path.Value) - if !packagepath.CanImport(caller.Types.Path(), path) { - return nil, fmt.Errorf("can't inline function %v as its body refers to inaccessible package %q", callee, path) - } - if lastPos.IsValid() { - lastPos++ - imp.spec.Path.ValuePos = lastPos - } - importDecl.Specs = append(importDecl.Specs, imp.spec) + // Add new imports. + // + // It's possible that not all are needed (e.g. for type names + // that melted away), but we'll let the client (such as an + // analysis driver) clean it up since it must remove unused + // imports anyway. + for _, imp := range res.newImports { + // Check that the new imports are accessible. + if !packagepath.CanImport(caller.Types.Path(), imp.path) { + return nil, fmt.Errorf("can't inline function %v as its body refers to inaccessible package %q", callee, imp.path) } - var out bytes.Buffer - out.Write(before) - commented := &printer.CommentedNode{ - Node: importDecl, - Comments: comments, - } - - if err := format.Node(&out, fset, commented); err != nil { - logf("failed to format new importDecl: %v", err) // debugging - return nil, err - } - out.Write(after) - content = out.Bytes() - if err := reparse(); err != nil { - return nil, err - } - } - // Delete imports referenced only by caller.Call.Fun. - for _, oldImport := range res.oldImports { - specToDelete := oldImport.spec + // We've already validated the import, so we call + // AddImportEdits directly to compute the edit. name := "" - if specToDelete.Name != nil { - name = specToDelete.Name.Name + if imp.explicit { + name = imp.name } - path, _ := strconv.Unquote(specToDelete.Path.Value) - astutil.DeleteNamedImport(caller.Fset, f, name, path) + edits = append(edits, refactor.AddImportEdits(caller.File, name, imp.path)...) } - var out bytes.Buffer - if err := format.Node(&out, caller.Fset, f); err != nil { - return nil, err - } - newSrc := out.Bytes() - literalized := false if call, ok := res.new.(*ast.CallExpr); ok && is[*ast.FuncLit](call.Fun) { literalized = true } + // Delete imports referenced only by caller.Call.Fun. + // + // It's ambiguous to let the client (e.g. analysis driver) + // remove unneeded imports in this case because it is common + // to inlining a call from "dir1/a".F to "dir2/a".F, which + // leaves two imports of packages named 'a', both providing a.F. + // + // However, the only two import deletion tools at our disposal + // are astutil.DeleteNamedImport, which mutates the AST, and + // refactor.Delete{Spec,Decl}, which need a Cursor. So we need + // to reinvent the wheel here. + for _, oldImport := range res.oldImports { + spec := oldImport.spec + + // Include adjacent comments. + pos := spec.Pos() + if doc := spec.Doc; doc != nil { + pos = doc.Pos() + } + end := spec.End() + if doc := spec.Comment; doc != nil { + end = doc.End() + } + + // Find the enclosing import decl. + // If it's paren-less, we must delete it too. + for _, decl := range caller.File.Decls { + decl, ok := decl.(*ast.GenDecl) + if !(ok && decl.Tok == token.IMPORT) { + break // stop at first non-import decl + } + if internalastutil.NodeContainsPos(decl, spec.Pos()) && !decl.Rparen.IsValid() { + // Include adjacent comments. + pos = decl.Pos() + if doc := decl.Doc; doc != nil { + pos = doc.Pos() + } + end = decl.End() + break + } + } + + edits = append(edits, refactor.Edit{ + Pos: pos, + End: end, + }) + } + return &Result{ - Content: newSrc, + Edits: edits, Literalized: literalized, BindingDecl: res.bindingDecl, }, nil @@ -424,8 +330,9 @@ type oldImport struct { // A newImport is an import that will be added to the caller file. type newImport struct { - pkgName string - spec *ast.ImportSpec + name string + path string + explicit bool // use name as ImportSpec.Name } // importState tracks information about imports. @@ -526,16 +433,12 @@ func (i *importState) importName(pkgPath string, shadow shadowMap) string { return "" } -// localName returns the local name for a given imported package path, -// adding one if it doesn't exists. -func (i *importState) localName(pkgPath, pkgName string, shadow shadowMap) string { - // Does an import already exist that works in this shadowing context? - if name := i.importName(pkgPath, shadow); name != "" { - return name - } - +// findNewLocalName returns a new local package name to use in a particular shadowing context. +// It considers the existing local name used by the callee, or construct a new local name +// based on the package name. +func (i *importState) findNewLocalName(pkgName, calleePkgName string, shadow shadowMap) string { newlyAdded := func(name string) bool { - return slices.ContainsFunc(i.newImports, func(n newImport) bool { return n.pkgName == name }) + return slices.ContainsFunc(i.newImports, func(n newImport) bool { return n.name == name }) } // shadowedInCaller reports whether a candidate package name @@ -551,74 +454,44 @@ func (i *importState) localName(pkgPath, pkgName string, shadow shadowMap) strin // import added by callee // - // Choose local PkgName based on last segment of - // package path plus, if needed, a numeric suffix to - // ensure uniqueness. + // Try to preserve the local package name used by the callee first. + // + // If that is shadowed, choose a local package name based on last segment of + // package path plus, if needed, a numeric suffix to ensure uniqueness. // // "init" is not a legal PkgName. - // - // TODO(rfindley): is it worth preserving local package names for callee - // imports? Are they likely to be better or worse than the name we choose - // here? + if shadow[calleePkgName] == 0 && !shadowedInCaller(calleePkgName) && !newlyAdded(calleePkgName) && calleePkgName != "init" { + return calleePkgName + } + base := pkgName name := base for n := 0; shadow[name] != 0 || shadowedInCaller(name) || newlyAdded(name) || name == "init"; n++ { name = fmt.Sprintf("%s%d", base, n) } - i.logf("adding import %s %q", name, pkgPath) - spec := &ast.ImportSpec{ - Path: &ast.BasicLit{ - Kind: token.STRING, - Value: strconv.Quote(pkgPath), - }, - } - // Use explicit pkgname (out of necessity) when it differs from the declared name, - // or (for good style) when it differs from base(pkgpath). - if name != pkgName || name != pathpkg.Base(pkgPath) { - spec.Name = makeIdent(name) - } - i.newImports = append(i.newImports, newImport{ - pkgName: name, - spec: spec, - }) - i.importMap[pkgPath] = append(i.importMap[pkgPath], name) + return name } -// trimNewImports removes imports that are no longer needed. -// -// The list of new imports as constructed by calls to [importState.localName] -// includes all of the packages referenced by the callee. -// But in the process of inlining, we may have dropped some of those references. -// For example, if the callee looked like this: -// -// func F(x int) (p.T) {... /* no mention of p */ ...} -// -// and we inlined by assignment: -// -// v := ... -// -// then the reference to package p drops away. -// -// Remove the excess imports by seeing which remain in new, the expression -// to be inlined. -// We can find those by looking at the free names in new. -// The list of free names cannot include spurious package names. -// Free-name tracking is precise except for the case of an identifier -// key in a composite literal, which names either a field or a value. -// Neither fields nor values are package names. -// Since they are not relevant to removing unused imports, we instruct -// freeishNames to omit composite-literal keys that are identifiers. -func trimNewImports(newImports []newImport, new ast.Node) []newImport { - const omitComplitIdents = false - free := free.Names(new, omitComplitIdents) - var res []newImport - for _, ni := range newImports { - if free[ni.pkgName] { - res = append(res, ni) - } +// localName returns the local name for a given imported package path, +// adding one if it doesn't exists. +func (i *importState) localName(pkgPath, pkgName, calleePkgName string, shadow shadowMap) string { + // Does an import already exist that works in this shadowing context? + if name := i.importName(pkgPath, shadow); name != "" { + return name } - return res + + name := i.findNewLocalName(pkgName, calleePkgName, shadow) + i.logf("adding import %s %q", name, pkgPath) + // Use explicit pkgname (out of necessity) when it differs from the declared name, + // or (for good style) when it differs from base(pkgpath). + i.newImports = append(i.newImports, newImport{ + name: name, + path: pkgPath, + explicit: name != pkgName || name != pathpkg.Base(pkgPath), + }) + i.importMap[pkgPath] = append(i.importMap[pkgPath], name) + return name } type inlineCallResult struct { @@ -655,14 +528,6 @@ type inlineCallResult struct { // allows inlining a statement list. However, due to loss of comments, more // sophisticated rewrites are challenging. // -// TODO(adonovan): in earlier drafts, the transformation was expressed -// by splicing substrings of the two source files because syntax -// trees don't preserve comments faithfully (see #20744), but such -// transformations don't compose. The current implementation is -// tree-based but is very lossy wrt comments. It would make a good -// candidate for evaluating an alternative fully self-contained tree -// representation, such as any proposed solution to #20744, or even -// dst or some private fork of go/ast.) // TODO(rfindley): see if we can reduce the amount of comment lossiness by // using printer.CommentedNode, which has been useful elsewhere. // @@ -1381,7 +1246,7 @@ func (st *state) renameFreeObjs(istate *importState) ([]ast.Expr, error) { var newName ast.Expr if obj.Kind == "pkgname" { // Use locally appropriate import, creating as needed. - n := istate.localName(obj.PkgPath, obj.PkgName, obj.Shadow) + n := istate.localName(obj.PkgPath, obj.PkgName, obj.Name, obj.Shadow) newName = makeIdent(n) // imported package } else if !obj.ValidPos { // Built-in function, type, or value (e.g. nil, zero): @@ -1426,7 +1291,7 @@ func (st *state) renameFreeObjs(istate *importState) ([]ast.Expr, error) { // Form a qualified identifier, pkg.Name. if qualify { - pkgName := istate.localName(obj.PkgPath, obj.PkgName, obj.Shadow) + pkgName := istate.localName(obj.PkgPath, obj.PkgName, obj.PkgName, obj.Shadow) newName = &ast.SelectorExpr{ X: makeIdent(pkgName), Sel: makeIdent(obj.Name), @@ -3272,25 +3137,6 @@ func last[T any](slice []T) T { return *new(T) } -// consistentOffsets reports whether the portion of caller.Content -// that corresponds to caller.Call can be parsed as a call expression. -// If not, the client has provided inconsistent information, possibly -// because they forgot to ignore line directives when computing the -// filename enclosing the call. -// This is just a heuristic. -func consistentOffsets(caller *Caller) bool { - start := offsetOf(caller.Fset, caller.Call.Pos()) - end := offsetOf(caller.Fset, caller.Call.End()) - if !(0 < start && start < end && end <= len(caller.Content)) { - return false - } - expr, err := parser.ParseExpr(string(caller.Content[start:end])) - if err != nil { - return false - } - return is[*ast.CallExpr](expr) -} - // needsParens reports whether parens are required to avoid ambiguity // around the new node replacing the specified old node (which is some // ancestor of the CallExpr identified by its PathEnclosingInterval). diff --git a/src/cmd/vendor/golang.org/x/tools/internal/refactor/refactor.go b/src/cmd/vendor/golang.org/x/tools/internal/refactor/refactor.go index 26bc079808f..8664377f854 100644 --- a/src/cmd/vendor/golang.org/x/tools/internal/refactor/refactor.go +++ b/src/cmd/vendor/golang.org/x/tools/internal/refactor/refactor.go @@ -5,8 +5,7 @@ // Package refactor provides operators to compute common textual edits // for refactoring tools. // -// This package should not use features of the analysis API -// other than [analysis.TextEdit]. +// This package should not use features of the analysis API other than [Edit]. package refactor import ( diff --git a/src/cmd/vendor/golang.org/x/tools/internal/stdlib/deps.go b/src/cmd/vendor/golang.org/x/tools/internal/stdlib/deps.go index 581784da435..f7b9c12865a 100644 --- a/src/cmd/vendor/golang.org/x/tools/internal/stdlib/deps.go +++ b/src/cmd/vendor/golang.org/x/tools/internal/stdlib/deps.go @@ -12,360 +12,364 @@ type pkginfo struct { } var deps = [...]pkginfo{ - {"archive/tar", "\x03n\x03E<\x01\n\x01$\x01\x01\x02\x05\b\x02\x01\x02\x02\f"}, - {"archive/zip", "\x02\x04d\a\x03\x12\x021<\x01+\x05\x01\x0f\x03\x02\x0e\x04"}, - {"bufio", "\x03n\x84\x01D\x14"}, - {"bytes", "q*Z\x03\fG\x02\x02"}, + {"archive/tar", "\x03p\x03F=\x01\n\x01$\x01\x01\x02\x05\b\x02\x01\x02\x02\f"}, + {"archive/zip", "\x02\x04f\a\x03\x13\x021=\x01+\x05\x01\x0f\x03\x02\x0e\x04"}, + {"bufio", "\x03p\x86\x01D\x14"}, + {"bytes", "s+[\x03\fG\x02\x02"}, {"cmp", ""}, - {"compress/bzip2", "\x02\x02\xf1\x01A"}, - {"compress/flate", "\x02o\x03\x81\x01\f\x033\x01\x03"}, - {"compress/gzip", "\x02\x04d\a\x03\x14mT"}, - {"compress/lzw", "\x02o\x03\x81\x01"}, - {"compress/zlib", "\x02\x04d\a\x03\x12\x01n"}, - {"container/heap", "\xb7\x02"}, + {"compress/bzip2", "\x02\x02\xf5\x01A"}, + {"compress/flate", "\x02q\x03\x83\x01\f\x033\x01\x03"}, + {"compress/gzip", "\x02\x04f\a\x03\x15nT"}, + {"compress/lzw", "\x02q\x03\x83\x01"}, + {"compress/zlib", "\x02\x04f\a\x03\x13\x01o"}, + {"container/heap", "\xbb\x02"}, {"container/list", ""}, {"container/ring", ""}, - {"context", "q[o\x01\r"}, - {"crypto", "\x86\x01oC"}, - {"crypto/aes", "\x10\n\t\x95\x02"}, - {"crypto/cipher", "\x03 \x01\x01\x1f\x11\x1c+Y"}, - {"crypto/des", "\x10\x15\x1f-+\x9c\x01\x03"}, - {"crypto/dsa", "D\x04)\x84\x01\r"}, - {"crypto/ecdh", "\x03\v\f\x10\x04\x16\x04\r\x1c\x84\x01"}, - {"crypto/ecdsa", "\x0e\x05\x03\x04\x01\x10\a\v\x06\x01\x04\f\x01\x1c\x84\x01\r\x05K\x01"}, - {"crypto/ed25519", "\x0e\x1e\x11\a\n\a\x1c\x84\x01C"}, - {"crypto/elliptic", "2?\x84\x01\r9"}, + {"context", "s\\p\x01\r"}, + {"crypto", "\x89\x01pC"}, + {"crypto/aes", "\x10\n\t\x99\x02"}, + {"crypto/cipher", "\x03 \x01\x01 \x12\x1c,Z"}, + {"crypto/des", "\x10\x15 .,\x9d\x01\x03"}, + {"crypto/dsa", "E\x04*\x86\x01\r"}, + {"crypto/ecdh", "\x03\v\f\x10\x04\x17\x04\x0e\x1c\x86\x01"}, + {"crypto/ecdsa", "\x0e\x05\x03\x04\x01\x10\b\v\x06\x01\x04\r\x01\x1c\x86\x01\r\x05K\x01"}, + {"crypto/ed25519", "\x0e\x1e\x12\a\v\a\x1c\x86\x01C"}, + {"crypto/elliptic", "3@\x86\x01\r9"}, {"crypto/fips140", "\"\x05"}, - {"crypto/hkdf", "/\x14\x01-\x15"}, - {"crypto/hmac", "\x1a\x16\x13\x01\x111"}, - {"crypto/internal/boring", "\x0e\x02\ri"}, - {"crypto/internal/boring/bbig", "\x1a\xe8\x01M"}, - {"crypto/internal/boring/bcache", "\xbc\x02\x13"}, + {"crypto/hkdf", "/\x15\x01.\x16"}, + {"crypto/hmac", "\x1a\x16\x14\x01\x122"}, + {"crypto/internal/boring", "\x0e\x02\rl"}, + {"crypto/internal/boring/bbig", "\x1a\xec\x01M"}, + {"crypto/internal/boring/bcache", "\xc0\x02\x13"}, {"crypto/internal/boring/sig", ""}, {"crypto/internal/constanttime", ""}, - {"crypto/internal/cryptotest", "\x03\r\n\b%\x0e\x19\x06\x12\x12 \x04\x06\t\x18\x01\x11\x11\x1b\x01\a\x05\b\x03\x05\v"}, - {"crypto/internal/entropy", "I"}, - {"crypto/internal/entropy/v1.0.0", "B/\x93\x018\x13"}, - {"crypto/internal/fips140", "A0\xbd\x01\v\x16"}, - {"crypto/internal/fips140/aes", "\x03\x1f\x03\x02\x13\x05\x01\x01\x06*\x93\x014"}, - {"crypto/internal/fips140/aes/gcm", "\"\x01\x02\x02\x02\x11\x05\x01\a*\x90\x01"}, - {"crypto/internal/fips140/alias", "\xcf\x02"}, - {"crypto/internal/fips140/bigmod", "'\x18\x01\a*\x93\x01"}, - {"crypto/internal/fips140/check", "\"\x0e\x06\t\x02\xb4\x01Z"}, - {"crypto/internal/fips140/check/checktest", "'\x87\x02!"}, - {"crypto/internal/fips140/drbg", "\x03\x1e\x01\x01\x04\x13\x05\t\x01(\x84\x01\x0f7\x01"}, - {"crypto/internal/fips140/ecdh", "\x03\x1f\x05\x02\t\r2\x84\x01\x0f7"}, - {"crypto/internal/fips140/ecdsa", "\x03\x1f\x04\x01\x02\a\x02\x069\x15oF"}, - {"crypto/internal/fips140/ed25519", "\x03\x1f\x05\x02\x04\v9\xc7\x01\x03"}, - {"crypto/internal/fips140/edwards25519", "\x1e\t\a\x112\x93\x017"}, - {"crypto/internal/fips140/edwards25519/field", "'\x13\x052\x93\x01"}, - {"crypto/internal/fips140/hkdf", "\x03\x1f\x05\t\x06;\x15"}, - {"crypto/internal/fips140/hmac", "\x03\x1f\x14\x01\x019\x15"}, - {"crypto/internal/fips140/mlkem", "\x03\x1f\x05\x02\x0e\x03\x052\xca\x01"}, - {"crypto/internal/fips140/nistec", "\x1e\t\f\f2\x93\x01*\r\x14"}, - {"crypto/internal/fips140/nistec/fiat", "'\x137\x93\x01"}, - {"crypto/internal/fips140/pbkdf2", "\x03\x1f\x05\t\x06;\x15"}, - {"crypto/internal/fips140/rsa", "\x03\x1b\x04\x04\x01\x02\r\x01\x01\x027\x15oF"}, - {"crypto/internal/fips140/sha256", "\x03\x1f\x1d\x01\a*\x15~"}, - {"crypto/internal/fips140/sha3", "\x03\x1f\x18\x05\x011\x93\x01K"}, - {"crypto/internal/fips140/sha512", "\x03\x1f\x1d\x01\a*\x15~"}, - {"crypto/internal/fips140/ssh", "'_"}, - {"crypto/internal/fips140/subtle", "\x1e\a\x1a\xc5\x01"}, - {"crypto/internal/fips140/tls12", "\x03\x1f\x05\t\x06\x029\x15"}, - {"crypto/internal/fips140/tls13", "\x03\x1f\x05\b\a\t2\x15"}, - {"crypto/internal/fips140cache", "\xae\x02\r&"}, + {"crypto/internal/cryptotest", "\x03\r\n\b&\x0f\x19\x06\x13\x12 \x04\x06\t\x19\x01\x11\x11\x1b\x01\a\x05\b\x03\x05\v"}, + {"crypto/internal/entropy", "J"}, + {"crypto/internal/entropy/v1.0.0", "C0\x95\x018\x13"}, + {"crypto/internal/fips140", "B1\xbf\x01\v\x16"}, + {"crypto/internal/fips140/aes", "\x03\x1f\x03\x02\x14\x05\x01\x01\x06+\x95\x014"}, + {"crypto/internal/fips140/aes/gcm", "\"\x01\x02\x02\x02\x12\x05\x01\a+\x92\x01"}, + {"crypto/internal/fips140/alias", "\xd3\x02"}, + {"crypto/internal/fips140/bigmod", "'\x19\x01\a+\x95\x01"}, + {"crypto/internal/fips140/check", "\"\x0e\a\t\x02\xb7\x01Z"}, + {"crypto/internal/fips140/check/checktest", "'\x8b\x02!"}, + {"crypto/internal/fips140/drbg", "\x03\x1e\x01\x01\x04\x14\x05\t\x01)\x86\x01\x0f7\x01"}, + {"crypto/internal/fips140/ecdh", "\x03\x1f\x05\x02\n\r3\x86\x01\x0f7"}, + {"crypto/internal/fips140/ecdsa", "\x03\x1f\x04\x01\x02\a\x03\x06:\x16pF"}, + {"crypto/internal/fips140/ed25519", "\x03\x1f\x05\x02\x04\f:\xc9\x01\x03"}, + {"crypto/internal/fips140/edwards25519", "\x1e\t\a\x123\x95\x017"}, + {"crypto/internal/fips140/edwards25519/field", "'\x14\x053\x95\x01"}, + {"crypto/internal/fips140/hkdf", "\x03\x1f\x05\t\a<\x16"}, + {"crypto/internal/fips140/hmac", "\x03\x1f\x15\x01\x01:\x16"}, + {"crypto/internal/fips140/mldsa", "\x03\x1b\x04\x05\x02\x0e\x01\x03\x053\x95\x017"}, + {"crypto/internal/fips140/mlkem", "\x03\x1f\x05\x02\x0f\x03\x053\xcc\x01"}, + {"crypto/internal/fips140/nistec", "\x1e\t\r\f3\x95\x01*\r\x14"}, + {"crypto/internal/fips140/nistec/fiat", "'\x148\x95\x01"}, + {"crypto/internal/fips140/pbkdf2", "\x03\x1f\x05\t\a<\x16"}, + {"crypto/internal/fips140/rsa", "\x03\x1b\x04\x04\x01\x02\x0e\x01\x01\x028\x16pF"}, + {"crypto/internal/fips140/sha256", "\x03\x1f\x1e\x01\a+\x16\x7f"}, + {"crypto/internal/fips140/sha3", "\x03\x1f\x19\x05\x012\x95\x01K"}, + {"crypto/internal/fips140/sha512", "\x03\x1f\x1e\x01\a+\x16\x7f"}, + {"crypto/internal/fips140/ssh", "'b"}, + {"crypto/internal/fips140/subtle", "\x1e\a\x1b\xc8\x01"}, + {"crypto/internal/fips140/tls12", "\x03\x1f\x05\t\a\x02:\x16"}, + {"crypto/internal/fips140/tls13", "\x03\x1f\x05\b\b\t3\x16"}, + {"crypto/internal/fips140cache", "\xb2\x02\r&"}, {"crypto/internal/fips140deps", ""}, - {"crypto/internal/fips140deps/byteorder", "\x9c\x01"}, - {"crypto/internal/fips140deps/cpu", "\xb1\x01\a"}, - {"crypto/internal/fips140deps/godebug", "\xb9\x01"}, - {"crypto/internal/fips140deps/time", "\xc9\x02"}, - {"crypto/internal/fips140hash", "7\x1c3\xc9\x01"}, - {"crypto/internal/fips140only", ")\r\x01\x01N3<"}, + {"crypto/internal/fips140deps/byteorder", "\x9f\x01"}, + {"crypto/internal/fips140deps/cpu", "\xb4\x01\a"}, + {"crypto/internal/fips140deps/godebug", "\xbc\x01"}, + {"crypto/internal/fips140deps/time", "\xcd\x02"}, + {"crypto/internal/fips140hash", "8\x1d4\xca\x01"}, + {"crypto/internal/fips140only", ")\x0e\x01\x01P3="}, {"crypto/internal/fips140test", ""}, - {"crypto/internal/hpke", "\x0e\x01\x01\x03\x056#+hM"}, - {"crypto/internal/impl", "\xb9\x02"}, - {"crypto/internal/randutil", "\xf5\x01\x12"}, - {"crypto/internal/sysrand", "qo! \r\r\x01\x01\f\x06"}, - {"crypto/internal/sysrand/internal/seccomp", "q"}, - {"crypto/md5", "\x0e6-\x15\x16h"}, - {"crypto/mlkem", "1"}, - {"crypto/pbkdf2", "4\x0f\x01-\x15"}, - {"crypto/rand", "\x1a\b\a\x1b\x04\x01(\x84\x01\rM"}, - {"crypto/rc4", "%\x1f-\xc7\x01"}, - {"crypto/rsa", "\x0e\f\x01\v\x0f\x0e\x01\x04\x06\a\x1c\x03\x123<\f\x01"}, - {"crypto/sha1", "\x0e\f*\x03*\x15\x16\x15S"}, - {"crypto/sha256", "\x0e\f\x1cP"}, - {"crypto/sha3", "\x0e)O\xc9\x01"}, - {"crypto/sha512", "\x0e\f\x1eN"}, - {"crypto/subtle", "\x1e\x1c\x9c\x01X"}, - {"crypto/tls", "\x03\b\x02\x01\x01\x01\x01\x02\x01\x01\x01\x02\x01\x01\t\x01\r\n\x01\n\x05\x03\x01\x01\x01\x01\x02\x01\x02\x01\x17\x02\x03\x12\x16\x15\b<\x16\x16\r\b\x01\x01\x01\x02\x01\r\x06\x02\x01\x0f"}, - {"crypto/tls/internal/fips140tls", "\x17\xa5\x02"}, - {"crypto/x509", "\x03\v\x01\x01\x01\x01\x01\x01\x01\x015\x05\x01\x01\x02\x05\x0e\x06\x02\x02\x03E\x039\x01\x02\b\x01\x01\x02\a\x10\x05\x01\x06\x02\x05\b\x02\x01\x02\x0e\x02\x01\x01\x02\x03\x01"}, - {"crypto/x509/pkix", "g\x06\a\x8e\x01G"}, - {"database/sql", "\x03\nN\x16\x03\x81\x01\v\a\"\x05\b\x02\x03\x01\r\x02\x02\x02"}, - {"database/sql/driver", "\rd\x03\xb5\x01\x0f\x11"}, - {"debug/buildinfo", "\x03[\x02\x01\x01\b\a\x03e\x1a\x02\x01+\x0f\x1f"}, - {"debug/dwarf", "\x03g\a\x03\x81\x011\x11\x01\x01"}, - {"debug/elf", "\x03\x06T\r\a\x03e\x1b\x01\f \x17\x01\x16"}, - {"debug/gosym", "\x03g\n\xc3\x01\x01\x01\x02"}, - {"debug/macho", "\x03\x06T\r\ne\x1c,\x17\x01"}, - {"debug/pe", "\x03\x06T\r\a\x03e\x1c,\x17\x01\x16"}, - {"debug/plan9obj", "j\a\x03e\x1c,"}, - {"embed", "q*A\x19\x01S"}, + {"crypto/internal/hpke", "\x03\v\x01\x01\x03\x055\x03\x04\x01\x01\x16\a\x03\x13\xcc\x01"}, + {"crypto/internal/impl", "\xbd\x02"}, + {"crypto/internal/randutil", "\xf9\x01\x12"}, + {"crypto/internal/sysrand", "sq! \r\r\x01\x01\f\x06"}, + {"crypto/internal/sysrand/internal/seccomp", "s"}, + {"crypto/md5", "\x0e7.\x16\x16i"}, + {"crypto/mlkem", "\x0e$"}, + {"crypto/mlkem/mlkemtest", "2\x1b&"}, + {"crypto/pbkdf2", "5\x0f\x01.\x16"}, + {"crypto/rand", "\x1a\b\a\x1c\x04\x01)\x86\x01\rM"}, + {"crypto/rc4", "% .\xc9\x01"}, + {"crypto/rsa", "\x0e\f\x01\v\x10\x0e\x01\x04\a\a\x1c\x03\x133=\f\x01"}, + {"crypto/sha1", "\x0e\f+\x03+\x16\x16\x15T"}, + {"crypto/sha256", "\x0e\f\x1dR"}, + {"crypto/sha3", "\x0e*Q\xca\x01"}, + {"crypto/sha512", "\x0e\f\x1fP"}, + {"crypto/subtle", "\x1e\x1d\x9f\x01X"}, + {"crypto/tls", "\x03\b\x02\x01\x01\x01\x01\x02\x01\x01\x01\x02\x01\x01\t\x01\x0e\n\x01\n\x05\x04\x01\x01\x01\x01\x02\x01\x02\x01\x17\x02\x03\x13\x16\x15\b=\x16\x16\r\b\x01\x01\x01\x02\x01\r\x06\x02\x01\x0f"}, + {"crypto/tls/internal/fips140tls", "\x17\xa9\x02"}, + {"crypto/x509", "\x03\v\x01\x01\x01\x01\x01\x01\x01\x016\x06\x01\x01\x02\x05\x0e\x06\x02\x02\x03F\x03:\x01\x02\b\x01\x01\x02\a\x10\x05\x01\x06\a\b\x02\x01\x02\x0e\x02\x01\x01\x02\x03\x01"}, + {"crypto/x509/pkix", "i\x06\a\x90\x01G"}, + {"database/sql", "\x03\nP\x16\x03\x83\x01\v\a\"\x05\b\x02\x03\x01\r\x02\x02\x02"}, + {"database/sql/driver", "\rf\x03\xb7\x01\x0f\x11"}, + {"debug/buildinfo", "\x03]\x02\x01\x01\b\a\x03g\x1a\x02\x01+\x0f\x1f"}, + {"debug/dwarf", "\x03i\a\x03\x83\x011\x11\x01\x01"}, + {"debug/elf", "\x03\x06V\r\a\x03g\x1b\x01\f \x17\x01\x16"}, + {"debug/gosym", "\x03i\n\xc5\x01\x01\x01\x02"}, + {"debug/macho", "\x03\x06V\r\ng\x1c,\x17\x01"}, + {"debug/pe", "\x03\x06V\r\a\x03g\x1c,\x17\x01\x16"}, + {"debug/plan9obj", "l\a\x03g\x1c,"}, + {"embed", "s+B\x19\x01S"}, {"embed/internal/embedtest", ""}, {"encoding", ""}, - {"encoding/ascii85", "\xf5\x01C"}, - {"encoding/asn1", "\x03n\x03e(\x01'\r\x02\x01\x10\x03\x01"}, - {"encoding/base32", "\xf5\x01A\x02"}, - {"encoding/base64", "\x9c\x01YA\x02"}, - {"encoding/binary", "q\x84\x01\f(\r\x05"}, - {"encoding/csv", "\x02\x01n\x03\x81\x01D\x12\x02"}, - {"encoding/gob", "\x02c\x05\a\x03e\x1c\v\x01\x03\x1d\b\x12\x01\x0f\x02"}, - {"encoding/hex", "q\x03\x81\x01A\x03"}, - {"encoding/json", "\x03\x01a\x04\b\x03\x81\x01\f(\r\x02\x01\x02\x10\x01\x01\x02"}, - {"encoding/pem", "\x03f\b\x84\x01A\x03"}, - {"encoding/xml", "\x02\x01b\f\x03\x81\x014\x05\n\x01\x02\x10\x02"}, - {"errors", "\xcc\x01\x83\x01"}, - {"expvar", "nK@\b\v\x15\r\b\x02\x03\x01\x11"}, - {"flag", "e\f\x03\x81\x01,\b\x05\b\x02\x01\x10"}, - {"fmt", "qE&\x19\f \b\r\x02\x03\x12"}, - {"go/ast", "\x03\x01p\x0e\x01r\x03)\b\r\x02\x01\x12\x02"}, - {"go/build", "\x02\x01n\x03\x01\x02\x02\a\x02\x01\x17\x1f\x04\x02\b\x1b\x13\x01+\x01\x04\x01\a\b\x02\x01\x12\x02\x02"}, - {"go/build/constraint", "q\xc7\x01\x01\x12\x02"}, - {"go/constant", "t\x0f~\x01\x024\x01\x02\x12"}, - {"go/doc", "\x04p\x01\x05\t=51\x10\x02\x01\x12\x02"}, - {"go/doc/comment", "\x03q\xc2\x01\x01\x01\x01\x12\x02"}, - {"go/format", "\x03q\x01\v\x01\x02rD"}, - {"go/importer", "v\a\x01\x01\x04\x01q9"}, - {"go/internal/gccgoimporter", "\x02\x01[\x13\x03\x04\v\x01o\x02,\x01\x05\x11\x01\f\b"}, - {"go/internal/gcimporter", "\x02r\x0f\x010\x05\r/,\x15\x03\x02"}, - {"go/internal/srcimporter", "t\x01\x01\n\x03\x01q,\x01\x05\x12\x02\x14"}, - {"go/parser", "\x03n\x03\x01\x02\v\x01r\x01+\x06\x12"}, - {"go/printer", "t\x01\x02\x03\tr\f \x15\x02\x01\x02\v\x05\x02"}, - {"go/scanner", "\x03q\x0fr2\x10\x01\x13\x02"}, - {"go/token", "\x04p\x84\x01>\x02\x03\x01\x0f\x02"}, - {"go/types", "\x03\x01\x06g\x03\x01\x03\b\x03\x024\x062\x04\x03\t \x06\a\b\x01\x01\x01\x02\x01\x0f\x02\x02"}, - {"go/version", "\xbe\x01{"}, - {"hash", "\xf5\x01"}, - {"hash/adler32", "q\x15\x16"}, - {"hash/crc32", "q\x15\x16\x15\x8a\x01\x01\x13"}, - {"hash/crc64", "q\x15\x16\x9f\x01"}, - {"hash/fnv", "q\x15\x16h"}, - {"hash/maphash", "\x86\x01\x11<|"}, - {"html", "\xb9\x02\x02\x12"}, - {"html/template", "\x03k\x06\x18-<\x01\n!\x05\x01\x02\x03\f\x01\x02\f\x01\x03\x02"}, - {"image", "\x02o\x1ef\x0f4\x03\x01"}, + {"encoding/ascii85", "\xf9\x01C"}, + {"encoding/asn1", "\x03p\x03g(\x01'\r\x02\x01\x10\x03\x01"}, + {"encoding/base32", "\xf9\x01A\x02"}, + {"encoding/base64", "\x9f\x01ZA\x02"}, + {"encoding/binary", "s\x86\x01\f(\r\x05"}, + {"encoding/csv", "\x02\x01p\x03\x83\x01D\x12\x02"}, + {"encoding/gob", "\x02e\x05\a\x03g\x1c\v\x01\x03\x1d\b\x12\x01\x0f\x02"}, + {"encoding/hex", "s\x03\x83\x01A\x03"}, + {"encoding/json", "\x03\x01c\x04\b\x03\x83\x01\f(\r\x02\x01\x02\x10\x01\x01\x02"}, + {"encoding/pem", "\x03h\b\x86\x01A\x03"}, + {"encoding/xml", "\x02\x01d\f\x03\x83\x014\x05\n\x01\x02\x10\x02"}, + {"errors", "\xcf\x01\x84\x01"}, + {"expvar", "pLA\b\v\x15\r\b\x02\x03\x01\x11"}, + {"flag", "g\f\x03\x83\x01,\b\x05\b\x02\x01\x10"}, + {"fmt", "sF'\x19\f \b\r\x02\x03\x12"}, + {"go/ast", "\x03\x01r\x0f\x01s\x03)\b\r\x02\x01\x12\x02"}, + {"go/build", "\x02\x01p\x03\x01\x02\x02\b\x02\x01\x17\x1f\x04\x02\b\x1c\x13\x01+\x01\x04\x01\a\b\x02\x01\x12\x02\x02"}, + {"go/build/constraint", "s\xc9\x01\x01\x12\x02"}, + {"go/constant", "v\x10\x7f\x01\x024\x01\x02\x12"}, + {"go/doc", "\x04r\x01\x05\n=61\x10\x02\x01\x12\x02"}, + {"go/doc/comment", "\x03s\xc4\x01\x01\x01\x01\x12\x02"}, + {"go/format", "\x03s\x01\f\x01\x02sD"}, + {"go/importer", "x\a\x01\x02\x04\x01r9"}, + {"go/internal/gccgoimporter", "\x02\x01]\x13\x03\x04\f\x01p\x02,\x01\x05\x11\x01\f\b"}, + {"go/internal/gcimporter", "\x02t\x10\x010\x05\r0,\x15\x03\x02"}, + {"go/internal/scannerhooks", "\x86\x01"}, + {"go/internal/srcimporter", "v\x01\x01\v\x03\x01r,\x01\x05\x12\x02\x14"}, + {"go/parser", "\x03p\x03\x01\x02\b\x04\x01s\x01+\x06\x12"}, + {"go/printer", "v\x01\x02\x03\ns\f \x15\x02\x01\x02\v\x05\x02"}, + {"go/scanner", "\x03s\v\x05s2\x10\x01\x13\x02"}, + {"go/token", "\x04r\x86\x01>\x02\x03\x01\x0f\x02"}, + {"go/types", "\x03\x01\x06i\x03\x01\x03\t\x03\x024\x063\x04\x03\t \x06\a\b\x01\x01\x01\x02\x01\x0f\x02\x02"}, + {"go/version", "\xc1\x01|"}, + {"hash", "\xf9\x01"}, + {"hash/adler32", "s\x16\x16"}, + {"hash/crc32", "s\x16\x16\x15\x8b\x01\x01\x13"}, + {"hash/crc64", "s\x16\x16\xa0\x01"}, + {"hash/fnv", "s\x16\x16i"}, + {"hash/maphash", "\x89\x01\x11<}"}, + {"html", "\xbd\x02\x02\x12"}, + {"html/template", "\x03m\x06\x19-=\x01\n!\x05\x01\x02\x03\f\x01\x02\f\x01\x03\x02"}, + {"image", "\x02q\x1fg\x0f4\x03\x01"}, {"image/color", ""}, - {"image/color/palette", "\x8f\x01"}, - {"image/draw", "\x8e\x01\x01\x04"}, - {"image/gif", "\x02\x01\x05i\x03\x1a\x01\x01\x01\vY"}, - {"image/internal/imageutil", "\x8e\x01"}, - {"image/jpeg", "\x02o\x1d\x01\x04b"}, - {"image/png", "\x02\aa\n\x12\x02\x06\x01fC"}, - {"index/suffixarray", "\x03g\a\x84\x01\f+\n\x01"}, - {"internal/abi", "\xb8\x01\x97\x01"}, - {"internal/asan", "\xcf\x02"}, - {"internal/bisect", "\xae\x02\r\x01"}, - {"internal/buildcfg", "tGf\x06\x02\x05\n\x01"}, - {"internal/bytealg", "\xb1\x01\x9e\x01"}, + {"image/color/palette", "\x92\x01"}, + {"image/draw", "\x91\x01\x01\x04"}, + {"image/gif", "\x02\x01\x05k\x03\x1b\x01\x01\x01\vZ\x0f"}, + {"image/internal/imageutil", "\x91\x01"}, + {"image/jpeg", "\x02q\x1e\x01\x04c"}, + {"image/png", "\x02\ac\n\x13\x02\x06\x01gC"}, + {"index/suffixarray", "\x03i\a\x86\x01\f+\n\x01"}, + {"internal/abi", "\xbb\x01\x98\x01"}, + {"internal/asan", "\xd3\x02"}, + {"internal/bisect", "\xb2\x02\r\x01"}, + {"internal/buildcfg", "vHg\x06\x02\x05\n\x01"}, + {"internal/bytealg", "\xb4\x01\x9f\x01"}, {"internal/byteorder", ""}, {"internal/cfg", ""}, - {"internal/cgrouptest", "tZS\x06\x0f\x02\x01\x04\x01"}, - {"internal/chacha8rand", "\x9c\x01\x15\a\x97\x01"}, + {"internal/cgrouptest", "v[T\x06\x0f\x02\x01\x04\x01"}, + {"internal/chacha8rand", "\x9f\x01\x15\a\x98\x01"}, {"internal/copyright", ""}, {"internal/coverage", ""}, {"internal/coverage/calloc", ""}, - {"internal/coverage/cfile", "n\x06\x16\x17\x01\x02\x01\x01\x01\x01\x01\x01\x01\"\x02&,\x06\a\n\x01\x03\r\x06"}, - {"internal/coverage/cformat", "\x04p-\x04P\v6\x01\x02\r"}, - {"internal/coverage/cmerge", "t-`"}, - {"internal/coverage/decodecounter", "j\n-\v\x02G,\x17\x17"}, - {"internal/coverage/decodemeta", "\x02h\n\x16\x17\v\x02G,"}, - {"internal/coverage/encodecounter", "\x02h\n-\f\x01\x02E\v!\x15"}, - {"internal/coverage/encodemeta", "\x02\x01g\n\x12\x04\x17\r\x02E,."}, - {"internal/coverage/pods", "\x04p-\x80\x01\x06\x05\n\x02\x01"}, - {"internal/coverage/rtcov", "\xcf\x02"}, - {"internal/coverage/slicereader", "j\n\x81\x01Z"}, - {"internal/coverage/slicewriter", "t\x81\x01"}, - {"internal/coverage/stringtab", "t8\x04E"}, + {"internal/coverage/cfile", "p\x06\x17\x17\x01\x02\x01\x01\x01\x01\x01\x01\x01\"\x02',\x06\a\n\x01\x03\r\x06"}, + {"internal/coverage/cformat", "\x04r.\x04Q\v6\x01\x02\r"}, + {"internal/coverage/cmerge", "v.a"}, + {"internal/coverage/decodecounter", "l\n.\v\x02H,\x17\x17"}, + {"internal/coverage/decodemeta", "\x02j\n\x17\x17\v\x02H,"}, + {"internal/coverage/encodecounter", "\x02j\n.\f\x01\x02F\v!\x15"}, + {"internal/coverage/encodemeta", "\x02\x01i\n\x13\x04\x17\r\x02F,."}, + {"internal/coverage/pods", "\x04r.\x81\x01\x06\x05\n\x02\x01"}, + {"internal/coverage/rtcov", "\xd3\x02"}, + {"internal/coverage/slicereader", "l\n\x83\x01Z"}, + {"internal/coverage/slicewriter", "v\x83\x01"}, + {"internal/coverage/stringtab", "v9\x04F"}, {"internal/coverage/test", ""}, {"internal/coverage/uleb128", ""}, - {"internal/cpu", "\xcf\x02"}, - {"internal/dag", "\x04p\xc2\x01\x03"}, - {"internal/diff", "\x03q\xc3\x01\x02"}, - {"internal/exportdata", "\x02\x01n\x03\x02c\x1c,\x01\x05\x11\x01\x02"}, - {"internal/filepathlite", "q*A\x1a@"}, - {"internal/fmtsort", "\x04\xa5\x02\r"}, - {"internal/fuzz", "\x03\nE\x18\x04\x03\x03\x01\v\x036<\f\x03\x1d\x01\x05\x02\x05\n\x01\x02\x01\x01\f\x04\x02"}, + {"internal/cpu", "\xd3\x02"}, + {"internal/dag", "\x04r\xc4\x01\x03"}, + {"internal/diff", "\x03s\xc5\x01\x02"}, + {"internal/exportdata", "\x02\x01p\x03\x02e\x1c,\x01\x05\x11\x01\x02"}, + {"internal/filepathlite", "s+B\x1a@"}, + {"internal/fmtsort", "\x04\xa9\x02\r"}, + {"internal/fuzz", "\x03\nG\x18\x04\x03\x03\x01\f\x036=\f\x03\x1d\x01\x05\x02\x05\n\x01\x02\x01\x01\f\x04\x02"}, {"internal/goarch", ""}, - {"internal/godebug", "\x99\x01!\x81\x01\x01\x13"}, + {"internal/godebug", "\x9c\x01!\x82\x01\x01\x13"}, {"internal/godebugs", ""}, {"internal/goexperiment", ""}, {"internal/goos", ""}, - {"internal/goroot", "\xa1\x02\x01\x05\x12\x02"}, + {"internal/goroot", "\xa5\x02\x01\x05\x12\x02"}, {"internal/gover", "\x04"}, {"internal/goversion", ""}, - {"internal/lazyregexp", "\xa1\x02\v\r\x02"}, - {"internal/lazytemplate", "\xf5\x01,\x18\x02\f"}, - {"internal/msan", "\xcf\x02"}, + {"internal/lazyregexp", "\xa5\x02\v\r\x02"}, + {"internal/lazytemplate", "\xf9\x01,\x18\x02\f"}, + {"internal/msan", "\xd3\x02"}, {"internal/nettrace", ""}, - {"internal/obscuretestdata", "i\x8c\x01,"}, - {"internal/oserror", "q"}, - {"internal/pkgbits", "\x03O\x18\a\x03\x04\vr\r\x1f\r\n\x01"}, + {"internal/obscuretestdata", "k\x8e\x01,"}, + {"internal/oserror", "s"}, + {"internal/pkgbits", "\x03Q\x18\a\x03\x04\fs\r\x1f\r\n\x01"}, {"internal/platform", ""}, - {"internal/poll", "qj\x05\x159\r\x01\x01\f\x06"}, - {"internal/profile", "\x03\x04j\x03\x81\x017\n\x01\x01\x01\x10"}, + {"internal/poll", "sl\x05\x159\r\x01\x01\f\x06"}, + {"internal/profile", "\x03\x04l\x03\x83\x017\n\x01\x01\x01\x10"}, {"internal/profilerecord", ""}, - {"internal/race", "\x97\x01\xb8\x01"}, - {"internal/reflectlite", "\x97\x01!:\x16"}, - {"vendor/golang.org/x/text/unicode/norm", "j\n\x81\x01F\x12\x11"}, - {"weak", "\x97\x01\x97\x01!"}, + {"vendor/golang.org/x/crypto/internal/alias", "\xd3\x02"}, + {"vendor/golang.org/x/crypto/internal/poly1305", "W\x15\x9c\x01"}, + {"vendor/golang.org/x/net/dns/dnsmessage", "s\xc7\x01"}, + {"vendor/golang.org/x/net/http/httpguts", "\x8f\x02\x14\x1a\x14\r"}, + {"vendor/golang.org/x/net/http/httpproxy", "s\x03\x99\x01\x10\x05\x01\x18\x14\r"}, + {"vendor/golang.org/x/net/http2/hpack", "\x03p\x03\x83\x01F"}, + {"vendor/golang.org/x/net/idna", "v\x8f\x018\x14\x10\x02\x01"}, + {"vendor/golang.org/x/net/nettest", "\x03i\a\x03\x83\x01\x11\x05\x16\x01\f\n\x01\x02\x02\x01\v"}, + {"vendor/golang.org/x/sys/cpu", "\xa5\x02\r\n\x01\x16"}, + {"vendor/golang.org/x/text/secure/bidirule", "s\xde\x01\x11\x01"}, + {"vendor/golang.org/x/text/transform", "\x03p\x86\x01X"}, + {"vendor/golang.org/x/text/unicode/bidi", "\x03\bk\x87\x01>\x16"}, + {"vendor/golang.org/x/text/unicode/norm", "l\n\x83\x01F\x12\x11"}, + {"weak", "\x9a\x01\x98\x01!"}, } // bootstrap is the list of bootstrap packages extracted from cmd/dist. @@ -385,6 +389,7 @@ var bootstrap = map[string]bool{ "cmd/compile/internal/arm64": true, "cmd/compile/internal/base": true, "cmd/compile/internal/bitvec": true, + "cmd/compile/internal/bloop": true, "cmd/compile/internal/compare": true, "cmd/compile/internal/coverage": true, "cmd/compile/internal/deadlocals": true, @@ -413,6 +418,7 @@ var bootstrap = map[string]bool{ "cmd/compile/internal/riscv64": true, "cmd/compile/internal/rttype": true, "cmd/compile/internal/s390x": true, + "cmd/compile/internal/slice": true, "cmd/compile/internal/ssa": true, "cmd/compile/internal/ssagen": true, "cmd/compile/internal/staticdata": true, diff --git a/src/cmd/vendor/golang.org/x/tools/internal/stdlib/manifest.go b/src/cmd/vendor/golang.org/x/tools/internal/stdlib/manifest.go index 362f23c436c..f1e24625a7a 100644 --- a/src/cmd/vendor/golang.org/x/tools/internal/stdlib/manifest.go +++ b/src/cmd/vendor/golang.org/x/tools/internal/stdlib/manifest.go @@ -16,6 +16,14 @@ var PackageSymbols = map[string][]Symbol{ {"(*Writer).Flush", Method, 0, ""}, {"(*Writer).Write", Method, 0, ""}, {"(*Writer).WriteHeader", Method, 0, ""}, + {"(FileInfoNames).Gname", Method, 23, ""}, + {"(FileInfoNames).IsDir", Method, 23, ""}, + {"(FileInfoNames).ModTime", Method, 23, ""}, + {"(FileInfoNames).Mode", Method, 23, ""}, + {"(FileInfoNames).Name", Method, 23, ""}, + {"(FileInfoNames).Size", Method, 23, ""}, + {"(FileInfoNames).Sys", Method, 23, ""}, + {"(FileInfoNames).Uname", Method, 23, ""}, {"(Format).String", Method, 10, ""}, {"ErrFieldTooLong", Var, 0, ""}, {"ErrHeader", Var, 0, ""}, @@ -338,6 +346,9 @@ var PackageSymbols = map[string][]Symbol{ {"(*Writer).Write", Method, 0, ""}, {"(CorruptInputError).Error", Method, 0, ""}, {"(InternalError).Error", Method, 0, ""}, + {"(Reader).Read", Method, 0, ""}, + {"(Reader).ReadByte", Method, 0, ""}, + {"(Resetter).Reset", Method, 4, ""}, {"BestCompression", Const, 0, ""}, {"BestSpeed", Const, 0, ""}, {"CorruptInputError", Type, 0, ""}, @@ -409,6 +420,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*Writer).Flush", Method, 0, ""}, {"(*Writer).Reset", Method, 2, ""}, {"(*Writer).Write", Method, 0, ""}, + {"(Resetter).Reset", Method, 4, ""}, {"BestCompression", Const, 0, ""}, {"BestSpeed", Const, 0, ""}, {"DefaultCompression", Const, 0, ""}, @@ -426,6 +438,11 @@ var PackageSymbols = map[string][]Symbol{ {"Writer", Type, 0, ""}, }, "container/heap": { + {"(Interface).Len", Method, 0, ""}, + {"(Interface).Less", Method, 0, ""}, + {"(Interface).Pop", Method, 0, ""}, + {"(Interface).Push", Method, 0, ""}, + {"(Interface).Swap", Method, 0, ""}, {"Fix", Func, 2, "func(h Interface, i int)"}, {"Init", Func, 0, "func(h Interface)"}, {"Interface", Type, 0, ""}, @@ -469,6 +486,10 @@ var PackageSymbols = map[string][]Symbol{ {"Ring.Value", Field, 0, ""}, }, "context": { + {"(Context).Deadline", Method, 7, ""}, + {"(Context).Done", Method, 7, ""}, + {"(Context).Err", Method, 7, ""}, + {"(Context).Value", Method, 7, ""}, {"AfterFunc", Func, 21, "func(ctx Context, f func()) (stop func() bool)"}, {"Background", Func, 7, "func() Context"}, {"CancelCauseFunc", Type, 20, ""}, @@ -488,17 +509,31 @@ var PackageSymbols = map[string][]Symbol{ {"WithoutCancel", Func, 21, "func(parent Context) Context"}, }, "crypto": { + {"(Decapsulator).Decapsulate", Method, 26, ""}, + {"(Decapsulator).Encapsulator", Method, 26, ""}, + {"(Decrypter).Decrypt", Method, 5, ""}, + {"(Decrypter).Public", Method, 5, ""}, + {"(Encapsulator).Bytes", Method, 26, ""}, + {"(Encapsulator).Encapsulate", Method, 26, ""}, {"(Hash).Available", Method, 0, ""}, {"(Hash).HashFunc", Method, 4, ""}, {"(Hash).New", Method, 0, ""}, {"(Hash).Size", Method, 0, ""}, {"(Hash).String", Method, 15, ""}, + {"(MessageSigner).Public", Method, 25, ""}, + {"(MessageSigner).Sign", Method, 25, ""}, + {"(MessageSigner).SignMessage", Method, 25, ""}, + {"(Signer).Public", Method, 4, ""}, + {"(Signer).Sign", Method, 4, ""}, + {"(SignerOpts).HashFunc", Method, 4, ""}, {"BLAKE2b_256", Const, 9, ""}, {"BLAKE2b_384", Const, 9, ""}, {"BLAKE2b_512", Const, 9, ""}, {"BLAKE2s_256", Const, 9, ""}, + {"Decapsulator", Type, 26, ""}, {"Decrypter", Type, 5, ""}, {"DecrypterOpts", Type, 5, ""}, + {"Encapsulator", Type, 26, ""}, {"Hash", Type, 0, ""}, {"MD4", Const, 0, ""}, {"MD5", Const, 0, ""}, @@ -530,6 +565,16 @@ var PackageSymbols = map[string][]Symbol{ {"NewCipher", Func, 0, "func(key []byte) (cipher.Block, error)"}, }, "crypto/cipher": { + {"(AEAD).NonceSize", Method, 2, ""}, + {"(AEAD).Open", Method, 2, ""}, + {"(AEAD).Overhead", Method, 2, ""}, + {"(AEAD).Seal", Method, 2, ""}, + {"(Block).BlockSize", Method, 0, ""}, + {"(Block).Decrypt", Method, 0, ""}, + {"(Block).Encrypt", Method, 0, ""}, + {"(BlockMode).BlockSize", Method, 0, ""}, + {"(BlockMode).CryptBlocks", Method, 0, ""}, + {"(Stream).XORKeyStream", Method, 0, ""}, {"(StreamReader).Read", Method, 0, ""}, {"(StreamWriter).Close", Method, 0, ""}, {"(StreamWriter).Write", Method, 0, ""}, @@ -594,7 +639,13 @@ var PackageSymbols = map[string][]Symbol{ {"(*PublicKey).Bytes", Method, 20, ""}, {"(*PublicKey).Curve", Method, 20, ""}, {"(*PublicKey).Equal", Method, 20, ""}, - {"Curve", Type, 20, ""}, + {"(Curve).GenerateKey", Method, 20, ""}, + {"(Curve).NewPrivateKey", Method, 20, ""}, + {"(Curve).NewPublicKey", Method, 20, ""}, + {"(KeyExchanger).Curve", Method, 26, ""}, + {"(KeyExchanger).ECDH", Method, 26, ""}, + {"(KeyExchanger).PublicKey", Method, 26, ""}, + {"KeyExchanger", Type, 26, ""}, {"P256", Func, 20, "func() Curve"}, {"P384", Func, 20, "func() Curve"}, {"P521", Func, 20, "func() Curve"}, @@ -667,6 +718,12 @@ var PackageSymbols = map[string][]Symbol{ {"(*CurveParams).Params", Method, 0, ""}, {"(*CurveParams).ScalarBaseMult", Method, 0, ""}, {"(*CurveParams).ScalarMult", Method, 0, ""}, + {"(Curve).Add", Method, 0, ""}, + {"(Curve).Double", Method, 0, ""}, + {"(Curve).IsOnCurve", Method, 0, ""}, + {"(Curve).Params", Method, 0, ""}, + {"(Curve).ScalarBaseMult", Method, 0, ""}, + {"(Curve).ScalarMult", Method, 0, ""}, {"Curve", Type, 0, ""}, {"CurveParams", Type, 0, ""}, {"CurveParams.B", Field, 0, ""}, @@ -688,6 +745,7 @@ var PackageSymbols = map[string][]Symbol{ }, "crypto/fips140": { {"Enabled", Func, 24, "func() bool"}, + {"Version", Func, 26, "func() string"}, }, "crypto/hkdf": { {"Expand", Func, 24, "func[H hash.Hash](h func() H, pseudorandomKey []byte, info string, keyLength int) ([]byte, error)"}, @@ -708,9 +766,11 @@ var PackageSymbols = map[string][]Symbol{ {"(*DecapsulationKey1024).Bytes", Method, 24, ""}, {"(*DecapsulationKey1024).Decapsulate", Method, 24, ""}, {"(*DecapsulationKey1024).EncapsulationKey", Method, 24, ""}, + {"(*DecapsulationKey1024).Encapsulator", Method, 26, ""}, {"(*DecapsulationKey768).Bytes", Method, 24, ""}, {"(*DecapsulationKey768).Decapsulate", Method, 24, ""}, {"(*DecapsulationKey768).EncapsulationKey", Method, 24, ""}, + {"(*DecapsulationKey768).Encapsulator", Method, 26, ""}, {"(*EncapsulationKey1024).Bytes", Method, 24, ""}, {"(*EncapsulationKey1024).Encapsulate", Method, 24, ""}, {"(*EncapsulationKey768).Bytes", Method, 24, ""}, @@ -732,6 +792,10 @@ var PackageSymbols = map[string][]Symbol{ {"SeedSize", Const, 24, ""}, {"SharedKeySize", Const, 24, ""}, }, + "crypto/mlkem/mlkemtest": { + {"Encapsulate1024", Func, 26, "func(ek *mlkem.EncapsulationKey1024, random []byte) (sharedKey []byte, ciphertext []byte, err error)"}, + {"Encapsulate768", Func, 26, "func(ek *mlkem.EncapsulationKey768, random []byte) (sharedKey []byte, ciphertext []byte, err error)"}, + }, "crypto/pbkdf2": { {"Key", Func, 24, "func[Hash hash.Hash](h func() Hash, password string, salt []byte, iter int, keyLength int) ([]byte, error)"}, }, @@ -769,6 +833,7 @@ var PackageSymbols = map[string][]Symbol{ {"DecryptPKCS1v15", Func, 0, "func(random io.Reader, priv *PrivateKey, ciphertext []byte) ([]byte, error)"}, {"DecryptPKCS1v15SessionKey", Func, 0, "func(random io.Reader, priv *PrivateKey, ciphertext []byte, key []byte) error"}, {"EncryptOAEP", Func, 0, "func(hash hash.Hash, random io.Reader, pub *PublicKey, msg []byte, label []byte) ([]byte, error)"}, + {"EncryptOAEPWithOptions", Func, 26, "func(random io.Reader, pub *PublicKey, msg []byte, opts *OAEPOptions) ([]byte, error)"}, {"EncryptPKCS1v15", Func, 0, "func(random io.Reader, pub *PublicKey, msg []byte) ([]byte, error)"}, {"ErrDecryption", Var, 0, ""}, {"ErrMessageTooLong", Var, 0, ""}, @@ -921,6 +986,8 @@ var PackageSymbols = map[string][]Symbol{ {"(*SessionState).Bytes", Method, 21, ""}, {"(AlertError).Error", Method, 21, ""}, {"(ClientAuthType).String", Method, 15, ""}, + {"(ClientSessionCache).Get", Method, 3, ""}, + {"(ClientSessionCache).Put", Method, 3, ""}, {"(CurveID).String", Method, 15, ""}, {"(QUICEncryptionLevel).String", Method, 21, ""}, {"(RecordHeaderError).Error", Method, 6, ""}, @@ -953,6 +1020,7 @@ var PackageSymbols = map[string][]Symbol{ {"ClientHelloInfo.CipherSuites", Field, 4, ""}, {"ClientHelloInfo.Conn", Field, 8, ""}, {"ClientHelloInfo.Extensions", Field, 24, ""}, + {"ClientHelloInfo.HelloRetryRequest", Field, 26, ""}, {"ClientHelloInfo.ServerName", Field, 4, ""}, {"ClientHelloInfo.SignatureSchemes", Field, 8, ""}, {"ClientHelloInfo.SupportedCurves", Field, 4, ""}, @@ -1001,6 +1069,7 @@ var PackageSymbols = map[string][]Symbol{ {"ConnectionState.DidResume", Field, 1, ""}, {"ConnectionState.ECHAccepted", Field, 23, ""}, {"ConnectionState.HandshakeComplete", Field, 0, ""}, + {"ConnectionState.HelloRetryRequest", Field, 26, ""}, {"ConnectionState.NegotiatedProtocol", Field, 0, ""}, {"ConnectionState.NegotiatedProtocolIsMutual", Field, 0, ""}, {"ConnectionState.OCSPResponse", Field, 5, ""}, @@ -1055,8 +1124,10 @@ var PackageSymbols = map[string][]Symbol{ {"QUICEncryptionLevelEarly", Const, 21, ""}, {"QUICEncryptionLevelHandshake", Const, 21, ""}, {"QUICEncryptionLevelInitial", Const, 21, ""}, + {"QUICErrorEvent", Const, 26, ""}, {"QUICEvent", Type, 21, ""}, {"QUICEvent.Data", Field, 21, ""}, + {"QUICEvent.Err", Field, 26, ""}, {"QUICEvent.Kind", Field, 21, ""}, {"QUICEvent.Level", Field, 21, ""}, {"QUICEvent.SessionState", Field, 23, ""}, @@ -1151,8 +1222,10 @@ var PackageSymbols = map[string][]Symbol{ {"(*RevocationList).CheckSignatureFrom", Method, 19, ""}, {"(CertificateInvalidError).Error", Method, 0, ""}, {"(ConstraintViolationError).Error", Method, 0, ""}, + {"(ExtKeyUsage).String", Method, 26, ""}, {"(HostnameError).Error", Method, 0, ""}, {"(InsecureAlgorithmError).Error", Method, 6, ""}, + {"(KeyUsage).String", Method, 26, ""}, {"(OID).AppendBinary", Method, 24, ""}, {"(OID).AppendText", Method, 24, ""}, {"(OID).Equal", Method, 22, ""}, @@ -1516,6 +1589,9 @@ var PackageSymbols = map[string][]Symbol{ {"(NullInt64).Value", Method, 0, ""}, {"(NullString).Value", Method, 0, ""}, {"(NullTime).Value", Method, 13, ""}, + {"(Result).LastInsertId", Method, 0, ""}, + {"(Result).RowsAffected", Method, 0, ""}, + {"(Scanner).Scan", Method, 0, ""}, {"ColumnType", Type, 8, ""}, {"Conn", Type, 9, ""}, {"DB", Type, 0, ""}, @@ -1547,8 +1623,6 @@ var PackageSymbols = map[string][]Symbol{ {"NamedArg.Name", Field, 8, ""}, {"NamedArg.Value", Field, 8, ""}, {"Null", Type, 22, ""}, - {"Null.V", Field, 22, ""}, - {"Null.Valid", Field, 22, ""}, {"NullBool", Type, 0, ""}, {"NullBool.Bool", Field, 0, ""}, {"NullBool.Valid", Field, 0, ""}, @@ -1591,10 +1665,72 @@ var PackageSymbols = map[string][]Symbol{ {"TxOptions.ReadOnly", Field, 8, ""}, }, "database/sql/driver": { + {"(ColumnConverter).ColumnConverter", Method, 0, ""}, + {"(Conn).Begin", Method, 0, ""}, + {"(Conn).Close", Method, 0, ""}, + {"(Conn).Prepare", Method, 0, ""}, + {"(ConnBeginTx).BeginTx", Method, 8, ""}, + {"(ConnPrepareContext).PrepareContext", Method, 8, ""}, + {"(Connector).Connect", Method, 10, ""}, + {"(Connector).Driver", Method, 10, ""}, + {"(Driver).Open", Method, 0, ""}, + {"(DriverContext).OpenConnector", Method, 10, ""}, + {"(Execer).Exec", Method, 0, ""}, + {"(ExecerContext).ExecContext", Method, 8, ""}, + {"(NamedValueChecker).CheckNamedValue", Method, 9, ""}, {"(NotNull).ConvertValue", Method, 0, ""}, {"(Null).ConvertValue", Method, 0, ""}, + {"(Pinger).Ping", Method, 8, ""}, + {"(Queryer).Query", Method, 1, ""}, + {"(QueryerContext).QueryContext", Method, 8, ""}, + {"(Result).LastInsertId", Method, 0, ""}, + {"(Result).RowsAffected", Method, 0, ""}, + {"(Rows).Close", Method, 0, ""}, + {"(Rows).Columns", Method, 0, ""}, + {"(Rows).Next", Method, 0, ""}, {"(RowsAffected).LastInsertId", Method, 0, ""}, {"(RowsAffected).RowsAffected", Method, 0, ""}, + {"(RowsColumnScanner).Close", Method, 26, ""}, + {"(RowsColumnScanner).Columns", Method, 26, ""}, + {"(RowsColumnScanner).Next", Method, 26, ""}, + {"(RowsColumnScanner).ScanColumn", Method, 26, ""}, + {"(RowsColumnTypeDatabaseTypeName).Close", Method, 8, ""}, + {"(RowsColumnTypeDatabaseTypeName).ColumnTypeDatabaseTypeName", Method, 8, ""}, + {"(RowsColumnTypeDatabaseTypeName).Columns", Method, 8, ""}, + {"(RowsColumnTypeDatabaseTypeName).Next", Method, 8, ""}, + {"(RowsColumnTypeLength).Close", Method, 8, ""}, + {"(RowsColumnTypeLength).ColumnTypeLength", Method, 8, ""}, + {"(RowsColumnTypeLength).Columns", Method, 8, ""}, + {"(RowsColumnTypeLength).Next", Method, 8, ""}, + {"(RowsColumnTypeNullable).Close", Method, 8, ""}, + {"(RowsColumnTypeNullable).ColumnTypeNullable", Method, 8, ""}, + {"(RowsColumnTypeNullable).Columns", Method, 8, ""}, + {"(RowsColumnTypeNullable).Next", Method, 8, ""}, + {"(RowsColumnTypePrecisionScale).Close", Method, 8, ""}, + {"(RowsColumnTypePrecisionScale).ColumnTypePrecisionScale", Method, 8, ""}, + {"(RowsColumnTypePrecisionScale).Columns", Method, 8, ""}, + {"(RowsColumnTypePrecisionScale).Next", Method, 8, ""}, + {"(RowsColumnTypeScanType).Close", Method, 8, ""}, + {"(RowsColumnTypeScanType).ColumnTypeScanType", Method, 8, ""}, + {"(RowsColumnTypeScanType).Columns", Method, 8, ""}, + {"(RowsColumnTypeScanType).Next", Method, 8, ""}, + {"(RowsNextResultSet).Close", Method, 8, ""}, + {"(RowsNextResultSet).Columns", Method, 8, ""}, + {"(RowsNextResultSet).HasNextResultSet", Method, 8, ""}, + {"(RowsNextResultSet).Next", Method, 8, ""}, + {"(RowsNextResultSet).NextResultSet", Method, 8, ""}, + {"(SessionResetter).ResetSession", Method, 10, ""}, + {"(Stmt).Close", Method, 0, ""}, + {"(Stmt).Exec", Method, 0, ""}, + {"(Stmt).NumInput", Method, 0, ""}, + {"(Stmt).Query", Method, 0, ""}, + {"(StmtExecContext).ExecContext", Method, 8, ""}, + {"(StmtQueryContext).QueryContext", Method, 8, ""}, + {"(Tx).Commit", Method, 0, ""}, + {"(Tx).Rollback", Method, 0, ""}, + {"(Validator).IsValid", Method, 15, ""}, + {"(ValueConverter).ConvertValue", Method, 0, ""}, + {"(Valuer).Value", Method, 0, ""}, {"Bool", Var, 0, ""}, {"ColumnConverter", Type, 0, ""}, {"Conn", Type, 0, ""}, @@ -1756,6 +1892,9 @@ var PackageSymbols = map[string][]Symbol{ {"(DecodeError).Error", Method, 0, ""}, {"(Tag).GoString", Method, 0, ""}, {"(Tag).String", Method, 0, ""}, + {"(Type).Common", Method, 0, ""}, + {"(Type).Size", Method, 0, ""}, + {"(Type).String", Method, 0, ""}, {"AddrType", Type, 0, ""}, {"AddrType.BasicType", Field, 0, ""}, {"ArrayType", Type, 0, ""}, @@ -3163,6 +3302,7 @@ var PackageSymbols = map[string][]Symbol{ {"R_LARCH_B16", Const, 20, ""}, {"R_LARCH_B21", Const, 20, ""}, {"R_LARCH_B26", Const, 20, ""}, + {"R_LARCH_CALL36", Const, 26, ""}, {"R_LARCH_CFA", Const, 22, ""}, {"R_LARCH_COPY", Const, 19, ""}, {"R_LARCH_DELETE", Const, 22, ""}, @@ -3220,11 +3360,25 @@ var PackageSymbols = map[string][]Symbol{ {"R_LARCH_SUB64", Const, 19, ""}, {"R_LARCH_SUB8", Const, 19, ""}, {"R_LARCH_SUB_ULEB128", Const, 22, ""}, + {"R_LARCH_TLS_DESC32", Const, 26, ""}, + {"R_LARCH_TLS_DESC64", Const, 26, ""}, + {"R_LARCH_TLS_DESC64_HI12", Const, 26, ""}, + {"R_LARCH_TLS_DESC64_LO20", Const, 26, ""}, + {"R_LARCH_TLS_DESC64_PC_HI12", Const, 26, ""}, + {"R_LARCH_TLS_DESC64_PC_LO20", Const, 26, ""}, + {"R_LARCH_TLS_DESC_CALL", Const, 26, ""}, + {"R_LARCH_TLS_DESC_HI20", Const, 26, ""}, + {"R_LARCH_TLS_DESC_LD", Const, 26, ""}, + {"R_LARCH_TLS_DESC_LO12", Const, 26, ""}, + {"R_LARCH_TLS_DESC_PCREL20_S2", Const, 26, ""}, + {"R_LARCH_TLS_DESC_PC_HI20", Const, 26, ""}, + {"R_LARCH_TLS_DESC_PC_LO12", Const, 26, ""}, {"R_LARCH_TLS_DTPMOD32", Const, 19, ""}, {"R_LARCH_TLS_DTPMOD64", Const, 19, ""}, {"R_LARCH_TLS_DTPREL32", Const, 19, ""}, {"R_LARCH_TLS_DTPREL64", Const, 19, ""}, {"R_LARCH_TLS_GD_HI20", Const, 20, ""}, + {"R_LARCH_TLS_GD_PCREL20_S2", Const, 26, ""}, {"R_LARCH_TLS_GD_PC_HI20", Const, 20, ""}, {"R_LARCH_TLS_IE64_HI12", Const, 20, ""}, {"R_LARCH_TLS_IE64_LO20", Const, 20, ""}, @@ -3235,11 +3389,15 @@ var PackageSymbols = map[string][]Symbol{ {"R_LARCH_TLS_IE_PC_HI20", Const, 20, ""}, {"R_LARCH_TLS_IE_PC_LO12", Const, 20, ""}, {"R_LARCH_TLS_LD_HI20", Const, 20, ""}, + {"R_LARCH_TLS_LD_PCREL20_S2", Const, 26, ""}, {"R_LARCH_TLS_LD_PC_HI20", Const, 20, ""}, {"R_LARCH_TLS_LE64_HI12", Const, 20, ""}, {"R_LARCH_TLS_LE64_LO20", Const, 20, ""}, + {"R_LARCH_TLS_LE_ADD_R", Const, 26, ""}, {"R_LARCH_TLS_LE_HI20", Const, 20, ""}, + {"R_LARCH_TLS_LE_HI20_R", Const, 26, ""}, {"R_LARCH_TLS_LE_LO12", Const, 20, ""}, + {"R_LARCH_TLS_LE_LO12_R", Const, 26, ""}, {"R_LARCH_TLS_TPREL32", Const, 19, ""}, {"R_LARCH_TLS_TPREL64", Const, 19, ""}, {"R_MIPS", Type, 6, ""}, @@ -3944,6 +4102,7 @@ var PackageSymbols = map[string][]Symbol{ {"(FatArch).ImportedSymbols", Method, 3, ""}, {"(FatArch).Section", Method, 3, ""}, {"(FatArch).Segment", Method, 3, ""}, + {"(Load).Raw", Method, 0, ""}, {"(LoadBytes).Raw", Method, 0, ""}, {"(LoadCmd).GoString", Method, 0, ""}, {"(LoadCmd).String", Method, 0, ""}, @@ -4590,6 +4749,12 @@ var PackageSymbols = map[string][]Symbol{ {"FS", Type, 16, ""}, }, "encoding": { + {"(BinaryAppender).AppendBinary", Method, 24, ""}, + {"(BinaryMarshaler).MarshalBinary", Method, 2, ""}, + {"(BinaryUnmarshaler).UnmarshalBinary", Method, 2, ""}, + {"(TextAppender).AppendText", Method, 24, ""}, + {"(TextMarshaler).MarshalText", Method, 2, ""}, + {"(TextUnmarshaler).UnmarshalText", Method, 2, ""}, {"BinaryAppender", Type, 24, ""}, {"BinaryMarshaler", Type, 2, ""}, {"BinaryUnmarshaler", Type, 2, ""}, @@ -4705,6 +4870,17 @@ var PackageSymbols = map[string][]Symbol{ {"URLEncoding", Var, 0, ""}, }, "encoding/binary": { + {"(AppendByteOrder).AppendUint16", Method, 19, ""}, + {"(AppendByteOrder).AppendUint32", Method, 19, ""}, + {"(AppendByteOrder).AppendUint64", Method, 19, ""}, + {"(AppendByteOrder).String", Method, 19, ""}, + {"(ByteOrder).PutUint16", Method, 0, ""}, + {"(ByteOrder).PutUint32", Method, 0, ""}, + {"(ByteOrder).PutUint64", Method, 0, ""}, + {"(ByteOrder).String", Method, 0, ""}, + {"(ByteOrder).Uint16", Method, 0, ""}, + {"(ByteOrder).Uint32", Method, 0, ""}, + {"(ByteOrder).Uint64", Method, 0, ""}, {"Append", Func, 23, "func(buf []byte, order ByteOrder, data any) ([]byte, error)"}, {"AppendByteOrder", Type, 19, ""}, {"AppendUvarint", Func, 19, "func(buf []byte, x uint64) []byte"}, @@ -4767,6 +4943,8 @@ var PackageSymbols = map[string][]Symbol{ {"(*Decoder).DecodeValue", Method, 0, ""}, {"(*Encoder).Encode", Method, 0, ""}, {"(*Encoder).EncodeValue", Method, 0, ""}, + {"(GobDecoder).GobDecode", Method, 0, ""}, + {"(GobEncoder).GobEncode", Method, 0, ""}, {"CommonType", Type, 0, ""}, {"CommonType.Id", Field, 0, ""}, {"CommonType.Name", Field, 0, ""}, @@ -4819,10 +4997,12 @@ var PackageSymbols = map[string][]Symbol{ {"(*UnsupportedTypeError).Error", Method, 0, ""}, {"(*UnsupportedValueError).Error", Method, 0, ""}, {"(Delim).String", Method, 5, ""}, + {"(Marshaler).MarshalJSON", Method, 0, ""}, {"(Number).Float64", Method, 1, ""}, {"(Number).Int64", Method, 1, ""}, {"(Number).String", Method, 1, ""}, {"(RawMessage).MarshalJSON", Method, 8, ""}, + {"(Unmarshaler).UnmarshalJSON", Method, 0, ""}, {"Compact", Func, 0, "func(dst *bytes.Buffer, src []byte) error"}, {"Decoder", Type, 0, ""}, {"Delim", Type, 5, ""}, @@ -4894,10 +5074,15 @@ var PackageSymbols = map[string][]Symbol{ {"(CharData).Copy", Method, 0, ""}, {"(Comment).Copy", Method, 0, ""}, {"(Directive).Copy", Method, 0, ""}, + {"(Marshaler).MarshalXML", Method, 2, ""}, + {"(MarshalerAttr).MarshalXMLAttr", Method, 2, ""}, {"(ProcInst).Copy", Method, 0, ""}, {"(StartElement).Copy", Method, 0, ""}, {"(StartElement).End", Method, 2, ""}, + {"(TokenReader).Token", Method, 10, ""}, {"(UnmarshalError).Error", Method, 0, ""}, + {"(Unmarshaler).UnmarshalXML", Method, 2, ""}, + {"(UnmarshalerAttr).UnmarshalXMLAttr", Method, 2, ""}, {"Attr", Type, 0, ""}, {"Attr.Name", Field, 0, ""}, {"Attr.Value", Field, 0, ""}, @@ -4984,6 +5169,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*String).Value", Method, 8, ""}, {"(Func).String", Method, 0, ""}, {"(Func).Value", Method, 8, ""}, + {"(Var).String", Method, 0, ""}, {"Do", Func, 0, "func(f func(KeyValue))"}, {"Float", Type, 0, ""}, {"Func", Type, 0, ""}, @@ -5039,6 +5225,11 @@ var PackageSymbols = map[string][]Symbol{ {"(*FlagSet).Var", Method, 0, ""}, {"(*FlagSet).Visit", Method, 0, ""}, {"(*FlagSet).VisitAll", Method, 0, ""}, + {"(Getter).Get", Method, 2, ""}, + {"(Getter).Set", Method, 2, ""}, + {"(Getter).String", Method, 2, ""}, + {"(Value).Set", Method, 0, ""}, + {"(Value).String", Method, 0, ""}, {"Arg", Func, 0, "func(i int) string"}, {"Args", Func, 0, "func() []string"}, {"Bool", Func, 0, "func(name string, value bool, usage string) *bool"}, @@ -5090,6 +5281,20 @@ var PackageSymbols = map[string][]Symbol{ {"VisitAll", Func, 0, "func(fn func(*Flag))"}, }, "fmt": { + {"(Formatter).Format", Method, 0, ""}, + {"(GoStringer).GoString", Method, 0, ""}, + {"(ScanState).Read", Method, 0, ""}, + {"(ScanState).ReadRune", Method, 0, ""}, + {"(ScanState).SkipSpace", Method, 0, ""}, + {"(ScanState).Token", Method, 0, ""}, + {"(ScanState).UnreadRune", Method, 0, ""}, + {"(ScanState).Width", Method, 0, ""}, + {"(Scanner).Scan", Method, 0, ""}, + {"(State).Flag", Method, 0, ""}, + {"(State).Precision", Method, 0, ""}, + {"(State).Width", Method, 0, ""}, + {"(State).Write", Method, 0, ""}, + {"(Stringer).String", Method, 0, ""}, {"Append", Func, 19, "func(b []byte, a ...any) []byte"}, {"Appendf", Func, 19, "func(b []byte, format string, a ...any) []byte"}, {"Appendln", Func, 19, "func(b []byte, a ...any) []byte"}, @@ -5248,7 +5453,18 @@ var PackageSymbols = map[string][]Symbol{ {"(CommentMap).Filter", Method, 1, ""}, {"(CommentMap).String", Method, 1, ""}, {"(CommentMap).Update", Method, 1, ""}, + {"(Decl).End", Method, 0, ""}, + {"(Decl).Pos", Method, 0, ""}, + {"(Expr).End", Method, 0, ""}, + {"(Expr).Pos", Method, 0, ""}, + {"(Node).End", Method, 0, ""}, + {"(Node).Pos", Method, 0, ""}, {"(ObjKind).String", Method, 0, ""}, + {"(Spec).End", Method, 0, ""}, + {"(Spec).Pos", Method, 0, ""}, + {"(Stmt).End", Method, 0, ""}, + {"(Stmt).Pos", Method, 0, ""}, + {"(Visitor).Visit", Method, 0, ""}, {"ArrayType", Type, 0, ""}, {"ArrayType.Elt", Field, 0, ""}, {"ArrayType.Lbrack", Field, 0, ""}, @@ -5271,6 +5487,7 @@ var PackageSymbols = map[string][]Symbol{ {"BasicLit", Type, 0, ""}, {"BasicLit.Kind", Field, 0, ""}, {"BasicLit.Value", Field, 0, ""}, + {"BasicLit.ValueEnd", Field, 26, ""}, {"BasicLit.ValuePos", Field, 0, ""}, {"BinaryExpr", Type, 0, ""}, {"BinaryExpr.Op", Field, 0, ""}, @@ -5320,7 +5537,6 @@ var PackageSymbols = map[string][]Symbol{ {"CompositeLit.Rbrace", Field, 0, ""}, {"CompositeLit.Type", Field, 0, ""}, {"Con", Const, 0, ""}, - {"Decl", Type, 0, ""}, {"DeclStmt", Type, 0, ""}, {"DeclStmt.Decl", Field, 0, ""}, {"DeferStmt", Type, 0, ""}, @@ -5341,7 +5557,6 @@ var PackageSymbols = map[string][]Symbol{ {"EmptyStmt", Type, 0, ""}, {"EmptyStmt.Implicit", Field, 5, ""}, {"EmptyStmt.Semicolon", Field, 0, ""}, - {"Expr", Type, 0, ""}, {"ExprStmt", Type, 0, ""}, {"ExprStmt.X", Field, 0, ""}, {"Field", Type, 0, ""}, @@ -5525,11 +5740,9 @@ var PackageSymbols = map[string][]Symbol{ {"SliceExpr.Slice3", Field, 2, ""}, {"SliceExpr.X", Field, 0, ""}, {"SortImports", Func, 0, "func(fset *token.FileSet, f *File)"}, - {"Spec", Type, 0, ""}, {"StarExpr", Type, 0, ""}, {"StarExpr.Star", Field, 0, ""}, {"StarExpr.X", Field, 0, ""}, - {"Stmt", Type, 0, ""}, {"StructType", Type, 0, ""}, {"StructType.Fields", Field, 0, ""}, {"StructType.Incomplete", Field, 0, ""}, @@ -5684,10 +5897,11 @@ var PackageSymbols = map[string][]Symbol{ {"(*SyntaxError).Error", Method, 16, ""}, {"(*TagExpr).Eval", Method, 16, ""}, {"(*TagExpr).String", Method, 16, ""}, + {"(Expr).Eval", Method, 16, ""}, + {"(Expr).String", Method, 16, ""}, {"AndExpr", Type, 16, ""}, {"AndExpr.X", Field, 16, ""}, {"AndExpr.Y", Field, 16, ""}, - {"Expr", Type, 16, ""}, {"GoVersion", Func, 21, "func(x Expr) string"}, {"IsGoBuild", Func, 16, "func(line string) bool"}, {"IsPlusBuild", Func, 16, "func(line string) bool"}, @@ -5706,6 +5920,9 @@ var PackageSymbols = map[string][]Symbol{ }, "go/constant": { {"(Kind).String", Method, 18, ""}, + {"(Value).ExactString", Method, 6, ""}, + {"(Value).Kind", Method, 5, ""}, + {"(Value).String", Method, 5, ""}, {"BinaryOp", Func, 5, "func(x_ Value, op token.Token, y_ Value) Value"}, {"BitLen", Func, 5, "func(x Value) int"}, {"Bool", Const, 5, ""}, @@ -5744,7 +5961,6 @@ var PackageSymbols = map[string][]Symbol{ {"UnaryOp", Func, 5, "func(op token.Token, y Value, prec uint) Value"}, {"Unknown", Const, 5, ""}, {"Val", Func, 13, "func(x Value) any"}, - {"Value", Type, 5, ""}, }, "go/doc": { {"(*Package).Filter", Method, 0, ""}, @@ -5828,7 +6044,6 @@ var PackageSymbols = map[string][]Symbol{ {"(*Printer).HTML", Method, 19, ""}, {"(*Printer).Markdown", Method, 19, ""}, {"(*Printer).Text", Method, 19, ""}, - {"Block", Type, 19, ""}, {"Code", Type, 19, ""}, {"Code.Text", Field, 19, ""}, {"DefaultLookupPackage", Func, 19, "func(name string) (importPath string, ok bool)"}, @@ -5873,7 +6088,6 @@ var PackageSymbols = map[string][]Symbol{ {"Printer.TextCodePrefix", Field, 19, ""}, {"Printer.TextPrefix", Field, 19, ""}, {"Printer.TextWidth", Field, 19, ""}, - {"Text", Type, 19, ""}, }, "go/format": { {"Node", Func, 1, "func(dst io.Writer, fset *token.FileSet, node any) error"}, @@ -5945,6 +6159,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*File).AddLineColumnInfo", Method, 11, ""}, {"(*File).AddLineInfo", Method, 0, ""}, {"(*File).Base", Method, 0, ""}, + {"(*File).End", Method, 26, ""}, {"(*File).Line", Method, 0, ""}, {"(*File).LineCount", Method, 0, ""}, {"(*File).LineStart", Method, 12, ""}, @@ -6307,6 +6522,22 @@ var PackageSymbols = map[string][]Symbol{ {"(Checker).PkgNameOf", Method, 22, ""}, {"(Checker).TypeOf", Method, 5, ""}, {"(Error).Error", Method, 5, ""}, + {"(Importer).Import", Method, 5, ""}, + {"(ImporterFrom).Import", Method, 6, ""}, + {"(ImporterFrom).ImportFrom", Method, 6, ""}, + {"(Object).Exported", Method, 5, ""}, + {"(Object).Id", Method, 5, ""}, + {"(Object).Name", Method, 5, ""}, + {"(Object).Parent", Method, 5, ""}, + {"(Object).Pkg", Method, 5, ""}, + {"(Object).Pos", Method, 5, ""}, + {"(Object).String", Method, 5, ""}, + {"(Object).Type", Method, 5, ""}, + {"(Sizes).Alignof", Method, 5, ""}, + {"(Sizes).Offsetsof", Method, 5, ""}, + {"(Sizes).Sizeof", Method, 5, ""}, + {"(Type).String", Method, 5, ""}, + {"(Type).Underlying", Method, 5, ""}, {"(TypeAndValue).Addressable", Method, 5, ""}, {"(TypeAndValue).Assignable", Method, 5, ""}, {"(TypeAndValue).HasOk", Method, 5, ""}, @@ -6445,7 +6676,6 @@ var PackageSymbols = map[string][]Symbol{ {"NewUnion", Func, 18, "func(terms []*Term) *Union"}, {"NewVar", Func, 5, "func(pos token.Pos, pkg *Package, name string, typ Type) *Var"}, {"Nil", Type, 5, ""}, - {"Object", Type, 5, ""}, {"ObjectString", Func, 5, "func(obj Object, qf Qualifier) string"}, {"Package", Type, 5, ""}, {"PackageVar", Const, 25, ""}, @@ -6516,6 +6746,33 @@ var PackageSymbols = map[string][]Symbol{ {"Lang", Func, 22, "func(x string) string"}, }, "hash": { + {"(Cloner).BlockSize", Method, 25, ""}, + {"(Cloner).Clone", Method, 25, ""}, + {"(Cloner).Reset", Method, 25, ""}, + {"(Cloner).Size", Method, 25, ""}, + {"(Cloner).Sum", Method, 25, ""}, + {"(Cloner).Write", Method, 25, ""}, + {"(Hash).BlockSize", Method, 0, ""}, + {"(Hash).Reset", Method, 0, ""}, + {"(Hash).Size", Method, 0, ""}, + {"(Hash).Sum", Method, 0, ""}, + {"(Hash).Write", Method, 0, ""}, + {"(Hash32).BlockSize", Method, 0, ""}, + {"(Hash32).Reset", Method, 0, ""}, + {"(Hash32).Size", Method, 0, ""}, + {"(Hash32).Sum", Method, 0, ""}, + {"(Hash32).Sum32", Method, 0, ""}, + {"(Hash32).Write", Method, 0, ""}, + {"(Hash64).BlockSize", Method, 0, ""}, + {"(Hash64).Reset", Method, 0, ""}, + {"(Hash64).Size", Method, 0, ""}, + {"(Hash64).Sum", Method, 0, ""}, + {"(Hash64).Sum64", Method, 0, ""}, + {"(Hash64).Write", Method, 0, ""}, + {"(XOF).BlockSize", Method, 25, ""}, + {"(XOF).Read", Method, 25, ""}, + {"(XOF).Reset", Method, 25, ""}, + {"(XOF).Write", Method, 25, ""}, {"Cloner", Type, 25, ""}, {"Hash", Type, 0, ""}, {"Hash32", Type, 0, ""}, @@ -6781,6 +7038,13 @@ var PackageSymbols = map[string][]Symbol{ {"(*YCbCr).SubImage", Method, 0, ""}, {"(*YCbCr).YCbCrAt", Method, 4, ""}, {"(*YCbCr).YOffset", Method, 0, ""}, + {"(Image).At", Method, 0, ""}, + {"(Image).Bounds", Method, 0, ""}, + {"(Image).ColorModel", Method, 0, ""}, + {"(PalettedImage).At", Method, 0, ""}, + {"(PalettedImage).Bounds", Method, 0, ""}, + {"(PalettedImage).ColorIndexAt", Method, 0, ""}, + {"(PalettedImage).ColorModel", Method, 0, ""}, {"(Point).Add", Method, 0, ""}, {"(Point).Div", Method, 0, ""}, {"(Point).Eq", Method, 0, ""}, @@ -6789,6 +7053,10 @@ var PackageSymbols = map[string][]Symbol{ {"(Point).Mul", Method, 0, ""}, {"(Point).String", Method, 0, ""}, {"(Point).Sub", Method, 0, ""}, + {"(RGBA64Image).At", Method, 17, ""}, + {"(RGBA64Image).Bounds", Method, 17, ""}, + {"(RGBA64Image).ColorModel", Method, 17, ""}, + {"(RGBA64Image).RGBA64At", Method, 17, ""}, {"(Rectangle).Add", Method, 0, ""}, {"(Rectangle).At", Method, 5, ""}, {"(Rectangle).Bounds", Method, 5, ""}, @@ -6913,8 +7181,10 @@ var PackageSymbols = map[string][]Symbol{ {"(Alpha).RGBA", Method, 0, ""}, {"(Alpha16).RGBA", Method, 0, ""}, {"(CMYK).RGBA", Method, 5, ""}, + {"(Color).RGBA", Method, 0, ""}, {"(Gray).RGBA", Method, 0, ""}, {"(Gray16).RGBA", Method, 0, ""}, + {"(Model).Convert", Method, 0, ""}, {"(NRGBA).RGBA", Method, 0, ""}, {"(NRGBA64).RGBA", Method, 0, ""}, {"(NYCbCrA).RGBA", Method, 6, ""}, @@ -6992,7 +7262,19 @@ var PackageSymbols = map[string][]Symbol{ {"WebSafe", Var, 2, ""}, }, "image/draw": { + {"(Drawer).Draw", Method, 2, ""}, + {"(Image).At", Method, 0, ""}, + {"(Image).Bounds", Method, 0, ""}, + {"(Image).ColorModel", Method, 0, ""}, + {"(Image).Set", Method, 0, ""}, {"(Op).Draw", Method, 2, ""}, + {"(Quantizer).Quantize", Method, 2, ""}, + {"(RGBA64Image).At", Method, 17, ""}, + {"(RGBA64Image).Bounds", Method, 17, ""}, + {"(RGBA64Image).ColorModel", Method, 17, ""}, + {"(RGBA64Image).RGBA64At", Method, 17, ""}, + {"(RGBA64Image).Set", Method, 17, ""}, + {"(RGBA64Image).SetRGBA64", Method, 17, ""}, {"Draw", Func, 0, "func(dst Image, r image.Rectangle, src image.Image, sp image.Point, op Op)"}, {"DrawMask", Func, 0, "func(dst Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op)"}, {"Drawer", Type, 2, ""}, @@ -7027,6 +7309,8 @@ var PackageSymbols = map[string][]Symbol{ }, "image/jpeg": { {"(FormatError).Error", Method, 0, ""}, + {"(Reader).Read", Method, 0, ""}, + {"(Reader).ReadByte", Method, 0, ""}, {"(UnsupportedError).Error", Method, 0, ""}, {"Decode", Func, 0, "func(r io.Reader) (image.Image, error)"}, {"DecodeConfig", Func, 0, "func(r io.Reader) (image.Config, error)"}, @@ -7040,6 +7324,8 @@ var PackageSymbols = map[string][]Symbol{ }, "image/png": { {"(*Encoder).Encode", Method, 4, ""}, + {"(EncoderBufferPool).Get", Method, 9, ""}, + {"(EncoderBufferPool).Put", Method, 9, ""}, {"(FormatError).Error", Method, 0, ""}, {"(UnsupportedError).Error", Method, 0, ""}, {"BestCompression", Const, 4, ""}, @@ -7083,6 +7369,41 @@ var PackageSymbols = map[string][]Symbol{ {"(*SectionReader).ReadAt", Method, 0, ""}, {"(*SectionReader).Seek", Method, 0, ""}, {"(*SectionReader).Size", Method, 0, ""}, + {"(ByteReader).ReadByte", Method, 0, ""}, + {"(ByteScanner).ReadByte", Method, 0, ""}, + {"(ByteScanner).UnreadByte", Method, 0, ""}, + {"(ByteWriter).WriteByte", Method, 1, ""}, + {"(Closer).Close", Method, 0, ""}, + {"(ReadCloser).Close", Method, 0, ""}, + {"(ReadCloser).Read", Method, 0, ""}, + {"(ReadSeekCloser).Close", Method, 16, ""}, + {"(ReadSeekCloser).Read", Method, 16, ""}, + {"(ReadSeekCloser).Seek", Method, 16, ""}, + {"(ReadSeeker).Read", Method, 0, ""}, + {"(ReadSeeker).Seek", Method, 0, ""}, + {"(ReadWriteCloser).Close", Method, 0, ""}, + {"(ReadWriteCloser).Read", Method, 0, ""}, + {"(ReadWriteCloser).Write", Method, 0, ""}, + {"(ReadWriteSeeker).Read", Method, 0, ""}, + {"(ReadWriteSeeker).Seek", Method, 0, ""}, + {"(ReadWriteSeeker).Write", Method, 0, ""}, + {"(ReadWriter).Read", Method, 0, ""}, + {"(ReadWriter).Write", Method, 0, ""}, + {"(Reader).Read", Method, 0, ""}, + {"(ReaderAt).ReadAt", Method, 0, ""}, + {"(ReaderFrom).ReadFrom", Method, 0, ""}, + {"(RuneReader).ReadRune", Method, 0, ""}, + {"(RuneScanner).ReadRune", Method, 0, ""}, + {"(RuneScanner).UnreadRune", Method, 0, ""}, + {"(Seeker).Seek", Method, 0, ""}, + {"(StringWriter).WriteString", Method, 12, ""}, + {"(WriteCloser).Close", Method, 0, ""}, + {"(WriteCloser).Write", Method, 0, ""}, + {"(WriteSeeker).Seek", Method, 0, ""}, + {"(WriteSeeker).Write", Method, 0, ""}, + {"(Writer).Write", Method, 0, ""}, + {"(WriterAt).WriteAt", Method, 0, ""}, + {"(WriterTo).WriteTo", Method, 0, ""}, {"ByteReader", Type, 0, ""}, {"ByteScanner", Type, 0, ""}, {"ByteWriter", Type, 1, ""}, @@ -7142,11 +7463,42 @@ var PackageSymbols = map[string][]Symbol{ {"(*PathError).Error", Method, 16, ""}, {"(*PathError).Timeout", Method, 16, ""}, {"(*PathError).Unwrap", Method, 16, ""}, + {"(DirEntry).Info", Method, 16, ""}, + {"(DirEntry).IsDir", Method, 16, ""}, + {"(DirEntry).Name", Method, 16, ""}, + {"(DirEntry).Type", Method, 16, ""}, + {"(FS).Open", Method, 16, ""}, + {"(File).Close", Method, 16, ""}, + {"(File).Read", Method, 16, ""}, + {"(File).Stat", Method, 16, ""}, + {"(FileInfo).IsDir", Method, 16, ""}, + {"(FileInfo).ModTime", Method, 16, ""}, + {"(FileInfo).Mode", Method, 16, ""}, + {"(FileInfo).Name", Method, 16, ""}, + {"(FileInfo).Size", Method, 16, ""}, + {"(FileInfo).Sys", Method, 16, ""}, {"(FileMode).IsDir", Method, 16, ""}, {"(FileMode).IsRegular", Method, 16, ""}, {"(FileMode).Perm", Method, 16, ""}, {"(FileMode).String", Method, 16, ""}, {"(FileMode).Type", Method, 16, ""}, + {"(GlobFS).Glob", Method, 16, ""}, + {"(GlobFS).Open", Method, 16, ""}, + {"(ReadDirFS).Open", Method, 16, ""}, + {"(ReadDirFS).ReadDir", Method, 16, ""}, + {"(ReadDirFile).Close", Method, 16, ""}, + {"(ReadDirFile).Read", Method, 16, ""}, + {"(ReadDirFile).ReadDir", Method, 16, ""}, + {"(ReadDirFile).Stat", Method, 16, ""}, + {"(ReadFileFS).Open", Method, 16, ""}, + {"(ReadFileFS).ReadFile", Method, 16, ""}, + {"(ReadLinkFS).Lstat", Method, 25, ""}, + {"(ReadLinkFS).Open", Method, 25, ""}, + {"(ReadLinkFS).ReadLink", Method, 25, ""}, + {"(StatFS).Open", Method, 16, ""}, + {"(StatFS).Stat", Method, 16, ""}, + {"(SubFS).Open", Method, 16, ""}, + {"(SubFS).Sub", Method, 16, ""}, {"DirEntry", Type, 16, ""}, {"ErrClosed", Var, 16, ""}, {"ErrExist", Var, 16, ""}, @@ -7299,12 +7651,18 @@ var PackageSymbols = map[string][]Symbol{ {"(*TextHandler).WithGroup", Method, 21, ""}, {"(Attr).Equal", Method, 21, ""}, {"(Attr).String", Method, 21, ""}, + {"(Handler).Enabled", Method, 21, ""}, + {"(Handler).Handle", Method, 21, ""}, + {"(Handler).WithAttrs", Method, 21, ""}, + {"(Handler).WithGroup", Method, 21, ""}, {"(Kind).String", Method, 21, ""}, {"(Level).AppendText", Method, 24, ""}, {"(Level).Level", Method, 21, ""}, {"(Level).MarshalJSON", Method, 21, ""}, {"(Level).MarshalText", Method, 21, ""}, {"(Level).String", Method, 21, ""}, + {"(Leveler).Level", Method, 21, ""}, + {"(LogValuer).LogValue", Method, 21, ""}, {"(Record).Attrs", Method, 21, ""}, {"(Record).Clone", Method, 21, ""}, {"(Record).NumAttrs", Method, 21, ""}, @@ -7833,6 +8191,11 @@ var PackageSymbols = map[string][]Symbol{ {"(*Rand).Uint32", Method, 0, ""}, {"(*Rand).Uint64", Method, 8, ""}, {"(*Zipf).Uint64", Method, 0, ""}, + {"(Source).Int63", Method, 0, ""}, + {"(Source).Seed", Method, 0, ""}, + {"(Source64).Int63", Method, 8, ""}, + {"(Source64).Seed", Method, 8, ""}, + {"(Source64).Uint64", Method, 8, ""}, {"ExpFloat64", Func, 0, "func() float64"}, {"Float32", Func, 0, "func() float32"}, {"Float64", Func, 0, "func() float64"}, @@ -7888,6 +8251,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*Rand).Uint64N", Method, 22, ""}, {"(*Rand).UintN", Method, 22, ""}, {"(*Zipf).Uint64", Method, 22, ""}, + {"(Source).Uint64", Method, 22, ""}, {"ChaCha8", Type, 22, ""}, {"ExpFloat64", Func, 22, "func() float64"}, {"Float32", Func, 22, "func() float32"}, @@ -7951,6 +8315,10 @@ var PackageSymbols = map[string][]Symbol{ {"(*Writer).FormDataContentType", Method, 0, ""}, {"(*Writer).SetBoundary", Method, 1, ""}, {"(*Writer).WriteField", Method, 0, ""}, + {"(File).Close", Method, 0, ""}, + {"(File).Read", Method, 0, ""}, + {"(File).ReadAt", Method, 0, ""}, + {"(File).Seek", Method, 0, ""}, {"ErrMessageTooLarge", Var, 9, ""}, {"File", Type, 0, ""}, {"FileContentDisposition", Func, 25, "func(fieldname string, filename string) string"}, @@ -8135,6 +8503,19 @@ var PackageSymbols = map[string][]Symbol{ {"(*UnixListener).SetDeadline", Method, 0, ""}, {"(*UnixListener).SetUnlinkOnClose", Method, 8, ""}, {"(*UnixListener).SyscallConn", Method, 10, ""}, + {"(Addr).Network", Method, 0, ""}, + {"(Addr).String", Method, 0, ""}, + {"(Conn).Close", Method, 0, ""}, + {"(Conn).LocalAddr", Method, 0, ""}, + {"(Conn).Read", Method, 0, ""}, + {"(Conn).RemoteAddr", Method, 0, ""}, + {"(Conn).SetDeadline", Method, 0, ""}, + {"(Conn).SetReadDeadline", Method, 0, ""}, + {"(Conn).SetWriteDeadline", Method, 0, ""}, + {"(Conn).Write", Method, 0, ""}, + {"(Error).Error", Method, 0, ""}, + {"(Error).Temporary", Method, 0, ""}, + {"(Error).Timeout", Method, 0, ""}, {"(Flags).String", Method, 0, ""}, {"(HardwareAddr).String", Method, 0, ""}, {"(IP).AppendText", Method, 24, ""}, @@ -8158,6 +8539,16 @@ var PackageSymbols = map[string][]Symbol{ {"(InvalidAddrError).Error", Method, 0, ""}, {"(InvalidAddrError).Temporary", Method, 0, ""}, {"(InvalidAddrError).Timeout", Method, 0, ""}, + {"(Listener).Accept", Method, 0, ""}, + {"(Listener).Addr", Method, 0, ""}, + {"(Listener).Close", Method, 0, ""}, + {"(PacketConn).Close", Method, 0, ""}, + {"(PacketConn).LocalAddr", Method, 0, ""}, + {"(PacketConn).ReadFrom", Method, 0, ""}, + {"(PacketConn).SetDeadline", Method, 0, ""}, + {"(PacketConn).SetReadDeadline", Method, 0, ""}, + {"(PacketConn).SetWriteDeadline", Method, 0, ""}, + {"(PacketConn).WriteTo", Method, 0, ""}, {"(UnknownNetworkError).Error", Method, 0, ""}, {"(UnknownNetworkError).Temporary", Method, 0, ""}, {"(UnknownNetworkError).Timeout", Method, 0, ""}, @@ -8333,6 +8724,14 @@ var PackageSymbols = map[string][]Symbol{ {"(*Client).Head", Method, 0, ""}, {"(*Client).Post", Method, 0, ""}, {"(*Client).PostForm", Method, 0, ""}, + {"(*ClientConn).Available", Method, 26, ""}, + {"(*ClientConn).Close", Method, 26, ""}, + {"(*ClientConn).Err", Method, 26, ""}, + {"(*ClientConn).InFlight", Method, 26, ""}, + {"(*ClientConn).Release", Method, 26, ""}, + {"(*ClientConn).Reserve", Method, 26, ""}, + {"(*ClientConn).RoundTrip", Method, 26, ""}, + {"(*ClientConn).SetStateHook", Method, 26, ""}, {"(*Cookie).String", Method, 0, ""}, {"(*Cookie).Valid", Method, 18, ""}, {"(*CrossOriginProtection).AddInsecureBypassPattern", Method, 25, ""}, @@ -8392,10 +8791,22 @@ var PackageSymbols = map[string][]Symbol{ {"(*Transport).CancelRequest", Method, 1, ""}, {"(*Transport).Clone", Method, 13, ""}, {"(*Transport).CloseIdleConnections", Method, 0, ""}, + {"(*Transport).NewClientConn", Method, 26, ""}, {"(*Transport).RegisterProtocol", Method, 0, ""}, {"(*Transport).RoundTrip", Method, 0, ""}, + {"(CloseNotifier).CloseNotify", Method, 1, ""}, {"(ConnState).String", Method, 3, ""}, + {"(CookieJar).Cookies", Method, 0, ""}, + {"(CookieJar).SetCookies", Method, 0, ""}, {"(Dir).Open", Method, 0, ""}, + {"(File).Close", Method, 0, ""}, + {"(File).Read", Method, 0, ""}, + {"(File).Readdir", Method, 0, ""}, + {"(File).Seek", Method, 0, ""}, + {"(File).Stat", Method, 0, ""}, + {"(FileSystem).Open", Method, 0, ""}, + {"(Flusher).Flush", Method, 0, ""}, + {"(Handler).ServeHTTP", Method, 0, ""}, {"(HandlerFunc).ServeHTTP", Method, 0, ""}, {"(Header).Add", Method, 0, ""}, {"(Header).Clone", Method, 13, ""}, @@ -8405,10 +8816,16 @@ var PackageSymbols = map[string][]Symbol{ {"(Header).Values", Method, 14, ""}, {"(Header).Write", Method, 0, ""}, {"(Header).WriteSubset", Method, 0, ""}, + {"(Hijacker).Hijack", Method, 0, ""}, {"(Protocols).HTTP1", Method, 24, ""}, {"(Protocols).HTTP2", Method, 24, ""}, {"(Protocols).String", Method, 24, ""}, {"(Protocols).UnencryptedHTTP2", Method, 24, ""}, + {"(Pusher).Push", Method, 8, ""}, + {"(ResponseWriter).Header", Method, 0, ""}, + {"(ResponseWriter).Write", Method, 0, ""}, + {"(ResponseWriter).WriteHeader", Method, 0, ""}, + {"(RoundTripper).RoundTrip", Method, 0, ""}, {"AllowQuerySemicolons", Func, 17, "func(h Handler) Handler"}, {"CanonicalHeaderKey", Func, 0, "func(s string) string"}, {"Client", Type, 0, ""}, @@ -8416,6 +8833,7 @@ var PackageSymbols = map[string][]Symbol{ {"Client.Jar", Field, 0, ""}, {"Client.Timeout", Field, 3, ""}, {"Client.Transport", Field, 0, ""}, + {"ClientConn", Type, 26, ""}, {"CloseNotifier", Type, 1, ""}, {"ConnState", Type, 3, ""}, {"Cookie", Type, 0, ""}, @@ -8726,6 +9144,8 @@ var PackageSymbols = map[string][]Symbol{ "net/http/cookiejar": { {"(*Jar).Cookies", Method, 1, ""}, {"(*Jar).SetCookies", Method, 1, ""}, + {"(PublicSuffixList).PublicSuffix", Method, 1, ""}, + {"(PublicSuffixList).String", Method, 1, ""}, {"Jar", Type, 1, ""}, {"New", Func, 1, "func(o *Options) (*Jar, error)"}, {"Options", Type, 1, ""}, @@ -8819,6 +9239,8 @@ var PackageSymbols = map[string][]Symbol{ {"(*ServerConn).Pending", Method, 0, ""}, {"(*ServerConn).Read", Method, 0, ""}, {"(*ServerConn).Write", Method, 0, ""}, + {"(BufferPool).Get", Method, 6, ""}, + {"(BufferPool).Put", Method, 6, ""}, {"BufferPool", Type, 6, ""}, {"ClientConn", Type, 0, ""}, {"DumpRequest", Func, 0, "func(req *http.Request, body bool) ([]byte, error)"}, @@ -8972,6 +9394,14 @@ var PackageSymbols = map[string][]Symbol{ {"(*Server).ServeConn", Method, 0, ""}, {"(*Server).ServeHTTP", Method, 0, ""}, {"(*Server).ServeRequest", Method, 0, ""}, + {"(ClientCodec).Close", Method, 0, ""}, + {"(ClientCodec).ReadResponseBody", Method, 0, ""}, + {"(ClientCodec).ReadResponseHeader", Method, 0, ""}, + {"(ClientCodec).WriteRequest", Method, 0, ""}, + {"(ServerCodec).Close", Method, 0, ""}, + {"(ServerCodec).ReadRequestBody", Method, 0, ""}, + {"(ServerCodec).ReadRequestHeader", Method, 0, ""}, + {"(ServerCodec).WriteResponse", Method, 0, ""}, {"(ServerError).Error", Method, 0, ""}, {"Accept", Func, 0, "func(lis net.Listener)"}, {"Call", Type, 0, ""}, @@ -9030,6 +9460,8 @@ var PackageSymbols = map[string][]Symbol{ {"(*Client).StartTLS", Method, 0, ""}, {"(*Client).TLSConnectionState", Method, 5, ""}, {"(*Client).Verify", Method, 0, ""}, + {"(Auth).Next", Method, 0, ""}, + {"(Auth).Start", Method, 0, ""}, {"Auth", Type, 0, ""}, {"CRAMMD5Auth", Func, 0, "func(username string, secret string) Auth"}, {"Client", Type, 0, ""}, @@ -9241,10 +9673,18 @@ var PackageSymbols = map[string][]Symbol{ {"(*SyscallError).Error", Method, 0, ""}, {"(*SyscallError).Timeout", Method, 10, ""}, {"(*SyscallError).Unwrap", Method, 13, ""}, + {"(FileInfo).IsDir", Method, 0, ""}, + {"(FileInfo).ModTime", Method, 0, ""}, + {"(FileInfo).Mode", Method, 0, ""}, + {"(FileInfo).Name", Method, 0, ""}, + {"(FileInfo).Size", Method, 0, ""}, + {"(FileInfo).Sys", Method, 0, ""}, {"(FileMode).IsDir", Method, 0, ""}, {"(FileMode).IsRegular", Method, 1, ""}, {"(FileMode).Perm", Method, 0, ""}, {"(FileMode).String", Method, 0, ""}, + {"(Signal).Signal", Method, 0, ""}, + {"(Signal).String", Method, 0, ""}, {"Args", Var, 0, ""}, {"Chdir", Func, 0, "func(dir string) error"}, {"Chmod", Func, 0, "func(name string, mode FileMode) error"}, @@ -9521,6 +9961,45 @@ var PackageSymbols = map[string][]Symbol{ {"(StructField).IsExported", Method, 17, ""}, {"(StructTag).Get", Method, 0, ""}, {"(StructTag).Lookup", Method, 7, ""}, + {"(Type).Align", Method, 0, ""}, + {"(Type).AssignableTo", Method, 0, ""}, + {"(Type).Bits", Method, 0, ""}, + {"(Type).CanSeq", Method, 23, ""}, + {"(Type).CanSeq2", Method, 23, ""}, + {"(Type).ChanDir", Method, 0, ""}, + {"(Type).Comparable", Method, 4, ""}, + {"(Type).ConvertibleTo", Method, 1, ""}, + {"(Type).Elem", Method, 0, ""}, + {"(Type).Field", Method, 0, ""}, + {"(Type).FieldAlign", Method, 0, ""}, + {"(Type).FieldByIndex", Method, 0, ""}, + {"(Type).FieldByName", Method, 0, ""}, + {"(Type).FieldByNameFunc", Method, 0, ""}, + {"(Type).Fields", Method, 26, ""}, + {"(Type).Implements", Method, 0, ""}, + {"(Type).In", Method, 0, ""}, + {"(Type).Ins", Method, 26, ""}, + {"(Type).IsVariadic", Method, 0, ""}, + {"(Type).Key", Method, 0, ""}, + {"(Type).Kind", Method, 0, ""}, + {"(Type).Len", Method, 0, ""}, + {"(Type).Method", Method, 0, ""}, + {"(Type).MethodByName", Method, 0, ""}, + {"(Type).Methods", Method, 26, ""}, + {"(Type).Name", Method, 0, ""}, + {"(Type).NumField", Method, 0, ""}, + {"(Type).NumIn", Method, 0, ""}, + {"(Type).NumMethod", Method, 0, ""}, + {"(Type).NumOut", Method, 0, ""}, + {"(Type).Out", Method, 0, ""}, + {"(Type).Outs", Method, 26, ""}, + {"(Type).OverflowComplex", Method, 23, ""}, + {"(Type).OverflowFloat", Method, 23, ""}, + {"(Type).OverflowInt", Method, 23, ""}, + {"(Type).OverflowUint", Method, 23, ""}, + {"(Type).PkgPath", Method, 0, ""}, + {"(Type).Size", Method, 0, ""}, + {"(Type).String", Method, 0, ""}, {"(Value).Addr", Method, 0, ""}, {"(Value).Bool", Method, 0, ""}, {"(Value).Bytes", Method, 0, ""}, @@ -9547,6 +10026,7 @@ var PackageSymbols = map[string][]Symbol{ {"(Value).FieldByIndexErr", Method, 18, ""}, {"(Value).FieldByName", Method, 0, ""}, {"(Value).FieldByNameFunc", Method, 0, ""}, + {"(Value).Fields", Method, 26, ""}, {"(Value).Float", Method, 0, ""}, {"(Value).Grow", Method, 20, ""}, {"(Value).Index", Method, 0, ""}, @@ -9563,6 +10043,7 @@ var PackageSymbols = map[string][]Symbol{ {"(Value).MapRange", Method, 12, ""}, {"(Value).Method", Method, 0, ""}, {"(Value).MethodByName", Method, 0, ""}, + {"(Value).Methods", Method, 26, ""}, {"(Value).NumField", Method, 0, ""}, {"(Value).NumMethod", Method, 0, ""}, {"(Value).OverflowComplex", Method, 0, ""}, @@ -9678,7 +10159,6 @@ var PackageSymbols = map[string][]Symbol{ {"StructOf", Func, 7, "func(fields []StructField) Type"}, {"StructTag", Type, 0, ""}, {"Swapper", Func, 8, "func(slice any) func(i int, j int)"}, - {"Type", Type, 0, ""}, {"TypeAssert", Func, 25, "func[T any](v Value) (T, bool)"}, {"TypeFor", Func, 22, "func[T any]() Type"}, {"TypeOf", Func, 0, "func(i any) Type"}, @@ -9880,6 +10360,8 @@ var PackageSymbols = map[string][]Symbol{ {"(*TypeAssertionError).Error", Method, 0, ""}, {"(*TypeAssertionError).RuntimeError", Method, 0, ""}, {"(Cleanup).Stop", Method, 24, ""}, + {"(Error).Error", Method, 0, ""}, + {"(Error).RuntimeError", Method, 0, ""}, {"AddCleanup", Func, 24, "func[T, S any](ptr *T, cleanup func(S), arg S) Cleanup"}, {"BlockProfile", Func, 1, "func(p []BlockProfileRecord) (n int, ok bool)"}, {"BlockProfileRecord", Type, 1, ""}, @@ -10154,6 +10636,9 @@ var PackageSymbols = map[string][]Symbol{ {"(IntSlice).Search", Method, 0, ""}, {"(IntSlice).Sort", Method, 0, ""}, {"(IntSlice).Swap", Method, 0, ""}, + {"(Interface).Len", Method, 0, ""}, + {"(Interface).Less", Method, 0, ""}, + {"(Interface).Swap", Method, 0, ""}, {"(StringSlice).Len", Method, 0, ""}, {"(StringSlice).Less", Method, 0, ""}, {"(StringSlice).Search", Method, 0, ""}, @@ -10345,6 +10830,8 @@ var PackageSymbols = map[string][]Symbol{ {"(*WaitGroup).Done", Method, 0, ""}, {"(*WaitGroup).Go", Method, 25, ""}, {"(*WaitGroup).Wait", Method, 0, ""}, + {"(Locker).Lock", Method, 0, ""}, + {"(Locker).Unlock", Method, 0, ""}, {"Cond", Type, 0, ""}, {"Cond.L", Field, 0, ""}, {"Locker", Type, 0, ""}, @@ -10486,10 +10973,14 @@ var PackageSymbols = map[string][]Symbol{ {"(*Timeval).Nano", Method, 0, ""}, {"(*Timeval).Nanoseconds", Method, 0, ""}, {"(*Timeval).Unix", Method, 0, ""}, + {"(Conn).SyscallConn", Method, 9, ""}, {"(Errno).Error", Method, 0, ""}, {"(Errno).Is", Method, 13, ""}, {"(Errno).Temporary", Method, 0, ""}, {"(Errno).Timeout", Method, 0, ""}, + {"(RawConn).Control", Method, 9, ""}, + {"(RawConn).Read", Method, 9, ""}, + {"(RawConn).Write", Method, 9, ""}, {"(Signal).Signal", Method, 0, ""}, {"(Signal).String", Method, 0, ""}, {"(Token).Close", Method, 0, ""}, @@ -14409,7 +14900,7 @@ var PackageSymbols = map[string][]Symbol{ {"RouteMessage.Data", Field, 0, ""}, {"RouteMessage.Header", Field, 0, ""}, {"RouteRIB", Func, 0, ""}, - {"RoutingMessage", Type, 0, ""}, + {"RoutingMessage", Type, 14, ""}, {"RtAttr", Type, 0, ""}, {"RtAttr.Len", Field, 0, ""}, {"RtAttr.Type", Field, 0, ""}, @@ -15895,7 +16386,6 @@ var PackageSymbols = map[string][]Symbol{ {"SockFprog.Filter", Field, 0, ""}, {"SockFprog.Len", Field, 0, ""}, {"SockFprog.Pad_cgo_0", Field, 0, ""}, - {"Sockaddr", Type, 0, ""}, {"SockaddrDatalink", Type, 0, ""}, {"SockaddrDatalink.Alen", Field, 0, ""}, {"SockaddrDatalink.Data", Field, 0, ""}, @@ -16801,6 +17291,29 @@ var PackageSymbols = map[string][]Symbol{ {"(BenchmarkResult).MemString", Method, 1, ""}, {"(BenchmarkResult).NsPerOp", Method, 0, ""}, {"(BenchmarkResult).String", Method, 0, ""}, + {"(TB).ArtifactDir", Method, 26, ""}, + {"(TB).Attr", Method, 25, ""}, + {"(TB).Chdir", Method, 24, ""}, + {"(TB).Cleanup", Method, 14, ""}, + {"(TB).Context", Method, 24, ""}, + {"(TB).Error", Method, 2, ""}, + {"(TB).Errorf", Method, 2, ""}, + {"(TB).Fail", Method, 2, ""}, + {"(TB).FailNow", Method, 2, ""}, + {"(TB).Failed", Method, 2, ""}, + {"(TB).Fatal", Method, 2, ""}, + {"(TB).Fatalf", Method, 2, ""}, + {"(TB).Helper", Method, 9, ""}, + {"(TB).Log", Method, 2, ""}, + {"(TB).Logf", Method, 2, ""}, + {"(TB).Name", Method, 8, ""}, + {"(TB).Output", Method, 25, ""}, + {"(TB).Setenv", Method, 17, ""}, + {"(TB).Skip", Method, 2, ""}, + {"(TB).SkipNow", Method, 2, ""}, + {"(TB).Skipf", Method, 2, ""}, + {"(TB).Skipped", Method, 2, ""}, + {"(TB).TempDir", Method, 15, ""}, {"AllocsPerRun", Func, 1, "func(runs int, f func()) (avg float64)"}, {"B", Type, 0, ""}, {"B.N", Field, 0, ""}, @@ -16851,7 +17364,6 @@ var PackageSymbols = map[string][]Symbol{ {"RunTests", Func, 0, "func(matchString func(pat string, str string) (bool, error), tests []InternalTest) (ok bool)"}, {"Short", Func, 0, "func() bool"}, {"T", Type, 0, ""}, - {"TB", Type, 2, ""}, {"Testing", Func, 21, "func() bool"}, {"Verbose", Func, 1, "func() bool"}, }, @@ -16887,6 +17399,7 @@ var PackageSymbols = map[string][]Symbol{ "testing/quick": { {"(*CheckEqualError).Error", Method, 0, ""}, {"(*CheckError).Error", Method, 0, ""}, + {"(Generator).Generate", Method, 0, ""}, {"(SetupError).Error", Method, 0, ""}, {"Check", Func, 0, "func(f any, config *Config) error"}, {"CheckEqual", Func, 0, "func(f any, g any, config *Config) error"}, @@ -17093,6 +17606,10 @@ var PackageSymbols = map[string][]Symbol{ {"(ListNode).Position", Method, 1, ""}, {"(ListNode).Type", Method, 0, ""}, {"(NilNode).Position", Method, 1, ""}, + {"(Node).Copy", Method, 0, ""}, + {"(Node).Position", Method, 1, ""}, + {"(Node).String", Method, 0, ""}, + {"(Node).Type", Method, 0, ""}, {"(NodeType).Type", Method, 0, ""}, {"(NumberNode).Position", Method, 1, ""}, {"(NumberNode).Type", Method, 0, ""}, diff --git a/src/cmd/vendor/golang.org/x/tools/internal/stdlib/stdlib.go b/src/cmd/vendor/golang.org/x/tools/internal/stdlib/stdlib.go index e223e0f3405..59a5de36a23 100644 --- a/src/cmd/vendor/golang.org/x/tools/internal/stdlib/stdlib.go +++ b/src/cmd/vendor/golang.org/x/tools/internal/stdlib/stdlib.go @@ -39,7 +39,7 @@ const ( Var // "EOF" Const // "Pi" Field // "Point.X" - Method // "(*Buffer).Grow" + Method // "(*Buffer).Grow" or "(Reader).Read" ) func (kind Kind) String() string { diff --git a/src/cmd/vendor/modules.txt b/src/cmd/vendor/modules.txt index a3c2d201774..7810dcf8b12 100644 --- a/src/cmd/vendor/modules.txt +++ b/src/cmd/vendor/modules.txt @@ -73,7 +73,7 @@ golang.org/x/text/internal/tag golang.org/x/text/language golang.org/x/text/transform golang.org/x/text/unicode/norm -# golang.org/x/tools v0.39.1-0.20251120214200-68724afed209 +# golang.org/x/tools v0.39.1-0.20251130212600-1ad6f3d02713 ## explicit; go 1.24.0 golang.org/x/tools/cmd/bisect golang.org/x/tools/cover From dc913c316a00d7233e14c80fc4d829a79f5c97b6 Mon Sep 17 00:00:00 2001 From: Dmitri Shuralyov Date: Mon, 1 Dec 2025 10:32:57 -0500 Subject: [PATCH 112/140] all: update vendored dependencies The Go 1.26 code freeze has recently started. This is a time to update all golang.org/x/... module versions that contribute packages to the std and cmd modules in the standard library to latest master versions. For #36905. [git-generate] go install golang.org/x/build/cmd/updatestd@latest go install golang.org/x/tools/cmd/bundle@latest updatestd -goroot=$(pwd) -branch=master Change-Id: I39c68d4c36d0c83ac07c3cda3c4d042bb32a9624 Reviewed-on: https://go-review.googlesource.com/c/go/+/725480 LUCI-TryBot-Result: Go LUCI Reviewed-by: Dmitri Shuralyov Auto-Submit: Dmitri Shuralyov Reviewed-by: Alan Donovan --- src/cmd/go.mod | 14 ++++---- src/cmd/go.sum | 32 +++++++++---------- .../golang.org/x/mod/sumdb/note/note.go | 9 +----- .../vendor/golang.org/x/sys/unix/mkerrors.sh | 3 +- .../golang.org/x/sys/unix/zerrors_linux.go | 2 ++ .../x/sys/unix/zerrors_linux_386.go | 2 ++ .../x/sys/unix/zerrors_linux_amd64.go | 2 ++ .../x/sys/unix/zerrors_linux_arm.go | 2 ++ .../x/sys/unix/zerrors_linux_arm64.go | 2 ++ .../x/sys/unix/zerrors_linux_loong64.go | 2 ++ .../x/sys/unix/zerrors_linux_mips.go | 2 ++ .../x/sys/unix/zerrors_linux_mips64.go | 2 ++ .../x/sys/unix/zerrors_linux_mips64le.go | 2 ++ .../x/sys/unix/zerrors_linux_mipsle.go | 2 ++ .../x/sys/unix/zerrors_linux_ppc.go | 2 ++ .../x/sys/unix/zerrors_linux_ppc64.go | 2 ++ .../x/sys/unix/zerrors_linux_ppc64le.go | 2 ++ .../x/sys/unix/zerrors_linux_riscv64.go | 2 ++ .../x/sys/unix/zerrors_linux_s390x.go | 2 ++ .../x/sys/unix/zerrors_linux_sparc64.go | 2 ++ .../x/sys/unix/ztypes_netbsd_arm.go | 2 +- src/cmd/vendor/golang.org/x/term/terminal.go | 6 ++-- src/cmd/vendor/modules.txt | 18 +++++------ src/go.mod | 8 ++--- src/go.sum | 16 +++++----- src/vendor/golang.org/x/sys/cpu/cpu.go | 3 -- src/vendor/golang.org/x/sys/cpu/cpu_arm64.go | 20 ++---------- src/vendor/golang.org/x/sys/cpu/cpu_arm64.s | 7 ---- .../golang.org/x/sys/cpu/cpu_gc_arm64.go | 1 - .../golang.org/x/sys/cpu/cpu_gccgo_arm64.go | 1 - .../golang.org/x/sys/cpu/cpu_netbsd_arm64.go | 2 +- .../golang.org/x/sys/cpu/cpu_openbsd_arm64.go | 2 +- src/vendor/modules.txt | 8 ++--- 33 files changed, 91 insertions(+), 93 deletions(-) diff --git a/src/cmd/go.mod b/src/cmd/go.mod index a23387699df..64bb4c3d791 100644 --- a/src/cmd/go.mod +++ b/src/cmd/go.mod @@ -4,18 +4,18 @@ go 1.26 require ( github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 - golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938 - golang.org/x/build v0.0.0-20250806225920-b7c66c047964 - golang.org/x/mod v0.30.1-0.20251114215501-3f03020ad526 + golang.org/x/arch v0.23.0 + golang.org/x/build v0.0.0-20251128064159-b9bfd88b30e8 + golang.org/x/mod v0.30.1-0.20251115032019-269c237cf350 golang.org/x/sync v0.18.0 - golang.org/x/sys v0.38.0 - golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 - golang.org/x/term v0.34.0 + golang.org/x/sys v0.38.1-0.20251125153526-08e54827f670 + golang.org/x/telemetry v0.0.0-20251128220624-abf20d0e57ec + golang.org/x/term v0.37.0 golang.org/x/tools v0.39.1-0.20251130212600-1ad6f3d02713 ) require ( github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/text v0.31.1-0.20251128220601-087616b6cde9 // indirect rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef // indirect ) diff --git a/src/cmd/go.sum b/src/cmd/go.sum index 5a49e61a4a3..489f9bfb001 100644 --- a/src/cmd/go.sum +++ b/src/cmd/go.sum @@ -1,27 +1,27 @@ -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ= github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b h1:ogbOPx86mIhFy764gGkqnkFC8m5PJA7sPzlk9ppLVQA= github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938 h1:VJ182b/ajNehMFRltVfCh/FR0jAH+QX6hs9zqYod/mU= -golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= -golang.org/x/build v0.0.0-20250806225920-b7c66c047964 h1:yRs1K51GKq7hsIO+YHJ8LsslrvwFceNPIv0tYjpcBd0= -golang.org/x/build v0.0.0-20250806225920-b7c66c047964/go.mod h1:i9Vx7+aOQUpYJRxSO+OpRStVBCVL/9ccI51xblWm5WY= -golang.org/x/mod v0.30.1-0.20251114215501-3f03020ad526 h1:LPpBM4CGUFMC47OqgAr2YIUxEUjH1Ur+D3KR/1LiuuQ= -golang.org/x/mod v0.30.1-0.20251114215501-3f03020ad526/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= +golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/build v0.0.0-20251128064159-b9bfd88b30e8 h1:Mp+uRtHbKFW85lGBTOkOOfkPBz7AUKmZGcflkavmGRM= +golang.org/x/build v0.0.0-20251128064159-b9bfd88b30e8/go.mod h1:Jx2RBBeTWGRSCwfSZ+w2Hg1f7LjWycsSkx+EciLAmPE= +golang.org/x/mod v0.30.1-0.20251115032019-269c237cf350 h1:JGDMsCp8NahDR9HSvwrF6V8tzEf87m7Bo4oZ07vRxdU= +golang.org/x/mod v0.30.1-0.20251115032019-269c237cf350/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/sys v0.38.1-0.20251125153526-08e54827f670 h1:s8+qM6u6X24AFOioI7tH2p/6zxCHqt3G7zwUYm7MgUc= +golang.org/x/sys v0.38.1-0.20251125153526-08e54827f670/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20251128220624-abf20d0e57ec h1:dRVkWZl6bUOp+oxnOe4BuyhWSIPmt29N4ooHarm7Ic8= +golang.org/x/telemetry v0.0.0-20251128220624-abf20d0e57ec/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/text v0.31.1-0.20251128220601-087616b6cde9 h1:IjQf87/qLz2y0SiCc0uY3DwajALXkAgP1Pxal0mmdrM= +golang.org/x/text v0.31.1-0.20251128220601-087616b6cde9/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.39.1-0.20251130212600-1ad6f3d02713 h1:i4GzAuZW4RuKXltwKyLYAfk7E1TSKQBxRAI7XKfLjSk= golang.org/x/tools v0.39.1-0.20251130212600-1ad6f3d02713/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8= diff --git a/src/cmd/vendor/golang.org/x/mod/sumdb/note/note.go b/src/cmd/vendor/golang.org/x/mod/sumdb/note/note.go index 8b2b25278da..c95777f5e85 100644 --- a/src/cmd/vendor/golang.org/x/mod/sumdb/note/note.go +++ b/src/cmd/vendor/golang.org/x/mod/sumdb/note/note.go @@ -273,13 +273,6 @@ func NewVerifier(vkey string) (Verifier, error) { return v, nil } -// chop chops s at the first instance of sep, if any, -// and returns the text before and after sep. -// If sep is not present, chop returns before is s and after is empty. -func chop(s, sep string) (before, after string, ok bool) { - return strings.Cut(s, sep) -} - // verifier is a trivial Verifier implementation. type verifier struct { name string @@ -553,7 +546,7 @@ func Open(msg []byte, known Verifiers) (*Note, error) { return nil, errMalformedNote } line = line[len(sigPrefix):] - name, b64, _ := chop(string(line), " ") + name, b64, _ := strings.Cut(string(line), " ") sig, err := base64.StdEncoding.DecodeString(b64) if err != nil || !isValidName(name) || b64 == "" || len(sig) < 5 { return nil, errMalformedNote diff --git a/src/cmd/vendor/golang.org/x/sys/unix/mkerrors.sh b/src/cmd/vendor/golang.org/x/sys/unix/mkerrors.sh index 42517077c43..fd39be4efdc 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/mkerrors.sh +++ b/src/cmd/vendor/golang.org/x/sys/unix/mkerrors.sh @@ -256,6 +256,7 @@ struct ltchars { #include #include #include +#include #include #include #include @@ -613,7 +614,7 @@ ccflags="$@" $2 !~ /IOC_MAGIC/ && $2 ~ /^[A-Z][A-Z0-9_]+_MAGIC2?$/ || $2 ~ /^(VM|VMADDR)_/ || - $2 ~ /^IOCTL_VM_SOCKETS_/ || + $2 ~ /^(IOCTL_VM_SOCKETS_|IOCTL_MEI_)/ || $2 ~ /^(TASKSTATS|TS)_/ || $2 ~ /^CGROUPSTATS_/ || $2 ~ /^GENL_/ || diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux.go b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux.go index d0a75da572c..120a7b35d1d 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux.go +++ b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux.go @@ -1615,6 +1615,8 @@ const ( IN_OPEN = 0x20 IN_Q_OVERFLOW = 0x4000 IN_UNMOUNT = 0x2000 + IOCTL_MEI_CONNECT_CLIENT = 0xc0104801 + IOCTL_MEI_CONNECT_CLIENT_VTAG = 0xc0144804 IPPROTO_AH = 0x33 IPPROTO_BEETPH = 0x5e IPPROTO_COMP = 0x6c diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_386.go b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_386.go index 1c37f9fbc45..97a61fc5b84 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_386.go +++ b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_386.go @@ -116,6 +116,8 @@ const ( IEXTEN = 0x8000 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 + IOCTL_MEI_NOTIFY_GET = 0x80044803 + IOCTL_MEI_NOTIFY_SET = 0x40044802 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 IPV6_FLOWINFO_MASK = 0xffffff0f IPV6_FLOWLABEL_MASK = 0xffff0f00 diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go index 6f54d34aefc..a0d6d498c4b 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go +++ b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go @@ -116,6 +116,8 @@ const ( IEXTEN = 0x8000 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 + IOCTL_MEI_NOTIFY_GET = 0x80044803 + IOCTL_MEI_NOTIFY_SET = 0x40044802 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 IPV6_FLOWINFO_MASK = 0xffffff0f IPV6_FLOWLABEL_MASK = 0xffff0f00 diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go index 783ec5c126f..dd9c903f9ad 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go +++ b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go @@ -115,6 +115,8 @@ const ( IEXTEN = 0x8000 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 + IOCTL_MEI_NOTIFY_GET = 0x80044803 + IOCTL_MEI_NOTIFY_SET = 0x40044802 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 IPV6_FLOWINFO_MASK = 0xffffff0f IPV6_FLOWLABEL_MASK = 0xffff0f00 diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go index ca83d3ba162..384c61ca3a8 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go +++ b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go @@ -120,6 +120,8 @@ const ( IEXTEN = 0x8000 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 + IOCTL_MEI_NOTIFY_GET = 0x80044803 + IOCTL_MEI_NOTIFY_SET = 0x40044802 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 IPV6_FLOWINFO_MASK = 0xffffff0f IPV6_FLOWLABEL_MASK = 0xffff0f00 diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go index 607e611c0cb..6384c9831fc 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go +++ b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go @@ -116,6 +116,8 @@ const ( IEXTEN = 0x8000 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 + IOCTL_MEI_NOTIFY_GET = 0x80044803 + IOCTL_MEI_NOTIFY_SET = 0x40044802 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 IPV6_FLOWINFO_MASK = 0xffffff0f IPV6_FLOWLABEL_MASK = 0xffff0f00 diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go index b9cb5bd3c09..553c1c6f15e 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go +++ b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go @@ -115,6 +115,8 @@ const ( IEXTEN = 0x100 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x80 + IOCTL_MEI_NOTIFY_GET = 0x40044803 + IOCTL_MEI_NOTIFY_SET = 0x80044802 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 IPV6_FLOWINFO_MASK = 0xfffffff IPV6_FLOWLABEL_MASK = 0xfffff diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go index 65b078a6382..b3339f2099a 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go +++ b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go @@ -115,6 +115,8 @@ const ( IEXTEN = 0x100 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x80 + IOCTL_MEI_NOTIFY_GET = 0x40044803 + IOCTL_MEI_NOTIFY_SET = 0x80044802 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 IPV6_FLOWINFO_MASK = 0xfffffff IPV6_FLOWLABEL_MASK = 0xfffff diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go index 5298a3033d0..177091d2bc3 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go +++ b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go @@ -115,6 +115,8 @@ const ( IEXTEN = 0x100 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x80 + IOCTL_MEI_NOTIFY_GET = 0x40044803 + IOCTL_MEI_NOTIFY_SET = 0x80044802 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 IPV6_FLOWINFO_MASK = 0xffffff0f IPV6_FLOWLABEL_MASK = 0xffff0f00 diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go index 7bc557c8761..c5abf156d09 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go +++ b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go @@ -115,6 +115,8 @@ const ( IEXTEN = 0x100 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x80 + IOCTL_MEI_NOTIFY_GET = 0x40044803 + IOCTL_MEI_NOTIFY_SET = 0x80044802 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 IPV6_FLOWINFO_MASK = 0xffffff0f IPV6_FLOWLABEL_MASK = 0xffff0f00 diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go index 152399bb04a..f1f3fadf576 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go +++ b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go @@ -115,6 +115,8 @@ const ( IEXTEN = 0x400 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 + IOCTL_MEI_NOTIFY_GET = 0x40044803 + IOCTL_MEI_NOTIFY_SET = 0x80044802 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 IPV6_FLOWINFO_MASK = 0xfffffff IPV6_FLOWLABEL_MASK = 0xfffff diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go index 1a1ce2409cf..203ad9c54af 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go +++ b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go @@ -115,6 +115,8 @@ const ( IEXTEN = 0x400 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 + IOCTL_MEI_NOTIFY_GET = 0x40044803 + IOCTL_MEI_NOTIFY_SET = 0x80044802 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 IPV6_FLOWINFO_MASK = 0xfffffff IPV6_FLOWLABEL_MASK = 0xfffff diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go index 4231a1fb578..4b9abcb21a2 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go +++ b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go @@ -115,6 +115,8 @@ const ( IEXTEN = 0x400 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 + IOCTL_MEI_NOTIFY_GET = 0x40044803 + IOCTL_MEI_NOTIFY_SET = 0x80044802 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 IPV6_FLOWINFO_MASK = 0xffffff0f IPV6_FLOWLABEL_MASK = 0xffff0f00 diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go index 21c0e952665..f87983037d9 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go +++ b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go @@ -115,6 +115,8 @@ const ( IEXTEN = 0x8000 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 + IOCTL_MEI_NOTIFY_GET = 0x80044803 + IOCTL_MEI_NOTIFY_SET = 0x40044802 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 IPV6_FLOWINFO_MASK = 0xffffff0f IPV6_FLOWLABEL_MASK = 0xffff0f00 diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go index f00d1cd7cf4..64347eb354c 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go +++ b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go @@ -115,6 +115,8 @@ const ( IEXTEN = 0x8000 IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 + IOCTL_MEI_NOTIFY_GET = 0x80044803 + IOCTL_MEI_NOTIFY_SET = 0x40044802 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 IPV6_FLOWINFO_MASK = 0xfffffff IPV6_FLOWLABEL_MASK = 0xfffff diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go index bc8d539e6af..7d71911718f 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go +++ b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go @@ -119,6 +119,8 @@ const ( IEXTEN = 0x8000 IN_CLOEXEC = 0x400000 IN_NONBLOCK = 0x4000 + IOCTL_MEI_NOTIFY_GET = 0x40044803 + IOCTL_MEI_NOTIFY_SET = 0x80044802 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 IPV6_FLOWINFO_MASK = 0xfffffff IPV6_FLOWLABEL_MASK = 0xfffff diff --git a/src/cmd/vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go b/src/cmd/vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go index 439548ec9ad..50e8e644970 100644 --- a/src/cmd/vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go +++ b/src/cmd/vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go @@ -104,7 +104,7 @@ type Statvfs_t struct { Fsid uint32 Namemax uint32 Owner uint32 - Spare [4]uint32 + Spare [4]uint64 Fstypename [32]byte Mntonname [1024]byte Mntfromname [1024]byte diff --git a/src/cmd/vendor/golang.org/x/term/terminal.go b/src/cmd/vendor/golang.org/x/term/terminal.go index bddb2e2aebd..9255449b9b3 100644 --- a/src/cmd/vendor/golang.org/x/term/terminal.go +++ b/src/cmd/vendor/golang.org/x/term/terminal.go @@ -413,7 +413,7 @@ func (t *Terminal) eraseNPreviousChars(n int) { } } -// countToLeftWord returns then number of characters from the cursor to the +// countToLeftWord returns the number of characters from the cursor to the // start of the previous word. func (t *Terminal) countToLeftWord() int { if t.pos == 0 { @@ -438,7 +438,7 @@ func (t *Terminal) countToLeftWord() int { return t.pos - pos } -// countToRightWord returns then number of characters from the cursor to the +// countToRightWord returns the number of characters from the cursor to the // start of the next word. func (t *Terminal) countToRightWord() int { pos := t.pos @@ -478,7 +478,7 @@ func visualLength(runes []rune) int { return length } -// histroryAt unlocks the terminal and relocks it while calling History.At. +// historyAt unlocks the terminal and relocks it while calling History.At. func (t *Terminal) historyAt(idx int) (string, bool) { t.lock.Unlock() // Unlock to avoid deadlock if History methods use the output writer. defer t.lock.Lock() // panic in At (or Len) protection. diff --git a/src/cmd/vendor/modules.txt b/src/cmd/vendor/modules.txt index 7810dcf8b12..a5e5cf05518 100644 --- a/src/cmd/vendor/modules.txt +++ b/src/cmd/vendor/modules.txt @@ -16,7 +16,7 @@ github.com/google/pprof/third_party/svgpan # github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b ## explicit; go 1.13 github.com/ianlancetaylor/demangle -# golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938 +# golang.org/x/arch v0.23.0 ## explicit; go 1.24.0 golang.org/x/arch/arm/armasm golang.org/x/arch/arm64/arm64asm @@ -25,10 +25,10 @@ golang.org/x/arch/ppc64/ppc64asm golang.org/x/arch/riscv64/riscv64asm golang.org/x/arch/s390x/s390xasm golang.org/x/arch/x86/x86asm -# golang.org/x/build v0.0.0-20250806225920-b7c66c047964 -## explicit; go 1.23.0 +# golang.org/x/build v0.0.0-20251128064159-b9bfd88b30e8 +## explicit; go 1.24.0 golang.org/x/build/relnote -# golang.org/x/mod v0.30.1-0.20251114215501-3f03020ad526 +# golang.org/x/mod v0.30.1-0.20251115032019-269c237cf350 ## explicit; go 1.24.0 golang.org/x/mod/internal/lazyregexp golang.org/x/mod/modfile @@ -43,12 +43,12 @@ golang.org/x/mod/zip ## explicit; go 1.24.0 golang.org/x/sync/errgroup golang.org/x/sync/semaphore -# golang.org/x/sys v0.38.0 +# golang.org/x/sys v0.38.1-0.20251125153526-08e54827f670 ## explicit; go 1.24.0 golang.org/x/sys/plan9 golang.org/x/sys/unix golang.org/x/sys/windows -# golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 +# golang.org/x/telemetry v0.0.0-20251128220624-abf20d0e57ec ## explicit; go 1.24.0 golang.org/x/telemetry golang.org/x/telemetry/counter @@ -60,10 +60,10 @@ golang.org/x/telemetry/internal/crashmonitor golang.org/x/telemetry/internal/mmap golang.org/x/telemetry/internal/telemetry golang.org/x/telemetry/internal/upload -# golang.org/x/term v0.34.0 -## explicit; go 1.23.0 +# golang.org/x/term v0.37.0 +## explicit; go 1.24.0 golang.org/x/term -# golang.org/x/text v0.31.0 +# golang.org/x/text v0.31.1-0.20251128220601-087616b6cde9 ## explicit; go 1.24.0 golang.org/x/text/cases golang.org/x/text/internal diff --git a/src/go.mod b/src/go.mod index 77ca25331ae..f79455c970e 100644 --- a/src/go.mod +++ b/src/go.mod @@ -3,11 +3,11 @@ module std go 1.26 require ( - golang.org/x/crypto v0.44.0 - golang.org/x/net v0.47.1-0.20251124223553-bff14c525670 + golang.org/x/crypto v0.45.0 + golang.org/x/net v0.47.1-0.20251128220604-7c360367ab7e ) require ( - golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/sys v0.38.1-0.20251125153526-08e54827f670 // indirect + golang.org/x/text v0.31.1-0.20251128220601-087616b6cde9 // indirect ) diff --git a/src/go.sum b/src/go.sum index b33582c5941..e2cf9591bc5 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,8 +1,8 @@ -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= -golang.org/x/net v0.47.1-0.20251124223553-bff14c525670 h1:6OE5meBQStq9OFgbyv9VH3wiSVw9HDJ7GBz2L5pkhuo= -golang.org/x/net v0.47.1-0.20251124223553-bff14c525670/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/net v0.47.1-0.20251128220604-7c360367ab7e h1:PAAT9cIDvIAIRQVz2txQvUFRt3jOlhiO84ihd8XMGlg= +golang.org/x/net v0.47.1-0.20251128220604-7c360367ab7e/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/sys v0.38.1-0.20251125153526-08e54827f670 h1:s8+qM6u6X24AFOioI7tH2p/6zxCHqt3G7zwUYm7MgUc= +golang.org/x/sys v0.38.1-0.20251125153526-08e54827f670/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.31.1-0.20251128220601-087616b6cde9 h1:IjQf87/qLz2y0SiCc0uY3DwajALXkAgP1Pxal0mmdrM= +golang.org/x/text v0.31.1-0.20251128220601-087616b6cde9/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= diff --git a/src/vendor/golang.org/x/sys/cpu/cpu.go b/src/vendor/golang.org/x/sys/cpu/cpu.go index 34c9ae76efd..63541994ef0 100644 --- a/src/vendor/golang.org/x/sys/cpu/cpu.go +++ b/src/vendor/golang.org/x/sys/cpu/cpu.go @@ -92,9 +92,6 @@ var ARM64 struct { HasSHA2 bool // SHA2 hardware implementation HasCRC32 bool // CRC32 hardware implementation HasATOMICS bool // Atomic memory operation instruction set - HasHPDS bool // Hierarchical permission disables in translations tables - HasLOR bool // Limited ordering regions - HasPAN bool // Privileged access never HasFPHP bool // Half precision floating-point instruction set HasASIMDHP bool // Advanced SIMD half precision instruction set HasCPUID bool // CPUID identification scheme registers diff --git a/src/vendor/golang.org/x/sys/cpu/cpu_arm64.go b/src/vendor/golang.org/x/sys/cpu/cpu_arm64.go index f449c679fe4..af2aa99f9f0 100644 --- a/src/vendor/golang.org/x/sys/cpu/cpu_arm64.go +++ b/src/vendor/golang.org/x/sys/cpu/cpu_arm64.go @@ -65,10 +65,10 @@ func setMinimalFeatures() { func readARM64Registers() { Initialized = true - parseARM64SystemRegisters(getisar0(), getisar1(), getmmfr1(), getpfr0()) + parseARM64SystemRegisters(getisar0(), getisar1(), getpfr0()) } -func parseARM64SystemRegisters(isar0, isar1, mmfr1, pfr0 uint64) { +func parseARM64SystemRegisters(isar0, isar1, pfr0 uint64) { // ID_AA64ISAR0_EL1 switch extractBits(isar0, 4, 7) { case 1: @@ -152,22 +152,6 @@ func parseARM64SystemRegisters(isar0, isar1, mmfr1, pfr0 uint64) { ARM64.HasI8MM = true } - // ID_AA64MMFR1_EL1 - switch extractBits(mmfr1, 12, 15) { - case 1, 2: - ARM64.HasHPDS = true - } - - switch extractBits(mmfr1, 16, 19) { - case 1: - ARM64.HasLOR = true - } - - switch extractBits(mmfr1, 20, 23) { - case 1, 2, 3: - ARM64.HasPAN = true - } - // ID_AA64PFR0_EL1 switch extractBits(pfr0, 16, 19) { case 0: diff --git a/src/vendor/golang.org/x/sys/cpu/cpu_arm64.s b/src/vendor/golang.org/x/sys/cpu/cpu_arm64.s index a4f24b3b0c8..3b0450a06a7 100644 --- a/src/vendor/golang.org/x/sys/cpu/cpu_arm64.s +++ b/src/vendor/golang.org/x/sys/cpu/cpu_arm64.s @@ -20,13 +20,6 @@ TEXT ·getisar1(SB),NOSPLIT,$0-8 MOVD R0, ret+0(FP) RET -// func getmmfr1() uint64 -TEXT ·getmmfr1(SB),NOSPLIT,$0-8 - // get Memory Model Feature Register 1 into x0 - MRS ID_AA64MMFR1_EL1, R0 - MOVD R0, ret+0(FP) - RET - // func getpfr0() uint64 TEXT ·getpfr0(SB),NOSPLIT,$0-8 // get Processor Feature Register 0 into x0 diff --git a/src/vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go b/src/vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go index e3fc5a8d31c..6ac6e1efb20 100644 --- a/src/vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go +++ b/src/vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go @@ -8,6 +8,5 @@ package cpu func getisar0() uint64 func getisar1() uint64 -func getmmfr1() uint64 func getpfr0() uint64 func getzfr0() uint64 diff --git a/src/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go b/src/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go index 8df2079e15f..7f1946780bd 100644 --- a/src/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go +++ b/src/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go @@ -8,5 +8,4 @@ package cpu func getisar0() uint64 { return 0 } func getisar1() uint64 { return 0 } -func getmmfr1() uint64 { return 0 } func getpfr0() uint64 { return 0 } diff --git a/src/vendor/golang.org/x/sys/cpu/cpu_netbsd_arm64.go b/src/vendor/golang.org/x/sys/cpu/cpu_netbsd_arm64.go index 19aea0633e8..ebfb3fc8e76 100644 --- a/src/vendor/golang.org/x/sys/cpu/cpu_netbsd_arm64.go +++ b/src/vendor/golang.org/x/sys/cpu/cpu_netbsd_arm64.go @@ -167,7 +167,7 @@ func doinit() { setMinimalFeatures() return } - parseARM64SystemRegisters(cpuid.aa64isar0, cpuid.aa64isar1, cpuid.aa64mmfr1, cpuid.aa64pfr0) + parseARM64SystemRegisters(cpuid.aa64isar0, cpuid.aa64isar1, cpuid.aa64pfr0) Initialized = true } diff --git a/src/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.go b/src/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.go index 87fd3a77807..85b64d5ccb7 100644 --- a/src/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.go +++ b/src/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.go @@ -59,7 +59,7 @@ func doinit() { if !ok { return } - parseARM64SystemRegisters(isar0, isar1, 0, 0) + parseARM64SystemRegisters(isar0, isar1, 0) Initialized = true } diff --git a/src/vendor/modules.txt b/src/vendor/modules.txt index 27f1ea7edf1..7932adddfaa 100644 --- a/src/vendor/modules.txt +++ b/src/vendor/modules.txt @@ -1,4 +1,4 @@ -# golang.org/x/crypto v0.44.0 +# golang.org/x/crypto v0.45.0 ## explicit; go 1.24.0 golang.org/x/crypto/chacha20 golang.org/x/crypto/chacha20poly1305 @@ -6,7 +6,7 @@ golang.org/x/crypto/cryptobyte golang.org/x/crypto/cryptobyte/asn1 golang.org/x/crypto/internal/alias golang.org/x/crypto/internal/poly1305 -# golang.org/x/net v0.47.1-0.20251124223553-bff14c525670 +# golang.org/x/net v0.47.1-0.20251128220604-7c360367ab7e ## explicit; go 1.24.0 golang.org/x/net/dns/dnsmessage golang.org/x/net/http/httpguts @@ -15,10 +15,10 @@ golang.org/x/net/http2/hpack golang.org/x/net/idna golang.org/x/net/lif golang.org/x/net/nettest -# golang.org/x/sys v0.38.0 +# golang.org/x/sys v0.38.1-0.20251125153526-08e54827f670 ## explicit; go 1.24.0 golang.org/x/sys/cpu -# golang.org/x/text v0.31.0 +# golang.org/x/text v0.31.1-0.20251128220601-087616b6cde9 ## explicit; go 1.24.0 golang.org/x/text/secure/bidirule golang.org/x/text/transform From 16c0f7e1524c40521312e485b0b0f8b26c95b174 Mon Sep 17 00:00:00 2001 From: Lin Lin Date: Mon, 1 Dec 2025 13:08:38 +0000 Subject: [PATCH 113/140] cmd/compile: run go generate for internal/ir Updates #70954 Change-Id: I00ddb37650d27a98da921f04570d33535865622c GitHub-Last-Rev: 69d52f3d720bd6e34befa1c6b92b2e80ed7ab2d1 GitHub-Pull-Request: golang/go#76638 Reviewed-on: https://go-review.googlesource.com/c/go/+/725440 LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Griesemer Reviewed-by: Keith Randall Auto-Submit: Keith Randall Reviewed-by: Keith Randall --- src/cmd/compile/internal/ir/node_gen.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/cmd/compile/internal/ir/node_gen.go b/src/cmd/compile/internal/ir/node_gen.go index 4298b3a43d7..2ae0a43e496 100644 --- a/src/cmd/compile/internal/ir/node_gen.go +++ b/src/cmd/compile/internal/ir/node_gen.go @@ -1188,6 +1188,9 @@ func (n *MoveToHeapExpr) doChildren(do func(Node) bool) bool { if n.Slice != nil && do(n.Slice) { return true } + if n.RType != nil && do(n.RType) { + return true + } return false } func (n *MoveToHeapExpr) doChildrenWithHidden(do func(Node) bool) bool { @@ -1198,6 +1201,9 @@ func (n *MoveToHeapExpr) editChildren(edit func(Node) Node) { if n.Slice != nil { n.Slice = edit(n.Slice).(Node) } + if n.RType != nil { + n.RType = edit(n.RType).(Node) + } } func (n *MoveToHeapExpr) editChildrenWithHidden(edit func(Node) Node) { n.editChildren(edit) From 4be545115cf8ed42aa0337cbb6c3a92f718192b9 Mon Sep 17 00:00:00 2001 From: Dmitri Shuralyov Date: Mon, 1 Dec 2025 11:29:26 -0500 Subject: [PATCH 114/140] cmd/pprof: update vendored github.com/google/pprof Pull in the latest published version of github.com/google/pprof as part of the continuous process of keeping Go's dependencies up to date. For #36905. [git-generate] cd src/cmd go get github.com/google/pprof@v0.0.0-20251114195745-4902fdda35c8 go mod tidy go mod vendor Change-Id: Id26eb632f637fb2c602d87cb83fdff7f099934ce Reviewed-on: https://go-review.googlesource.com/c/go/+/725500 Auto-Submit: Dmitri Shuralyov LUCI-TryBot-Result: Go LUCI Reviewed-by: Dmitri Shuralyov Reviewed-by: Michael Pratt --- src/cmd/go.mod | 2 +- src/cmd/go.sum | 4 +- .../internal/binutils/addr2liner_llvm.go | 10 ++--- .../google/pprof/internal/driver/config.go | 2 +- .../pprof/internal/driver/html/header.html | 2 +- .../google/pprof/internal/driver/webui.go | 22 +++++----- .../google/pprof/internal/graph/graph.go | 43 +++++++++++++------ .../google/pprof/internal/plugin/plugin.go | 10 ++--- .../github.com/google/pprof/profile/proto.go | 19 +++++++- src/cmd/vendor/modules.txt | 4 +- 10 files changed, 77 insertions(+), 41 deletions(-) diff --git a/src/cmd/go.mod b/src/cmd/go.mod index 64bb4c3d791..9a1a38bccca 100644 --- a/src/cmd/go.mod +++ b/src/cmd/go.mod @@ -3,7 +3,7 @@ module cmd go 1.26 require ( - github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 + github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 golang.org/x/arch v0.23.0 golang.org/x/build v0.0.0-20251128064159-b9bfd88b30e8 golang.org/x/mod v0.30.1-0.20251115032019-269c237cf350 diff --git a/src/cmd/go.sum b/src/cmd/go.sum index 489f9bfb001..52d55d81732 100644 --- a/src/cmd/go.sum +++ b/src/cmd/go.sum @@ -1,7 +1,7 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ= -github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= +github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE= +github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b h1:ogbOPx86mIhFy764gGkqnkFC8m5PJA7sPzlk9ppLVQA= github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= diff --git a/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go b/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go index 2f5d97e89a6..c5c7cc0e10c 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/binutils/addr2liner_llvm.go @@ -151,11 +151,11 @@ func (d *llvmSymbolizer) readCodeFrames() ([]plugin.Frame, error) { Address string `json:"Address"` ModuleName string `json:"ModuleName"` Symbol []struct { - Line int `json:"Line"` - Column int `json:"Column"` - FunctionName string `json:"FunctionName"` - FileName string `json:"FileName"` - StartLine int `json:"StartLine"` + Line int `json:"Line"` + Column int `json:"Column"` + FunctionName string `json:"FunctionName"` + FileName string `json:"FileName"` + StartLine int `json:"StartLine"` } `json:"Symbol"` } if err := json.Unmarshal([]byte(line), &frame); err != nil { diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/config.go b/src/cmd/vendor/github.com/google/pprof/internal/driver/config.go index 184de397ef5..20d169c8f68 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/config.go +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/config.go @@ -164,7 +164,7 @@ func init() { def := defaultConfig() configFieldMap = map[string]configField{} - t := reflect.TypeOf(config{}) + t := reflect.TypeFor[config]() for i, n := 0, t.NumField(); i < n; i++ { field := t.Field(i) js := strings.Split(field.Tag.Get("json"), ",") diff --git a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/header.html b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/header.html index 5405a0be955..4d0f198a695 100644 --- a/src/cmd/vendor/github.com/google/pprof/internal/driver/html/header.html +++ b/src/cmd/vendor/github.com/google/pprof/internal/driver/html/header.html @@ -10,7 +10,7 @@