Update to go1.24.2

This commit is contained in:
Vorapol Rinsatitnon
2025-04-02 07:37:39 +07:00
parent 33f3fba1e8
commit f665e748c7
40 changed files with 795 additions and 119 deletions

View File

@@ -1,2 +1,2 @@
go1.24.1
time 2025-02-27T17:57:18Z
go1.24.2
time 2025-03-26T19:09:39Z

View File

@@ -42,6 +42,7 @@ import (
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/pgo"
"cmd/internal/src"
)
// Inlining budget parameters, gathered in one place
@@ -974,6 +975,16 @@ func inlineCostOK(n *ir.CallExpr, caller, callee *ir.Func, bigCaller, closureCal
return true, 0, metric, hot
}
// parsePos returns all the inlining positions and the innermost position.
func parsePos(pos src.XPos, posTmp []src.Pos) ([]src.Pos, src.Pos) {
ctxt := base.Ctxt
ctxt.AllPos(pos, func(p src.Pos) {
posTmp = append(posTmp, p)
})
l := len(posTmp) - 1
return posTmp[:l], posTmp[l]
}
// canInlineCallExpr returns true if the call n from caller to callee
// can be inlined, plus the score computed for the call expr in question,
// and whether the callee is hot according to PGO.
@@ -1001,6 +1012,17 @@ func canInlineCallExpr(callerfn *ir.Func, n *ir.CallExpr, callee *ir.Func, bigCa
return false, 0, false
}
callees, calleeInner := parsePos(n.Pos(), make([]src.Pos, 0, 10))
for _, p := range callees {
if p.Line() == calleeInner.Line() && p.Col() == calleeInner.Col() && p.AbsFilename() == calleeInner.AbsFilename() {
if log && logopt.Enabled() {
logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", fmt.Sprintf("recursive call to %s", ir.FuncName(callerfn)))
}
return false, 0, false
}
}
if callee == callerfn {
// Can't recursively inline a function into itself.
if log && logopt.Enabled() {

View File

@@ -253,7 +253,7 @@ func (s *inlClosureState) mark(n ir.Node) ir.Node {
if isTestingBLoop(n) {
// No inlining nor devirtualization performed on b.Loop body
if base.Flag.LowerM > 1 {
if base.Flag.LowerM > 0 {
fmt.Printf("%v: skip inlining within testing.B.loop for %v\n", ir.Line(n), n)
}
// We still want to explore inlining opportunities in other parts of ForStmt.

View File

@@ -230,6 +230,9 @@ func TestIntendedInlining(t *testing.T) {
"(*Pointer[go.shape.int]).Store",
"(*Pointer[go.shape.int]).Swap",
},
"testing": {
"(*B).Loop",
},
}
if !goexperiment.SwissMap {

View File

@@ -204,7 +204,7 @@ func (check *Checker) lhsVar(lhs syntax.Expr) Type {
// dot-imported variables.
if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg {
v = w
v_used = v.used
v_used = check.usedVars[v]
}
}
}
@@ -213,7 +213,7 @@ func (check *Checker) lhsVar(lhs syntax.Expr) Type {
check.expr(nil, &x, lhs)
if v != nil {
v.used = v_used // restore v.used
check.usedVars[v] = v_used // restore v.used
}
if x.mode == invalid || !isValid(x.typ) {

View File

@@ -687,7 +687,7 @@ func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, def *TypeName
if pname, _ := obj.(*PkgName); pname != nil {
assert(pname.pkg == check.pkg)
check.recordUse(ident, pname)
pname.used = true
check.usedPkgNames[pname] = true
pkg := pname.imported
var exp Object
@@ -972,13 +972,13 @@ func (check *Checker) use1(e syntax.Expr, lhs bool) bool {
// dot-imported variables.
if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg {
v = w
v_used = v.used
v_used = check.usedVars[v]
}
}
}
check.exprOrType(&x, n, true)
if v != nil {
v.used = v_used // restore v.used
check.usedVars[v] = v_used // restore v.used
}
case *syntax.ListExpr:
return check.useN(n.ElemList, lhs)

View File

@@ -162,6 +162,8 @@ type Checker struct {
dotImportMap map[dotImportKey]*PkgName // maps dot-imported objects to the package they were dot-imported through
brokenAliases map[*TypeName]bool // set of aliases with broken (not yet determined) types
unionTypeSets map[*Union]*_TypeSet // computed type sets for union types
usedVars map[*Var]bool // set of used variables
usedPkgNames map[*PkgName]bool // set of used package names
mono monoGraph // graph for detecting non-monomorphizable instantiation loops
firstErr error // first error encountered
@@ -291,6 +293,8 @@ func NewChecker(conf *Config, pkg *Package, info *Info) *Checker {
Info: info,
objMap: make(map[Object]*declInfo),
impMap: make(map[importKey]*Package),
usedVars: make(map[*Var]bool),
usedPkgNames: make(map[*PkgName]bool),
}
}
@@ -298,6 +302,8 @@ func NewChecker(conf *Config, pkg *Package, info *Info) *Checker {
// The provided files must all belong to the same package.
func (check *Checker) initFiles(files []*syntax.File) {
// start with a clean slate (check.Files may be called multiple times)
// TODO(gri): what determines which fields are zeroed out here, vs at the end
// of checkFiles?
check.files = nil
check.imports = nil
check.dotImportMap = nil
@@ -309,6 +315,13 @@ func (check *Checker) initFiles(files []*syntax.File) {
check.objPath = nil
check.cleaners = nil
// We must initialize usedVars and usedPkgNames both here and in NewChecker,
// because initFiles is not called in the CheckExpr or Eval codepaths, yet we
// want to free this memory at the end of Files ('used' predicates are
// only needed in the context of a given file).
check.usedVars = make(map[*Var]bool)
check.usedPkgNames = make(map[*PkgName]bool)
// determine package name and collect valid files
pkg := check.pkg
for _, file := range files {
@@ -482,8 +495,11 @@ func (check *Checker) checkFiles(files []*syntax.File) {
check.seenPkgMap = nil
check.brokenAliases = nil
check.unionTypeSets = nil
check.usedVars = nil
check.usedPkgNames = nil
check.ctxt = nil
// TODO(gri): shouldn't the cleanup above occur after the bailout?
// TODO(gri) There's more memory we should release at this point.
}

View File

@@ -242,13 +242,12 @@ func (a *object) cmp(b *object) int {
type PkgName struct {
object
imported *Package
used bool // set if the package was used
}
// NewPkgName returns a new PkgName object representing an imported package.
// The remaining arguments set the attributes found with all Objects.
func NewPkgName(pos syntax.Pos, pkg *Package, name string, imported *Package) *PkgName {
return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, black, nopos}, imported, false}
return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, black, nopos}, imported}
}
// Imported returns the package that was imported.
@@ -331,10 +330,10 @@ func (obj *TypeName) IsAlias() bool {
// A Variable represents a declared variable (including function parameters and results, and struct fields).
type Var struct {
object
origin *Var // if non-nil, the Var from which this one was instantiated
embedded bool // if set, the variable is an embedded struct field, and name is the type name
isField bool // var is struct field
used bool // set if the variable was used
origin *Var // if non-nil, the Var from which this one was instantiated
isParam bool // var is a param, for backport of 'used' check to go1.24 (go.dev/issue/72826)
}
// NewVar returns a new variable.
@@ -345,7 +344,7 @@ func NewVar(pos syntax.Pos, pkg *Package, name string, typ Type) *Var {
// NewParam returns a new variable representing a function parameter.
func NewParam(pos syntax.Pos, pkg *Package, name string, typ Type) *Var {
return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, used: true} // parameters are always 'used'
return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, isParam: true}
}
// NewField returns a new variable representing a struct field.

View File

@@ -295,7 +295,7 @@ func (check *Checker) collectObjects() {
if imp.fake {
// match 1.17 cmd/compile (not prescribed by spec)
pkgName.used = true
check.usedPkgNames[pkgName] = true
}
// add import to file scope
@@ -715,7 +715,7 @@ func (check *Checker) unusedImports() {
// (initialization), use the blank identifier as explicit package name."
for _, obj := range check.imports {
if !obj.used && obj.name != "_" {
if obj.name != "_" && !check.usedPkgNames[obj] {
check.errorUnusedPkg(obj)
}
}

View File

@@ -36,7 +36,7 @@ func TestSizeof(t *testing.T) {
{term{}, 12, 24},
// Objects
{PkgName{}, 64, 104},
{PkgName{}, 60, 96},
{Const{}, 64, 104},
{TypeName{}, 56, 88},
{Var{}, 64, 104},

View File

@@ -58,7 +58,7 @@ func (check *Checker) usage(scope *Scope) {
var unused []*Var
for name, elem := range scope.elems {
elem = resolve(name, elem)
if v, _ := elem.(*Var); v != nil && !v.used {
if v, _ := elem.(*Var); v != nil && !v.isParam && !check.usedVars[v] {
unused = append(unused, v)
}
}
@@ -824,10 +824,10 @@ func (check *Checker) typeSwitchStmt(inner stmtContext, s *syntax.SwitchStmt, gu
if lhs != nil {
var used bool
for _, v := range lhsVars {
if v.used {
if check.usedVars[v] {
used = true
}
v.used = true // avoid usage error when checking entire function
check.usedVars[v] = true // avoid usage error when checking entire function
}
if !used {
check.softErrorf(lhs, UnusedVar, "%s declared and not used", lhs.Value)
@@ -934,7 +934,7 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s
if typ == nil || typ == Typ[Invalid] {
// typ == Typ[Invalid] can happen if allowVersion fails.
obj.typ = Typ[Invalid]
obj.used = true // don't complain about unused variable
check.usedVars[obj] = true // don't complain about unused variable
continue
}

View File

@@ -55,7 +55,7 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType
// avoid "declared but not used" errors
// (don't use Checker.use - we don't want to evaluate too much)
if v, _ := obj.(*Var); v != nil && v.pkg == check.pkg /* see Checker.use1 */ {
v.used = true
check.usedVars[v] = true
}
return
}
@@ -83,7 +83,7 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType
// (This code is only needed for dot-imports. Without them,
// we only have to mark variables, see *Var case below).
if pkgName := check.dotImportMap[dotImportKey{scope, obj.Name()}]; pkgName != nil {
pkgName.used = true
check.usedPkgNames[pkgName] = true
}
switch obj := obj.(type) {
@@ -120,7 +120,7 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType
// from other packages to avoid potential race conditions with
// dot-imported variables.
if obj.pkg == check.pkg {
obj.used = true
check.usedVars[obj] = true
}
check.addDeclDep(obj)
if !isValid(typ) {

View File

@@ -43,6 +43,9 @@ type PkgSpecial struct {
}
var runtimePkgs = []string{
// TODO(panjf2000): consider syncing the list inside the
// isAsyncSafePoint in preempt.go based on this list?
"runtime",
"internal/runtime/atomic",

View File

@@ -92,7 +92,8 @@ var defaultCipherSuitesTLS13NoAES = []uint16{
}
// The FIPS-only policies below match BoringSSL's
// ssl_compliance_policy_fips_202205, which is based on NIST SP 800-52r2.
// ssl_compliance_policy_fips_202205, which is based on NIST SP 800-52r2, with
// minor changes per https://go.dev/issue/71757.
// https://cs.opensource.google/boringssl/boringssl/+/master:ssl/ssl_lib.cc;l=3289;drc=ea7a88fa
var defaultSupportedVersionsFIPS = []uint16{
@@ -102,7 +103,7 @@ var defaultSupportedVersionsFIPS = []uint16{
// defaultCurvePreferencesFIPS are the FIPS-allowed curves,
// in preference order (most preferable first).
var defaultCurvePreferencesFIPS = []CurveID{CurveP256, CurveP384}
var defaultCurvePreferencesFIPS = []CurveID{CurveP256, CurveP384, CurveP521}
// defaultSupportedSignatureAlgorithmsFIPS currently are a subset of
// defaultSupportedSignatureAlgorithms without Ed25519 and SHA-1.
@@ -115,6 +116,7 @@ var defaultSupportedSignatureAlgorithmsFIPS = []SignatureScheme{
PKCS1WithSHA384,
ECDSAWithP384AndSHA384,
PKCS1WithSHA512,
ECDSAWithP521AndSHA512,
}
// defaultCipherSuitesFIPS are the FIPS-allowed cipher suites.

View File

@@ -106,7 +106,7 @@ func isFIPSCipherSuite(id uint16) bool {
func isFIPSCurve(id CurveID) bool {
switch id {
case CurveP256, CurveP384:
case CurveP256, CurveP384, CurveP521:
return true
}
return false
@@ -130,6 +130,7 @@ func isFIPSSignatureScheme(alg SignatureScheme) bool {
PKCS1WithSHA384,
ECDSAWithP384AndSHA384,
PKCS1WithSHA512,
ECDSAWithP521AndSHA512,
PSSWithSHA256,
PSSWithSHA384,
PSSWithSHA512:

View File

@@ -207,7 +207,7 @@ func (check *Checker) lhsVar(lhs ast.Expr) Type {
// dot-imported variables.
if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg {
v = w
v_used = v.used
v_used = check.usedVars[v]
}
}
}
@@ -216,7 +216,7 @@ func (check *Checker) lhsVar(lhs ast.Expr) Type {
check.expr(nil, &x, lhs)
if v != nil {
v.used = v_used // restore v.used
check.usedVars[v] = v_used // restore v.used
}
if x.mode == invalid || !isValid(x.typ) {

View File

@@ -689,7 +689,7 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr, def *TypeName, w
if pname, _ := obj.(*PkgName); pname != nil {
assert(pname.pkg == check.pkg)
check.recordUse(ident, pname)
pname.used = true
check.usedPkgNames[pname] = true
pkg := pname.imported
var exp Object
@@ -1020,13 +1020,13 @@ func (check *Checker) use1(e ast.Expr, lhs bool) bool {
// dot-imported variables.
if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg {
v = w
v_used = v.used
v_used = check.usedVars[v]
}
}
}
check.exprOrType(&x, n, true)
if v != nil {
v.used = v_used // restore v.used
check.usedVars[v] = v_used // restore v.used
}
default:
check.rawExpr(nil, &x, e, nil, true)

View File

@@ -182,6 +182,8 @@ type Checker struct {
dotImportMap map[dotImportKey]*PkgName // maps dot-imported objects to the package they were dot-imported through
brokenAliases map[*TypeName]bool // set of aliases with broken (not yet determined) types
unionTypeSets map[*Union]*_TypeSet // computed type sets for union types
usedVars map[*Var]bool // set of used variables
usedPkgNames map[*PkgName]bool // set of used package names
mono monoGraph // graph for detecting non-monomorphizable instantiation loops
firstErr error // first error encountered
@@ -315,6 +317,8 @@ func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Ch
Info: info,
objMap: make(map[Object]*declInfo),
impMap: make(map[importKey]*Package),
usedVars: make(map[*Var]bool),
usedPkgNames: make(map[*PkgName]bool),
}
}
@@ -322,6 +326,8 @@ func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Ch
// The provided files must all belong to the same package.
func (check *Checker) initFiles(files []*ast.File) {
// start with a clean slate (check.Files may be called multiple times)
// TODO(gri): what determines which fields are zeroed out here, vs at the end
// of checkFiles?
check.files = nil
check.imports = nil
check.dotImportMap = nil
@@ -333,6 +339,13 @@ func (check *Checker) initFiles(files []*ast.File) {
check.objPath = nil
check.cleaners = nil
// We must initialize usedVars and usedPkgNames both here and in NewChecker,
// because initFiles is not called in the CheckExpr or Eval codepaths, yet we
// want to free this memory at the end of Files ('used' predicates are
// only needed in the context of a given file).
check.usedVars = make(map[*Var]bool)
check.usedPkgNames = make(map[*PkgName]bool)
// determine package name and collect valid files
pkg := check.pkg
for _, file := range files {
@@ -507,9 +520,12 @@ func (check *Checker) checkFiles(files []*ast.File) {
check.seenPkgMap = nil
check.brokenAliases = nil
check.unionTypeSets = nil
check.usedVars = nil
check.usedPkgNames = nil
check.ctxt = nil
// TODO(rFindley) There's more memory we should release at this point.
// TODO(gri): shouldn't the cleanup above occur after the bailout?
// TODO(gri) There's more memory we should release at this point.
}
// processDelayed processes all delayed actions pushed after top.

View File

@@ -245,13 +245,12 @@ func (a *object) cmp(b *object) int {
type PkgName struct {
object
imported *Package
used bool // set if the package was used
}
// NewPkgName returns a new PkgName object representing an imported package.
// The remaining arguments set the attributes found with all Objects.
func NewPkgName(pos token.Pos, pkg *Package, name string, imported *Package) *PkgName {
return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, black, nopos}, imported, false}
return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, black, nopos}, imported}
}
// Imported returns the package that was imported.
@@ -334,10 +333,10 @@ func (obj *TypeName) IsAlias() bool {
// A Variable represents a declared variable (including function parameters and results, and struct fields).
type Var struct {
object
origin *Var // if non-nil, the Var from which this one was instantiated
embedded bool // if set, the variable is an embedded struct field, and name is the type name
isField bool // var is struct field
used bool // set if the variable was used
origin *Var // if non-nil, the Var from which this one was instantiated
isParam bool // var is a param, for backport of 'used' check to go1.24 (go.dev/issue/72826)
}
// NewVar returns a new variable.
@@ -348,7 +347,7 @@ func NewVar(pos token.Pos, pkg *Package, name string, typ Type) *Var {
// NewParam returns a new variable representing a function parameter.
func NewParam(pos token.Pos, pkg *Package, name string, typ Type) *Var {
return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, used: true} // parameters are always 'used'
return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, isParam: true}
}
// NewField returns a new variable representing a struct field.

View File

@@ -310,7 +310,7 @@ func (check *Checker) collectObjects() {
if imp.fake {
// match 1.17 cmd/compile (not prescribed by spec)
pkgName.used = true
check.usedPkgNames[pkgName] = true
}
// add import to file scope
@@ -710,7 +710,7 @@ func (check *Checker) unusedImports() {
// (initialization), use the blank identifier as explicit package name."
for _, obj := range check.imports {
if !obj.used && obj.name != "_" {
if obj.name != "_" && !check.usedPkgNames[obj] {
check.errorUnusedPkg(obj)
}
}

View File

@@ -35,7 +35,7 @@ func TestSizeof(t *testing.T) {
{term{}, 12, 24},
// Objects
{PkgName{}, 48, 88},
{PkgName{}, 44, 80},
{Const{}, 48, 88},
{TypeName{}, 40, 72},
{Var{}, 48, 88},

View File

@@ -59,7 +59,7 @@ func (check *Checker) usage(scope *Scope) {
var unused []*Var
for name, elem := range scope.elems {
elem = resolve(name, elem)
if v, _ := elem.(*Var); v != nil && !v.used {
if v, _ := elem.(*Var); v != nil && !v.isParam && !check.usedVars[v] {
unused = append(unused, v)
}
}
@@ -777,13 +777,16 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
}
// If lhs exists, we must have at least one lhs variable that was used.
// (We can't use check.usage because that only looks at one scope; and
// we don't want to use the same variable for all scopes and change the
// variable type underfoot.)
if lhs != nil {
var used bool
for _, v := range lhsVars {
if v.used {
if check.usedVars[v] {
used = true
}
v.used = true // avoid usage error when checking entire function
check.usedVars[v] = true // avoid usage error when checking entire function
}
if !used {
check.softErrorf(lhs, UnusedVar, "%s declared and not used", lhs.Name)
@@ -952,7 +955,7 @@ func (check *Checker) rangeStmt(inner stmtContext, s *ast.RangeStmt) {
if typ == nil || typ == Typ[Invalid] {
// typ == Typ[Invalid] can happen if allowVersion fails.
obj.typ = Typ[Invalid]
obj.used = true // don't complain about unused variable
check.usedVars[obj] = true // don't complain about unused variable
continue
}

View File

@@ -54,7 +54,7 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *TypeName, wantType bo
// avoid "declared but not used" errors
// (don't use Checker.use - we don't want to evaluate too much)
if v, _ := obj.(*Var); v != nil && v.pkg == check.pkg /* see Checker.use1 */ {
v.used = true
check.usedVars[v] = true
}
return
}
@@ -82,7 +82,7 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *TypeName, wantType bo
// (This code is only needed for dot-imports. Without them,
// we only have to mark variables, see *Var case below).
if pkgName := check.dotImportMap[dotImportKey{scope, obj.Name()}]; pkgName != nil {
pkgName.used = true
check.usedPkgNames[pkgName] = true
}
switch obj := obj.(type) {
@@ -119,7 +119,7 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *TypeName, wantType bo
// from other packages to avoid potential race conditions with
// dot-imported variables.
if obj.pkg == check.pkg {
obj.used = true
check.usedVars[obj] = true
}
check.addDeclDep(obj)
if !isValid(typ) {

View File

@@ -59,8 +59,8 @@ var All = []Info{
{Name: "tlsmlkem", Package: "crypto/tls", Changed: 24, Old: "0", Opaque: true},
{Name: "tlsrsakex", Package: "crypto/tls", Changed: 22, Old: "1"},
{Name: "tlsunsafeekm", Package: "crypto/tls", Changed: 22, Old: "1"},
{Name: "winreadlinkvolume", Package: "os", Changed: 22, Old: "0"},
{Name: "winsymlink", Package: "os", Changed: 22, Old: "0"},
{Name: "winreadlinkvolume", Package: "os", Changed: 23, Old: "0"},
{Name: "winsymlink", Package: "os", Changed: 23, Old: "0"},
{Name: "x509keypairleaf", Package: "crypto/tls", Changed: 23, Old: "0"},
{Name: "x509negativeserial", Package: "crypto/x509", Changed: 23, Old: "1"},
{Name: "x509rsacrt", Package: "crypto/x509", Changed: 24, Old: "0"},

View File

@@ -504,3 +504,26 @@ func ParallelOn64Bit(t *testing.T) {
}
t.Parallel()
}
// CPUProfilingBroken returns true if CPU profiling has known issues on this
// platform.
func CPUProfilingBroken() bool {
switch runtime.GOOS {
case "plan9":
// Profiling unimplemented.
return true
case "aix":
// See https://golang.org/issue/45170.
return true
case "ios", "dragonfly", "netbsd", "illumos", "solaris":
// See https://golang.org/issue/13841.
return true
case "openbsd":
if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" {
// See https://golang.org/issue/13841.
return true
}
}
return false
}

View File

@@ -164,6 +164,19 @@ func readChunkLine(b *bufio.Reader) ([]byte, error) {
}
return nil, err
}
// RFC 9112 permits parsers to accept a bare \n as a line ending in headers,
// but not in chunked encoding lines. See https://www.rfc-editor.org/errata/eid7633,
// which explicitly rejects a clarification permitting \n as a chunk terminator.
//
// Verify that the line ends in a CRLF, and that no CRs appear before the end.
if idx := bytes.IndexByte(p, '\r'); idx == -1 {
return nil, errors.New("chunked line ends with bare LF")
} else if idx != len(p)-2 {
return nil, errors.New("invalid CR in chunked line")
}
p = p[:len(p)-2] // trim CRLF
if len(p) >= maxLineLength {
return nil, ErrLineTooLong
}
@@ -171,14 +184,14 @@ func readChunkLine(b *bufio.Reader) ([]byte, error) {
}
func trimTrailingWhitespace(b []byte) []byte {
for len(b) > 0 && isASCIISpace(b[len(b)-1]) {
for len(b) > 0 && isOWS(b[len(b)-1]) {
b = b[:len(b)-1]
}
return b
}
func isASCIISpace(b byte) bool {
return b == ' ' || b == '\t' || b == '\n' || b == '\r'
func isOWS(b byte) bool {
return b == ' ' || b == '\t'
}
var semi = []byte(";")

View File

@@ -280,6 +280,33 @@ func TestChunkReaderByteAtATime(t *testing.T) {
}
}
func TestChunkInvalidInputs(t *testing.T) {
for _, test := range []struct {
name string
b string
}{{
name: "bare LF in chunk size",
b: "1\na\r\n0\r\n",
}, {
name: "extra LF in chunk size",
b: "1\r\r\na\r\n0\r\n",
}, {
name: "bare LF in chunk data",
b: "1\r\na\n0\r\n",
}, {
name: "bare LF in chunk extension",
b: "1;\na\r\n0\r\n",
}} {
t.Run(test.name, func(t *testing.T) {
r := NewChunkedReader(strings.NewReader(test.b))
got, err := io.ReadAll(r)
if err == nil {
t.Fatalf("unexpectedly parsed invalid chunked data:\n%q", got)
}
})
}
}
type funcReader struct {
f func(iteration int) ([]byte, error)
i int

View File

@@ -13,6 +13,7 @@ import (
"compress/zlib"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
@@ -7303,3 +7304,120 @@ func testServerReadAfterHandlerAbort100Continue(t *testing.T, mode testMode) {
readyc <- struct{}{} // server starts reading from the request body
readyc <- struct{}{} // server finishes reading from the request body
}
// Issue #72100: Verify that we don't modify the caller's TLS.Config.NextProtos slice.
func TestServerTLSNextProtos(t *testing.T) {
run(t, testServerTLSNextProtos, []testMode{https1Mode, http2Mode})
}
func testServerTLSNextProtos(t *testing.T, mode testMode) {
CondSkipHTTP2(t)
cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey)
if err != nil {
t.Fatal(err)
}
leafCert, err := x509.ParseCertificate(cert.Certificate[0])
if err != nil {
t.Fatal(err)
}
certpool := x509.NewCertPool()
certpool.AddCert(leafCert)
protos := new(Protocols)
switch mode {
case https1Mode:
protos.SetHTTP1(true)
case http2Mode:
protos.SetHTTP2(true)
}
wantNextProtos := []string{"http/1.1", "h2", "other"}
nextProtos := slices.Clone(wantNextProtos)
// We don't use httptest here because it overrides the tls.Config.
srv := &Server{
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{cert},
NextProtos: nextProtos,
},
Handler: HandlerFunc(func(w ResponseWriter, req *Request) {}),
Protocols: protos,
}
tr := &Transport{
TLSClientConfig: &tls.Config{
RootCAs: certpool,
NextProtos: nextProtos,
},
Protocols: protos,
}
listener := newLocalListener(t)
srvc := make(chan error, 1)
go func() {
srvc <- srv.ServeTLS(listener, "", "")
}()
t.Cleanup(func() {
srv.Close()
<-srvc
})
client := &Client{Transport: tr}
resp, err := client.Get("https://" + listener.Addr().String())
if err != nil {
t.Fatal(err)
}
resp.Body.Close()
if !slices.Equal(nextProtos, wantNextProtos) {
t.Fatalf("after running test: original NextProtos slice = %v, want %v", nextProtos, wantNextProtos)
}
}
func TestInvalidChunkedBodies(t *testing.T) {
for _, test := range []struct {
name string
b string
}{{
name: "bare LF in chunk size",
b: "1\na\r\n0\r\n\r\n",
}, {
name: "bare LF at body end",
b: "1\r\na\r\n0\r\n\n",
}} {
t.Run(test.name, func(t *testing.T) {
reqc := make(chan error)
ts := newClientServerTest(t, http1Mode, HandlerFunc(func(w ResponseWriter, r *Request) {
got, err := io.ReadAll(r.Body)
if err == nil {
t.Logf("read body: %q", got)
}
reqc <- err
})).ts
serverURL, err := url.Parse(ts.URL)
if err != nil {
t.Fatal(err)
}
conn, err := net.Dial("tcp", serverURL.Host)
if err != nil {
t.Fatal(err)
}
if _, err := conn.Write([]byte(
"POST / HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Transfer-Encoding: chunked\r\n" +
"Connection: close\r\n" +
"\r\n" +
test.b)); err != nil {
t.Fatal(err)
}
conn.(*net.TCPConn).CloseWrite()
if err := <-reqc; err == nil {
t.Errorf("server handler: io.ReadAll(r.Body) succeeded, want error")
}
})
}
}

View File

@@ -3521,6 +3521,12 @@ func (s *Server) protocols() Protocols {
// adjustNextProtos adds or removes "http/1.1" and "h2" entries from
// a tls.Config.NextProtos list, according to the set of protocols in protos.
func adjustNextProtos(nextProtos []string, protos Protocols) []string {
// Make a copy of NextProtos since it might be shared with some other tls.Config.
// (tls.Config.Clone doesn't do a deep copy.)
//
// We could avoid an allocation in the common case by checking to see if the slice
// is already in order, but this is just one small allocation per connection.
nextProtos = slices.Clone(nextProtos)
var have Protocols
nextProtos = slices.DeleteFunc(nextProtos, func(s string) bool {
switch s {

View File

@@ -355,7 +355,9 @@ func cgocallbackg(fn, frame unsafe.Pointer, ctxt uintptr) {
gp.m.incgo = true
unlockOSThread()
if gp.m.isextra {
if gp.m.isextra && gp.m.ncgo == 0 {
// There are no active cgocalls above this frame (ncgo == 0),
// thus there can't be more Go frames above this frame.
gp.m.isExtraInC = true
}

View File

@@ -72,6 +72,22 @@ func TestCgoCallbackGC(t *testing.T) {
}
}
func TestCgoCallbackPprof(t *testing.T) {
t.Parallel()
switch runtime.GOOS {
case "plan9", "windows":
t.Skipf("no pthreads on %s", runtime.GOOS)
}
if testenv.CPUProfilingBroken() {
t.Skip("skipping on platform with broken profiling")
}
got := runTestProg(t, "testprogcgo", "CgoCallbackPprof")
if want := "OK\n"; got != want {
t.Fatalf("expected %q, but got:\n%s", want, got)
}
}
func TestCgoExternalThreadPanic(t *testing.T) {
t.Parallel()
if runtime.GOOS == "plan9" {

View File

@@ -6,6 +6,7 @@ package runtime_test
import (
"fmt"
"os"
"runtime"
"strings"
)
@@ -59,3 +60,36 @@ func ExampleFrames() {
// - more:true | runtime_test.ExampleFrames.func3
// - more:true | runtime_test.ExampleFrames
}
func ExampleAddCleanup() {
tempFile, err := os.CreateTemp(os.TempDir(), "file.*")
if err != nil {
fmt.Println("failed to create temp file:", err)
return
}
ch := make(chan struct{})
// Attach a cleanup function to the file object.
runtime.AddCleanup(&tempFile, func(fileName string) {
if err := os.Remove(fileName); err == nil {
fmt.Println("temp file has been removed")
}
ch <- struct{}{}
}, tempFile.Name())
if err := tempFile.Close(); err != nil {
fmt.Println("failed to close temp file:", err)
return
}
// Run the garbage collector to reclaim unreachable objects
// and enqueue their cleanup functions.
runtime.GC()
// Wait until cleanup function is done.
<-ch
// Output:
// temp file has been removed
}

View File

@@ -416,27 +416,6 @@ func parseProfile(t *testing.T, valBytes []byte, f func(uintptr, []*profile.Loca
return p
}
func cpuProfilingBroken() bool {
switch runtime.GOOS {
case "plan9":
// Profiling unimplemented.
return true
case "aix":
// See https://golang.org/issue/45170.
return true
case "ios", "dragonfly", "netbsd", "illumos", "solaris":
// See https://golang.org/issue/13841.
return true
case "openbsd":
if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" {
// See https://golang.org/issue/13841.
return true
}
}
return false
}
// testCPUProfile runs f under the CPU profiler, checking for some conditions specified by need,
// as interpreted by matches, and returns the parsed profile.
func testCPUProfile(t *testing.T, matches profileMatchFunc, f func(dur time.Duration)) *profile.Profile {
@@ -454,7 +433,7 @@ func testCPUProfile(t *testing.T, matches profileMatchFunc, f func(dur time.Dura
t.Skip("skipping on wasip1")
}
broken := cpuProfilingBroken()
broken := testenv.CPUProfilingBroken()
deadline, ok := t.Deadline()
if broken || !ok {

View File

@@ -419,14 +419,21 @@ func isAsyncSafePoint(gp *g, pc, sp, lr uintptr) (bool, uintptr) {
name := u.srcFunc(uf).name()
if stringslite.HasPrefix(name, "runtime.") ||
stringslite.HasPrefix(name, "runtime/internal/") ||
stringslite.HasPrefix(name, "internal/runtime/") ||
stringslite.HasPrefix(name, "reflect.") {
// For now we never async preempt the runtime or
// anything closely tied to the runtime. Known issues
// include: various points in the scheduler ("don't
// preempt between here and here"), much of the defer
// implementation (untyped info on stack), bulk write
// barriers (write barrier check),
// reflect.{makeFuncStub,methodValueCall}.
// barriers (write barrier check), atomic functions in
// internal/runtime/atomic, reflect.{makeFuncStub,methodValueCall}.
//
// Note that this is a subset of the runtimePkgs in pkgspecial.go
// and these checks are theoretically redundant because the compiler
// marks "all points" in runtime functions as unsafe for async preemption.
// But for some reason, we can't eliminate these checks until https://go.dev/issue/72031
// is resolved.
//
// TODO(austin): We should improve this, or opt things
// in incrementally.

View File

@@ -556,7 +556,7 @@ type m struct {
printlock int8
incgo bool // m is executing a cgo call
isextra bool // m is an extra m
isExtraInC bool // m is an extra m that is not executing Go code
isExtraInC bool // m is an extra m that does not have any Go frames
isExtraInSig bool // m is an extra m in a signal handler
freeWait atomic.Uint32 // Whether it is safe to free g0 and delete m (one of freeMRef, freeMStack, freeMWait)
needextram bool

View File

@@ -0,0 +1,138 @@
// 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 !plan9 && !windows
package main
// Regression test for https://go.dev/issue/72870. Go code called from C should
// never be reported as external code.
/*
#include <pthread.h>
void go_callback1();
void go_callback2();
static void *callback_pprof_thread(void *arg) {
go_callback1();
return 0;
}
static void c_callback(void) {
go_callback2();
}
static void start_callback_pprof_thread() {
pthread_t th;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&th, &attr, callback_pprof_thread, 0);
// Don't join, caller will watch pprof.
}
*/
import "C"
import (
"bytes"
"fmt"
"internal/profile"
"os"
"runtime/pprof"
"time"
)
func init() {
register("CgoCallbackPprof", CgoCallbackPprof)
}
func CgoCallbackPprof() {
C.start_callback_pprof_thread()
var buf bytes.Buffer
if err := pprof.StartCPUProfile(&buf); err != nil {
fmt.Printf("Error starting CPU profile: %v\n", err)
os.Exit(1)
}
time.Sleep(1 * time.Second)
pprof.StopCPUProfile()
p, err := profile.Parse(&buf)
if err != nil {
fmt.Printf("Error parsing profile: %v\n", err)
os.Exit(1)
}
foundCallee := false
for _, s := range p.Sample {
funcs := flattenFrames(s)
if len(funcs) == 0 {
continue
}
leaf := funcs[0]
if leaf.Name != "main.go_callback1_callee" {
continue
}
foundCallee = true
if len(funcs) < 2 {
fmt.Printf("Profile: %s\n", p)
frames := make([]string, len(funcs))
for i := range funcs {
frames[i] = funcs[i].Name
}
fmt.Printf("FAIL: main.go_callback1_callee sample missing caller in frames %v\n", frames)
os.Exit(1)
}
if funcs[1].Name != "main.go_callback1" {
// In https://go.dev/issue/72870, this will be runtime._ExternalCode.
fmt.Printf("Profile: %s\n", p)
frames := make([]string, len(funcs))
for i := range funcs {
frames[i] = funcs[i].Name
}
fmt.Printf("FAIL: main.go_callback1_callee sample caller got %s want main.go_callback1 in frames %v\n", funcs[1].Name, frames)
os.Exit(1)
}
}
if !foundCallee {
fmt.Printf("Missing main.go_callback1_callee sample in profile %s\n", p)
os.Exit(1)
}
fmt.Printf("OK\n")
}
// Return the frame functions in s, regardless of inlining.
func flattenFrames(s *profile.Sample) []*profile.Function {
ret := make([]*profile.Function, 0, len(s.Location))
for _, loc := range s.Location {
for _, line := range loc.Line {
ret = append(ret, line.Function)
}
}
return ret
}
//export go_callback1
func go_callback1() {
// This is a separate function just to ensure we have another Go
// function as the caller in the profile.
go_callback1_callee()
}
func go_callback1_callee() {
C.c_callback()
// Spin for CPU samples.
for {
}
}
//export go_callback2
func go_callback2() {
}

View File

@@ -114,10 +114,22 @@ type B struct {
netBytes uint64
// Extra metrics collected by ReportMetric.
extra map[string]float64
// For Loop() to be executed in benchFunc.
// Loop() has its own control logic that skips the loop scaling.
// See issue #61515.
loopN int
// loop tracks the state of B.Loop
loop struct {
// n is the target number of iterations. It gets bumped up as we go.
// When the benchmark loop is done, we commit this to b.N so users can
// do reporting based on it, but we avoid exposing it until then.
n uint64
// i is the current Loop iteration. It's strictly monotonically
// increasing toward n.
//
// The high bit is used to poison the Loop fast path and fall back to
// the slow path.
i uint64
done bool // set when B.Loop return false
}
}
// StartTimer starts timing a test. This function is called automatically
@@ -130,6 +142,7 @@ func (b *B) StartTimer() {
b.startBytes = memStats.TotalAlloc
b.start = highPrecisionTimeNow()
b.timerOn = true
b.loop.i &^= loopPoisonTimer
}
}
@@ -142,6 +155,8 @@ func (b *B) StopTimer() {
b.netAllocs += memStats.Mallocs - b.startAllocs
b.netBytes += memStats.TotalAlloc - b.startBytes
b.timerOn = false
// If we hit B.Loop with the timer stopped, fail.
b.loop.i |= loopPoisonTimer
}
}
@@ -192,7 +207,9 @@ func (b *B) runN(n int) {
runtime.GC()
b.resetRaces()
b.N = n
b.loopN = 0
b.loop.n = 0
b.loop.i = 0
b.loop.done = false
b.ctx = ctx
b.cancelCtx = cancelCtx
@@ -203,6 +220,10 @@ func (b *B) runN(n int) {
b.StopTimer()
b.previousN = n
b.previousDuration = b.duration
if b.loop.n > 0 && !b.loop.done && !b.failed {
b.Error("benchmark function returned without B.Loop() == false (break or return in loop?)")
}
}
// run1 runs the first iteration of benchFunc. It reports whether more
@@ -312,8 +333,8 @@ func (b *B) launch() {
}()
// b.Loop does its own ramp-up logic so we just need to run it once.
// If b.loopN is non zero, it means b.Loop has already run.
if b.loopN == 0 {
// If b.loop.n is non zero, it means b.Loop has already run.
if b.loop.n == 0 {
// Run the benchmark for at least the specified amount of time.
if b.benchTime.n > 0 {
// We already ran a single iteration in run1.
@@ -368,38 +389,59 @@ func (b *B) ReportMetric(n float64, unit string) {
}
func (b *B) stopOrScaleBLoop() bool {
timeElapsed := highPrecisionTimeSince(b.start)
if timeElapsed >= b.benchTime.d {
t := b.Elapsed()
if t >= b.benchTime.d {
// Stop the timer so we don't count cleanup time
b.StopTimer()
// Commit iteration count
b.N = int(b.loop.n)
b.loop.done = true
return false
}
// Loop scaling
goalns := b.benchTime.d.Nanoseconds()
prevIters := int64(b.N)
b.N = predictN(goalns, prevIters, timeElapsed.Nanoseconds(), prevIters)
b.loopN++
prevIters := int64(b.loop.n)
b.loop.n = uint64(predictN(goalns, prevIters, t.Nanoseconds(), prevIters))
if b.loop.n&loopPoisonMask != 0 {
// The iteration count should never get this high, but if it did we'd be
// in big trouble.
panic("loop iteration target overflow")
}
b.loop.i++
return true
}
func (b *B) loopSlowPath() bool {
if b.loopN == 0 {
// Consistency checks
if !b.timerOn {
b.Fatal("B.Loop called with timer stopped")
}
if b.loop.i&loopPoisonMask != 0 {
panic(fmt.Sprintf("unknown loop stop condition: %#x", b.loop.i))
}
if b.loop.n == 0 {
// If it's the first call to b.Loop() in the benchmark function.
// Allows more precise measurement of benchmark loop cost counts.
// Also initialize b.N to 1 to kick start loop scaling.
b.N = 1
b.loopN = 1
// Also initialize target to 1 to kick start loop scaling.
b.loop.n = 1
// Within a b.Loop loop, we don't use b.N (to avoid confusion).
b.N = 0
b.loop.i++
b.ResetTimer()
return true
}
// Handles fixed iterations case
if b.benchTime.n > 0 {
if b.N < b.benchTime.n {
b.N = b.benchTime.n
b.loopN++
if b.loop.n < uint64(b.benchTime.n) {
b.loop.n = uint64(b.benchTime.n)
b.loop.i++
return true
}
b.StopTimer()
// Commit iteration count
b.N = int(b.loop.n)
b.loop.done = true
return false
}
// Handles fixed time case
@@ -440,13 +482,38 @@ func (b *B) loopSlowPath() bool {
// whereas b.N-based benchmarks must run the benchmark function (and any
// associated setup and cleanup) several times.
func (b *B) Loop() bool {
if b.loopN != 0 && b.loopN < b.N {
b.loopN++
// This is written such that the fast path is as fast as possible and can be
// inlined.
//
// There are three cases where we'll fall out of the fast path:
//
// - On the first call, both i and n are 0.
//
// - If the loop reaches the n'th iteration, then i == n and we need
// to figure out the new target iteration count or if we're done.
//
// - If the timer is stopped, it poisons the top bit of i so the slow
// path can do consistency checks and fail.
if b.loop.i < b.loop.n {
b.loop.i++
return true
}
return b.loopSlowPath()
}
// The loopPoison constants can be OR'd into B.loop.i to cause it to fall back
// to the slow path.
const (
loopPoisonTimer = uint64(1 << (63 - iota))
// If necessary, add more poison bits here.
// loopPoisonMask is the set of all loop poison bits. (iota-1) is the index
// of the bit we just set, from which we recreate that bit mask. We subtract
// 1 to set all of the bits below that bit, then complement the result to
// get the mask. Sorry, not sorry.
loopPoisonMask = ^uint64((1 << (63 - (iota - 1))) - 1)
)
// BenchmarkResult contains the results of a benchmark run.
type BenchmarkResult struct {
N int // The number of iterations.

View File

@@ -4,13 +4,22 @@
package testing
import (
"bytes"
"strings"
)
// See also TestBenchmarkBLoop* in other files.
func TestBenchmarkBLoop(t *T) {
var initialStart highPrecisionTime
var firstStart highPrecisionTime
var lastStart highPrecisionTime
var scaledStart highPrecisionTime
var runningEnd bool
runs := 0
iters := 0
firstBN := 0
restBN := 0
finalBN := 0
bRet := Benchmark(func(b *B) {
initialStart = b.start
@@ -18,8 +27,13 @@ func TestBenchmarkBLoop(t *T) {
for b.Loop() {
if iters == 0 {
firstStart = b.start
firstBN = b.N
} else {
restBN = max(restBN, b.N)
}
if iters == 1 {
scaledStart = b.start
}
lastStart = b.start
iters++
}
finalBN = b.N
@@ -37,6 +51,13 @@ func TestBenchmarkBLoop(t *T) {
if finalBN != iters || bRet.N != iters {
t.Errorf("benchmark iterations mismatch: %d loop iterations, final b.N=%d, bRet.N=%d", iters, finalBN, bRet.N)
}
// Verify that b.N was 0 inside the loop
if firstBN != 0 {
t.Errorf("want b.N == 0 on first iteration, got %d", firstBN)
}
if restBN != 0 {
t.Errorf("want b.N == 0 on subsequent iterations, got %d", restBN)
}
// Make sure the benchmark ran for an appropriate amount of time.
if bRet.T < benchTime.d {
t.Fatalf("benchmark ran for %s, want >= %s", bRet.T, benchTime.d)
@@ -45,8 +66,8 @@ func TestBenchmarkBLoop(t *T) {
if firstStart == initialStart {
t.Errorf("b.Loop did not reset the timer")
}
if lastStart != firstStart {
t.Errorf("timer was reset during iteration")
if scaledStart != firstStart {
t.Errorf("b.Loop stops and restarts the timer during iteration")
}
// Verify that it stopped the timer after the last loop.
if runningEnd {
@@ -54,4 +75,80 @@ func TestBenchmarkBLoop(t *T) {
}
}
// See also TestBenchmarkBLoop* in other files.
func TestBenchmarkBLoopBreak(t *T) {
var bState *B
var bLog bytes.Buffer
bRet := Benchmark(func(b *B) {
// The Benchmark function provides no access to the failure state and
// discards the log, so capture the B and save its log.
bState = b
b.common.w = &bLog
for i := 0; b.Loop(); i++ {
if i == 2 {
break
}
}
})
if !bState.failed {
t.Errorf("benchmark should have failed")
}
const wantLog = "benchmark function returned without B.Loop"
if log := bLog.String(); !strings.Contains(log, wantLog) {
t.Errorf("missing error %q in output:\n%s", wantLog, log)
}
// A benchmark that exits early should not report its target iteration count
// because it's not meaningful.
if bRet.N != 0 {
t.Errorf("want N == 0, got %d", bRet.N)
}
}
func TestBenchmarkBLoopError(t *T) {
// Test that a benchmark that exits early because of an error doesn't *also*
// complain that the benchmark exited early.
var bState *B
var bLog bytes.Buffer
bRet := Benchmark(func(b *B) {
bState = b
b.common.w = &bLog
for i := 0; b.Loop(); i++ {
b.Error("error")
return
}
})
if !bState.failed {
t.Errorf("benchmark should have failed")
}
const noWantLog = "benchmark function returned without B.Loop"
if log := bLog.String(); strings.Contains(log, noWantLog) {
t.Errorf("unexpected error %q in output:\n%s", noWantLog, log)
}
if bRet.N != 0 {
t.Errorf("want N == 0, got %d", bRet.N)
}
}
func TestBenchmarkBLoopStop(t *T) {
var bState *B
var bLog bytes.Buffer
bRet := Benchmark(func(b *B) {
bState = b
b.common.w = &bLog
for i := 0; b.Loop(); i++ {
b.StopTimer()
}
})
if !bState.failed {
t.Errorf("benchmark should have failed")
}
const wantLog = "B.Loop called with timer stopped"
if log := bLog.String(); !strings.Contains(log, wantLog) {
t.Errorf("missing error %q in output:\n%s", wantLog, log)
}
if bRet.N != 0 {
t.Errorf("want N == 0, got %d", bRet.N)
}
}

View File

@@ -0,0 +1,85 @@
// build
// 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 (
"iter"
)
type leafSet map[rune]struct{}
type branchMap map[rune]*node
func (bm branchMap) findOrCreateBranch(r rune) *node {
if _, ok := bm[r]; !ok {
bm[r] = newNode()
}
return bm[r]
}
func (bm branchMap) allSuffixes() iter.Seq[string] {
return func(yield func(string) bool) {
for r, n := range bm {
for s := range n.allStrings() {
if !yield(string(r) + s) {
return
}
}
}
}
}
type node struct {
leafSet
branchMap
}
func newNode() *node {
return &node{make(leafSet), make(branchMap)}
}
func (n *node) add(s []rune) {
switch len(s) {
case 0:
return
case 1:
n.leafSet[s[0]] = struct{}{}
default:
n.branchMap.findOrCreateBranch(s[0]).add(s[1:])
}
}
func (n *node) addString(s string) {
n.add([]rune(s))
}
func (n *node) allStrings() iter.Seq[string] {
return func(yield func(string) bool) {
for s := range n.leafSet {
if !yield(string(s)) {
return
}
}
for r, n := range n.branchMap {
for s := range n.allSuffixes() {
if !yield(string(r) + s) {
return
}
}
}
}
}
func main() {
root := newNode()
for _, s := range []string{"foo", "bar", "baz", "a", "b", "c", "hello", "world"} {
root.addString(s)
}
for s := range root.allStrings() {
println(s)
}
}

View File

@@ -1,4 +1,4 @@
// errorcheck -0 -m=2
// errorcheck -0 -m
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
@@ -15,7 +15,7 @@ func caninline(x int) int { // ERROR "can inline caninline"
return x
}
func cannotinline(b *testing.B) { // ERROR "b does not escape" "cannot inline cannotinline.*"
func test(b *testing.B) { // ERROR "leaking param: b"
for i := 0; i < b.N; i++ {
caninline(1) // ERROR "inlining call to caninline"
}