Update to go1.24.2
This commit is contained in:
4
VERSION
4
VERSION
@@ -1,2 +1,2 @@
|
||||
go1.24.1
|
||||
time 2025-02-27T17:57:18Z
|
||||
go1.24.2
|
||||
time 2025-03-26T19:09:39Z
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(";")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
138
src/runtime/testdata/testprogcgo/callback_pprof.go
vendored
Normal file
138
src/runtime/testdata/testprogcgo/callback_pprof.go
vendored
Normal 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() {
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
85
test/fixedbugs/issue72090.go
Normal file
85
test/fixedbugs/issue72090.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user