Update to go1.24.2
This commit is contained in:
4
VERSION
4
VERSION
@@ -1,2 +1,2 @@
|
|||||||
go1.24.1
|
go1.24.2
|
||||||
time 2025-02-27T17:57:18Z
|
time 2025-03-26T19:09:39Z
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import (
|
|||||||
"cmd/compile/internal/types"
|
"cmd/compile/internal/types"
|
||||||
"cmd/internal/obj"
|
"cmd/internal/obj"
|
||||||
"cmd/internal/pgo"
|
"cmd/internal/pgo"
|
||||||
|
"cmd/internal/src"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Inlining budget parameters, gathered in one place
|
// 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
|
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
|
// canInlineCallExpr returns true if the call n from caller to callee
|
||||||
// can be inlined, plus the score computed for the call expr in question,
|
// can be inlined, plus the score computed for the call expr in question,
|
||||||
// and whether the callee is hot according to PGO.
|
// 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
|
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 {
|
if callee == callerfn {
|
||||||
// Can't recursively inline a function into itself.
|
// Can't recursively inline a function into itself.
|
||||||
if log && logopt.Enabled() {
|
if log && logopt.Enabled() {
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ func (s *inlClosureState) mark(n ir.Node) ir.Node {
|
|||||||
|
|
||||||
if isTestingBLoop(n) {
|
if isTestingBLoop(n) {
|
||||||
// No inlining nor devirtualization performed on b.Loop body
|
// 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)
|
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.
|
// 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]).Store",
|
||||||
"(*Pointer[go.shape.int]).Swap",
|
"(*Pointer[go.shape.int]).Swap",
|
||||||
},
|
},
|
||||||
|
"testing": {
|
||||||
|
"(*B).Loop",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if !goexperiment.SwissMap {
|
if !goexperiment.SwissMap {
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ func (check *Checker) lhsVar(lhs syntax.Expr) Type {
|
|||||||
// dot-imported variables.
|
// dot-imported variables.
|
||||||
if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg {
|
if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg {
|
||||||
v = w
|
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)
|
check.expr(nil, &x, lhs)
|
||||||
|
|
||||||
if v != nil {
|
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) {
|
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 {
|
if pname, _ := obj.(*PkgName); pname != nil {
|
||||||
assert(pname.pkg == check.pkg)
|
assert(pname.pkg == check.pkg)
|
||||||
check.recordUse(ident, pname)
|
check.recordUse(ident, pname)
|
||||||
pname.used = true
|
check.usedPkgNames[pname] = true
|
||||||
pkg := pname.imported
|
pkg := pname.imported
|
||||||
|
|
||||||
var exp Object
|
var exp Object
|
||||||
@@ -972,13 +972,13 @@ func (check *Checker) use1(e syntax.Expr, lhs bool) bool {
|
|||||||
// dot-imported variables.
|
// dot-imported variables.
|
||||||
if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg {
|
if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg {
|
||||||
v = w
|
v = w
|
||||||
v_used = v.used
|
v_used = check.usedVars[v]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
check.exprOrType(&x, n, true)
|
check.exprOrType(&x, n, true)
|
||||||
if v != nil {
|
if v != nil {
|
||||||
v.used = v_used // restore v.used
|
check.usedVars[v] = v_used // restore v.used
|
||||||
}
|
}
|
||||||
case *syntax.ListExpr:
|
case *syntax.ListExpr:
|
||||||
return check.useN(n.ElemList, lhs)
|
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
|
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
|
brokenAliases map[*TypeName]bool // set of aliases with broken (not yet determined) types
|
||||||
unionTypeSets map[*Union]*_TypeSet // computed type sets for union 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
|
mono monoGraph // graph for detecting non-monomorphizable instantiation loops
|
||||||
|
|
||||||
firstErr error // first error encountered
|
firstErr error // first error encountered
|
||||||
@@ -285,12 +287,14 @@ func NewChecker(conf *Config, pkg *Package, info *Info) *Checker {
|
|||||||
// (previously, pkg.goVersion was mutated here: go.dev/issue/61212)
|
// (previously, pkg.goVersion was mutated here: go.dev/issue/61212)
|
||||||
|
|
||||||
return &Checker{
|
return &Checker{
|
||||||
conf: conf,
|
conf: conf,
|
||||||
ctxt: conf.Context,
|
ctxt: conf.Context,
|
||||||
pkg: pkg,
|
pkg: pkg,
|
||||||
Info: info,
|
Info: info,
|
||||||
objMap: make(map[Object]*declInfo),
|
objMap: make(map[Object]*declInfo),
|
||||||
impMap: make(map[importKey]*Package),
|
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.
|
// The provided files must all belong to the same package.
|
||||||
func (check *Checker) initFiles(files []*syntax.File) {
|
func (check *Checker) initFiles(files []*syntax.File) {
|
||||||
// start with a clean slate (check.Files may be called multiple times)
|
// 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.files = nil
|
||||||
check.imports = nil
|
check.imports = nil
|
||||||
check.dotImportMap = nil
|
check.dotImportMap = nil
|
||||||
@@ -309,6 +315,13 @@ func (check *Checker) initFiles(files []*syntax.File) {
|
|||||||
check.objPath = nil
|
check.objPath = nil
|
||||||
check.cleaners = 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
|
// determine package name and collect valid files
|
||||||
pkg := check.pkg
|
pkg := check.pkg
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
@@ -482,8 +495,11 @@ func (check *Checker) checkFiles(files []*syntax.File) {
|
|||||||
check.seenPkgMap = nil
|
check.seenPkgMap = nil
|
||||||
check.brokenAliases = nil
|
check.brokenAliases = nil
|
||||||
check.unionTypeSets = nil
|
check.unionTypeSets = nil
|
||||||
|
check.usedVars = nil
|
||||||
|
check.usedPkgNames = nil
|
||||||
check.ctxt = 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.
|
// 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 {
|
type PkgName struct {
|
||||||
object
|
object
|
||||||
imported *Package
|
imported *Package
|
||||||
used bool // set if the package was used
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPkgName returns a new PkgName object representing an imported package.
|
// NewPkgName returns a new PkgName object representing an imported package.
|
||||||
// The remaining arguments set the attributes found with all Objects.
|
// The remaining arguments set the attributes found with all Objects.
|
||||||
func NewPkgName(pos syntax.Pos, pkg *Package, name string, imported *Package) *PkgName {
|
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.
|
// 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).
|
// A Variable represents a declared variable (including function parameters and results, and struct fields).
|
||||||
type Var struct {
|
type Var struct {
|
||||||
object
|
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
|
embedded bool // if set, the variable is an embedded struct field, and name is the type name
|
||||||
isField bool // var is struct field
|
isField bool // var is struct field
|
||||||
used bool // set if the variable was used
|
isParam bool // var is a param, for backport of 'used' check to go1.24 (go.dev/issue/72826)
|
||||||
origin *Var // if non-nil, the Var from which this one was instantiated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewVar returns a new variable.
|
// 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.
|
// NewParam returns a new variable representing a function parameter.
|
||||||
func NewParam(pos syntax.Pos, pkg *Package, name string, typ Type) *Var {
|
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.
|
// NewField returns a new variable representing a struct field.
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ func (check *Checker) collectObjects() {
|
|||||||
|
|
||||||
if imp.fake {
|
if imp.fake {
|
||||||
// match 1.17 cmd/compile (not prescribed by spec)
|
// match 1.17 cmd/compile (not prescribed by spec)
|
||||||
pkgName.used = true
|
check.usedPkgNames[pkgName] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// add import to file scope
|
// add import to file scope
|
||||||
@@ -715,7 +715,7 @@ func (check *Checker) unusedImports() {
|
|||||||
// (initialization), use the blank identifier as explicit package name."
|
// (initialization), use the blank identifier as explicit package name."
|
||||||
|
|
||||||
for _, obj := range check.imports {
|
for _, obj := range check.imports {
|
||||||
if !obj.used && obj.name != "_" {
|
if obj.name != "_" && !check.usedPkgNames[obj] {
|
||||||
check.errorUnusedPkg(obj)
|
check.errorUnusedPkg(obj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func TestSizeof(t *testing.T) {
|
|||||||
{term{}, 12, 24},
|
{term{}, 12, 24},
|
||||||
|
|
||||||
// Objects
|
// Objects
|
||||||
{PkgName{}, 64, 104},
|
{PkgName{}, 60, 96},
|
||||||
{Const{}, 64, 104},
|
{Const{}, 64, 104},
|
||||||
{TypeName{}, 56, 88},
|
{TypeName{}, 56, 88},
|
||||||
{Var{}, 64, 104},
|
{Var{}, 64, 104},
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (check *Checker) usage(scope *Scope) {
|
|||||||
var unused []*Var
|
var unused []*Var
|
||||||
for name, elem := range scope.elems {
|
for name, elem := range scope.elems {
|
||||||
elem = resolve(name, elem)
|
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)
|
unused = append(unused, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -824,10 +824,10 @@ func (check *Checker) typeSwitchStmt(inner stmtContext, s *syntax.SwitchStmt, gu
|
|||||||
if lhs != nil {
|
if lhs != nil {
|
||||||
var used bool
|
var used bool
|
||||||
for _, v := range lhsVars {
|
for _, v := range lhsVars {
|
||||||
if v.used {
|
if check.usedVars[v] {
|
||||||
used = true
|
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 {
|
if !used {
|
||||||
check.softErrorf(lhs, UnusedVar, "%s declared and not used", lhs.Value)
|
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] {
|
if typ == nil || typ == Typ[Invalid] {
|
||||||
// typ == Typ[Invalid] can happen if allowVersion fails.
|
// typ == Typ[Invalid] can happen if allowVersion fails.
|
||||||
obj.typ = Typ[Invalid]
|
obj.typ = Typ[Invalid]
|
||||||
obj.used = true // don't complain about unused variable
|
check.usedVars[obj] = true // don't complain about unused variable
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType
|
|||||||
// avoid "declared but not used" errors
|
// avoid "declared but not used" errors
|
||||||
// (don't use Checker.use - we don't want to evaluate too much)
|
// (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 */ {
|
if v, _ := obj.(*Var); v != nil && v.pkg == check.pkg /* see Checker.use1 */ {
|
||||||
v.used = true
|
check.usedVars[v] = true
|
||||||
}
|
}
|
||||||
return
|
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,
|
// (This code is only needed for dot-imports. Without them,
|
||||||
// we only have to mark variables, see *Var case below).
|
// we only have to mark variables, see *Var case below).
|
||||||
if pkgName := check.dotImportMap[dotImportKey{scope, obj.Name()}]; pkgName != nil {
|
if pkgName := check.dotImportMap[dotImportKey{scope, obj.Name()}]; pkgName != nil {
|
||||||
pkgName.used = true
|
check.usedPkgNames[pkgName] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
switch obj := obj.(type) {
|
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
|
// from other packages to avoid potential race conditions with
|
||||||
// dot-imported variables.
|
// dot-imported variables.
|
||||||
if obj.pkg == check.pkg {
|
if obj.pkg == check.pkg {
|
||||||
obj.used = true
|
check.usedVars[obj] = true
|
||||||
}
|
}
|
||||||
check.addDeclDep(obj)
|
check.addDeclDep(obj)
|
||||||
if !isValid(typ) {
|
if !isValid(typ) {
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ type PkgSpecial struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var runtimePkgs = []string{
|
var runtimePkgs = []string{
|
||||||
|
// TODO(panjf2000): consider syncing the list inside the
|
||||||
|
// isAsyncSafePoint in preempt.go based on this list?
|
||||||
|
|
||||||
"runtime",
|
"runtime",
|
||||||
|
|
||||||
"internal/runtime/atomic",
|
"internal/runtime/atomic",
|
||||||
|
|||||||
@@ -92,7 +92,8 @@ var defaultCipherSuitesTLS13NoAES = []uint16{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The FIPS-only policies below match BoringSSL's
|
// 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
|
// https://cs.opensource.google/boringssl/boringssl/+/master:ssl/ssl_lib.cc;l=3289;drc=ea7a88fa
|
||||||
|
|
||||||
var defaultSupportedVersionsFIPS = []uint16{
|
var defaultSupportedVersionsFIPS = []uint16{
|
||||||
@@ -102,7 +103,7 @@ var defaultSupportedVersionsFIPS = []uint16{
|
|||||||
|
|
||||||
// defaultCurvePreferencesFIPS are the FIPS-allowed curves,
|
// defaultCurvePreferencesFIPS are the FIPS-allowed curves,
|
||||||
// in preference order (most preferable first).
|
// in preference order (most preferable first).
|
||||||
var defaultCurvePreferencesFIPS = []CurveID{CurveP256, CurveP384}
|
var defaultCurvePreferencesFIPS = []CurveID{CurveP256, CurveP384, CurveP521}
|
||||||
|
|
||||||
// defaultSupportedSignatureAlgorithmsFIPS currently are a subset of
|
// defaultSupportedSignatureAlgorithmsFIPS currently are a subset of
|
||||||
// defaultSupportedSignatureAlgorithms without Ed25519 and SHA-1.
|
// defaultSupportedSignatureAlgorithms without Ed25519 and SHA-1.
|
||||||
@@ -115,6 +116,7 @@ var defaultSupportedSignatureAlgorithmsFIPS = []SignatureScheme{
|
|||||||
PKCS1WithSHA384,
|
PKCS1WithSHA384,
|
||||||
ECDSAWithP384AndSHA384,
|
ECDSAWithP384AndSHA384,
|
||||||
PKCS1WithSHA512,
|
PKCS1WithSHA512,
|
||||||
|
ECDSAWithP521AndSHA512,
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultCipherSuitesFIPS are the FIPS-allowed cipher suites.
|
// defaultCipherSuitesFIPS are the FIPS-allowed cipher suites.
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ func isFIPSCipherSuite(id uint16) bool {
|
|||||||
|
|
||||||
func isFIPSCurve(id CurveID) bool {
|
func isFIPSCurve(id CurveID) bool {
|
||||||
switch id {
|
switch id {
|
||||||
case CurveP256, CurveP384:
|
case CurveP256, CurveP384, CurveP521:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -130,6 +130,7 @@ func isFIPSSignatureScheme(alg SignatureScheme) bool {
|
|||||||
PKCS1WithSHA384,
|
PKCS1WithSHA384,
|
||||||
ECDSAWithP384AndSHA384,
|
ECDSAWithP384AndSHA384,
|
||||||
PKCS1WithSHA512,
|
PKCS1WithSHA512,
|
||||||
|
ECDSAWithP521AndSHA512,
|
||||||
PSSWithSHA256,
|
PSSWithSHA256,
|
||||||
PSSWithSHA384,
|
PSSWithSHA384,
|
||||||
PSSWithSHA512:
|
PSSWithSHA512:
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ func (check *Checker) lhsVar(lhs ast.Expr) Type {
|
|||||||
// dot-imported variables.
|
// dot-imported variables.
|
||||||
if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg {
|
if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg {
|
||||||
v = w
|
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)
|
check.expr(nil, &x, lhs)
|
||||||
|
|
||||||
if v != nil {
|
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) {
|
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 {
|
if pname, _ := obj.(*PkgName); pname != nil {
|
||||||
assert(pname.pkg == check.pkg)
|
assert(pname.pkg == check.pkg)
|
||||||
check.recordUse(ident, pname)
|
check.recordUse(ident, pname)
|
||||||
pname.used = true
|
check.usedPkgNames[pname] = true
|
||||||
pkg := pname.imported
|
pkg := pname.imported
|
||||||
|
|
||||||
var exp Object
|
var exp Object
|
||||||
@@ -1020,13 +1020,13 @@ func (check *Checker) use1(e ast.Expr, lhs bool) bool {
|
|||||||
// dot-imported variables.
|
// dot-imported variables.
|
||||||
if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg {
|
if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg {
|
||||||
v = w
|
v = w
|
||||||
v_used = v.used
|
v_used = check.usedVars[v]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
check.exprOrType(&x, n, true)
|
check.exprOrType(&x, n, true)
|
||||||
if v != nil {
|
if v != nil {
|
||||||
v.used = v_used // restore v.used
|
check.usedVars[v] = v_used // restore v.used
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
check.rawExpr(nil, &x, e, nil, true)
|
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
|
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
|
brokenAliases map[*TypeName]bool // set of aliases with broken (not yet determined) types
|
||||||
unionTypeSets map[*Union]*_TypeSet // computed type sets for union 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
|
mono monoGraph // graph for detecting non-monomorphizable instantiation loops
|
||||||
|
|
||||||
firstErr error // first error encountered
|
firstErr error // first error encountered
|
||||||
@@ -308,13 +310,15 @@ func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Ch
|
|||||||
conf._EnableAlias = gotypesalias.Value() != "0"
|
conf._EnableAlias = gotypesalias.Value() != "0"
|
||||||
|
|
||||||
return &Checker{
|
return &Checker{
|
||||||
conf: conf,
|
conf: conf,
|
||||||
ctxt: conf.Context,
|
ctxt: conf.Context,
|
||||||
fset: fset,
|
fset: fset,
|
||||||
pkg: pkg,
|
pkg: pkg,
|
||||||
Info: info,
|
Info: info,
|
||||||
objMap: make(map[Object]*declInfo),
|
objMap: make(map[Object]*declInfo),
|
||||||
impMap: make(map[importKey]*Package),
|
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.
|
// The provided files must all belong to the same package.
|
||||||
func (check *Checker) initFiles(files []*ast.File) {
|
func (check *Checker) initFiles(files []*ast.File) {
|
||||||
// start with a clean slate (check.Files may be called multiple times)
|
// 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.files = nil
|
||||||
check.imports = nil
|
check.imports = nil
|
||||||
check.dotImportMap = nil
|
check.dotImportMap = nil
|
||||||
@@ -333,6 +339,13 @@ func (check *Checker) initFiles(files []*ast.File) {
|
|||||||
check.objPath = nil
|
check.objPath = nil
|
||||||
check.cleaners = 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
|
// determine package name and collect valid files
|
||||||
pkg := check.pkg
|
pkg := check.pkg
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
@@ -507,9 +520,12 @@ func (check *Checker) checkFiles(files []*ast.File) {
|
|||||||
check.seenPkgMap = nil
|
check.seenPkgMap = nil
|
||||||
check.brokenAliases = nil
|
check.brokenAliases = nil
|
||||||
check.unionTypeSets = nil
|
check.unionTypeSets = nil
|
||||||
|
check.usedVars = nil
|
||||||
|
check.usedPkgNames = nil
|
||||||
check.ctxt = 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.
|
// processDelayed processes all delayed actions pushed after top.
|
||||||
|
|||||||
@@ -245,13 +245,12 @@ func (a *object) cmp(b *object) int {
|
|||||||
type PkgName struct {
|
type PkgName struct {
|
||||||
object
|
object
|
||||||
imported *Package
|
imported *Package
|
||||||
used bool // set if the package was used
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPkgName returns a new PkgName object representing an imported package.
|
// NewPkgName returns a new PkgName object representing an imported package.
|
||||||
// The remaining arguments set the attributes found with all Objects.
|
// The remaining arguments set the attributes found with all Objects.
|
||||||
func NewPkgName(pos token.Pos, pkg *Package, name string, imported *Package) *PkgName {
|
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.
|
// 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).
|
// A Variable represents a declared variable (including function parameters and results, and struct fields).
|
||||||
type Var struct {
|
type Var struct {
|
||||||
object
|
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
|
embedded bool // if set, the variable is an embedded struct field, and name is the type name
|
||||||
isField bool // var is struct field
|
isField bool // var is struct field
|
||||||
used bool // set if the variable was used
|
isParam bool // var is a param, for backport of 'used' check to go1.24 (go.dev/issue/72826)
|
||||||
origin *Var // if non-nil, the Var from which this one was instantiated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewVar returns a new variable.
|
// 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.
|
// NewParam returns a new variable representing a function parameter.
|
||||||
func NewParam(pos token.Pos, pkg *Package, name string, typ Type) *Var {
|
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.
|
// NewField returns a new variable representing a struct field.
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ func (check *Checker) collectObjects() {
|
|||||||
|
|
||||||
if imp.fake {
|
if imp.fake {
|
||||||
// match 1.17 cmd/compile (not prescribed by spec)
|
// match 1.17 cmd/compile (not prescribed by spec)
|
||||||
pkgName.used = true
|
check.usedPkgNames[pkgName] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// add import to file scope
|
// add import to file scope
|
||||||
@@ -710,7 +710,7 @@ func (check *Checker) unusedImports() {
|
|||||||
// (initialization), use the blank identifier as explicit package name."
|
// (initialization), use the blank identifier as explicit package name."
|
||||||
|
|
||||||
for _, obj := range check.imports {
|
for _, obj := range check.imports {
|
||||||
if !obj.used && obj.name != "_" {
|
if obj.name != "_" && !check.usedPkgNames[obj] {
|
||||||
check.errorUnusedPkg(obj)
|
check.errorUnusedPkg(obj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func TestSizeof(t *testing.T) {
|
|||||||
{term{}, 12, 24},
|
{term{}, 12, 24},
|
||||||
|
|
||||||
// Objects
|
// Objects
|
||||||
{PkgName{}, 48, 88},
|
{PkgName{}, 44, 80},
|
||||||
{Const{}, 48, 88},
|
{Const{}, 48, 88},
|
||||||
{TypeName{}, 40, 72},
|
{TypeName{}, 40, 72},
|
||||||
{Var{}, 48, 88},
|
{Var{}, 48, 88},
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func (check *Checker) usage(scope *Scope) {
|
|||||||
var unused []*Var
|
var unused []*Var
|
||||||
for name, elem := range scope.elems {
|
for name, elem := range scope.elems {
|
||||||
elem = resolve(name, elem)
|
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)
|
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.
|
// 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 {
|
if lhs != nil {
|
||||||
var used bool
|
var used bool
|
||||||
for _, v := range lhsVars {
|
for _, v := range lhsVars {
|
||||||
if v.used {
|
if check.usedVars[v] {
|
||||||
used = true
|
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 {
|
if !used {
|
||||||
check.softErrorf(lhs, UnusedVar, "%s declared and not used", lhs.Name)
|
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] {
|
if typ == nil || typ == Typ[Invalid] {
|
||||||
// typ == Typ[Invalid] can happen if allowVersion fails.
|
// typ == Typ[Invalid] can happen if allowVersion fails.
|
||||||
obj.typ = Typ[Invalid]
|
obj.typ = Typ[Invalid]
|
||||||
obj.used = true // don't complain about unused variable
|
check.usedVars[obj] = true // don't complain about unused variable
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *TypeName, wantType bo
|
|||||||
// avoid "declared but not used" errors
|
// avoid "declared but not used" errors
|
||||||
// (don't use Checker.use - we don't want to evaluate too much)
|
// (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 */ {
|
if v, _ := obj.(*Var); v != nil && v.pkg == check.pkg /* see Checker.use1 */ {
|
||||||
v.used = true
|
check.usedVars[v] = true
|
||||||
}
|
}
|
||||||
return
|
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,
|
// (This code is only needed for dot-imports. Without them,
|
||||||
// we only have to mark variables, see *Var case below).
|
// we only have to mark variables, see *Var case below).
|
||||||
if pkgName := check.dotImportMap[dotImportKey{scope, obj.Name()}]; pkgName != nil {
|
if pkgName := check.dotImportMap[dotImportKey{scope, obj.Name()}]; pkgName != nil {
|
||||||
pkgName.used = true
|
check.usedPkgNames[pkgName] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
switch obj := obj.(type) {
|
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
|
// from other packages to avoid potential race conditions with
|
||||||
// dot-imported variables.
|
// dot-imported variables.
|
||||||
if obj.pkg == check.pkg {
|
if obj.pkg == check.pkg {
|
||||||
obj.used = true
|
check.usedVars[obj] = true
|
||||||
}
|
}
|
||||||
check.addDeclDep(obj)
|
check.addDeclDep(obj)
|
||||||
if !isValid(typ) {
|
if !isValid(typ) {
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ var All = []Info{
|
|||||||
{Name: "tlsmlkem", Package: "crypto/tls", Changed: 24, Old: "0", Opaque: true},
|
{Name: "tlsmlkem", Package: "crypto/tls", Changed: 24, Old: "0", Opaque: true},
|
||||||
{Name: "tlsrsakex", Package: "crypto/tls", Changed: 22, Old: "1"},
|
{Name: "tlsrsakex", Package: "crypto/tls", Changed: 22, Old: "1"},
|
||||||
{Name: "tlsunsafeekm", 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: "winreadlinkvolume", Package: "os", Changed: 23, Old: "0"},
|
||||||
{Name: "winsymlink", Package: "os", Changed: 22, Old: "0"},
|
{Name: "winsymlink", Package: "os", Changed: 23, Old: "0"},
|
||||||
{Name: "x509keypairleaf", Package: "crypto/tls", Changed: 23, Old: "0"},
|
{Name: "x509keypairleaf", Package: "crypto/tls", Changed: 23, Old: "0"},
|
||||||
{Name: "x509negativeserial", Package: "crypto/x509", Changed: 23, Old: "1"},
|
{Name: "x509negativeserial", Package: "crypto/x509", Changed: 23, Old: "1"},
|
||||||
{Name: "x509rsacrt", Package: "crypto/x509", Changed: 24, Old: "0"},
|
{Name: "x509rsacrt", Package: "crypto/x509", Changed: 24, Old: "0"},
|
||||||
|
|||||||
@@ -504,3 +504,26 @@ func ParallelOn64Bit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Parallel()
|
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
|
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 {
|
if len(p) >= maxLineLength {
|
||||||
return nil, ErrLineTooLong
|
return nil, ErrLineTooLong
|
||||||
}
|
}
|
||||||
@@ -171,14 +184,14 @@ func readChunkLine(b *bufio.Reader) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func trimTrailingWhitespace(b []byte) []byte {
|
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]
|
b = b[:len(b)-1]
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func isASCIISpace(b byte) bool {
|
func isOWS(b byte) bool {
|
||||||
return b == ' ' || b == '\t' || b == '\n' || b == '\r'
|
return b == ' ' || b == '\t'
|
||||||
}
|
}
|
||||||
|
|
||||||
var semi = []byte(";")
|
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 {
|
type funcReader struct {
|
||||||
f func(iteration int) ([]byte, error)
|
f func(iteration int) ([]byte, error)
|
||||||
i int
|
i int
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"compress/zlib"
|
"compress/zlib"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -7303,3 +7304,120 @@ func testServerReadAfterHandlerAbort100Continue(t *testing.T, mode testMode) {
|
|||||||
readyc <- struct{}{} // server starts reading from the request body
|
readyc <- struct{}{} // server starts reading from the request body
|
||||||
readyc <- struct{}{} // server finishes 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
|
// adjustNextProtos adds or removes "http/1.1" and "h2" entries from
|
||||||
// a tls.Config.NextProtos list, according to the set of protocols in protos.
|
// a tls.Config.NextProtos list, according to the set of protocols in protos.
|
||||||
func adjustNextProtos(nextProtos []string, protos Protocols) []string {
|
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
|
var have Protocols
|
||||||
nextProtos = slices.DeleteFunc(nextProtos, func(s string) bool {
|
nextProtos = slices.DeleteFunc(nextProtos, func(s string) bool {
|
||||||
switch s {
|
switch s {
|
||||||
|
|||||||
@@ -355,7 +355,9 @@ func cgocallbackg(fn, frame unsafe.Pointer, ctxt uintptr) {
|
|||||||
gp.m.incgo = true
|
gp.m.incgo = true
|
||||||
unlockOSThread()
|
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
|
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) {
|
func TestCgoExternalThreadPanic(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
if runtime.GOOS == "plan9" {
|
if runtime.GOOS == "plan9" {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package runtime_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -59,3 +60,36 @@ func ExampleFrames() {
|
|||||||
// - more:true | runtime_test.ExampleFrames.func3
|
// - more:true | runtime_test.ExampleFrames.func3
|
||||||
// - more:true | runtime_test.ExampleFrames
|
// - 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
|
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,
|
// testCPUProfile runs f under the CPU profiler, checking for some conditions specified by need,
|
||||||
// as interpreted by matches, and returns the parsed profile.
|
// as interpreted by matches, and returns the parsed profile.
|
||||||
func testCPUProfile(t *testing.T, matches profileMatchFunc, f func(dur time.Duration)) *profile.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")
|
t.Skip("skipping on wasip1")
|
||||||
}
|
}
|
||||||
|
|
||||||
broken := cpuProfilingBroken()
|
broken := testenv.CPUProfilingBroken()
|
||||||
|
|
||||||
deadline, ok := t.Deadline()
|
deadline, ok := t.Deadline()
|
||||||
if broken || !ok {
|
if broken || !ok {
|
||||||
|
|||||||
@@ -419,14 +419,21 @@ func isAsyncSafePoint(gp *g, pc, sp, lr uintptr) (bool, uintptr) {
|
|||||||
name := u.srcFunc(uf).name()
|
name := u.srcFunc(uf).name()
|
||||||
if stringslite.HasPrefix(name, "runtime.") ||
|
if stringslite.HasPrefix(name, "runtime.") ||
|
||||||
stringslite.HasPrefix(name, "runtime/internal/") ||
|
stringslite.HasPrefix(name, "runtime/internal/") ||
|
||||||
|
stringslite.HasPrefix(name, "internal/runtime/") ||
|
||||||
stringslite.HasPrefix(name, "reflect.") {
|
stringslite.HasPrefix(name, "reflect.") {
|
||||||
// For now we never async preempt the runtime or
|
// For now we never async preempt the runtime or
|
||||||
// anything closely tied to the runtime. Known issues
|
// anything closely tied to the runtime. Known issues
|
||||||
// include: various points in the scheduler ("don't
|
// include: various points in the scheduler ("don't
|
||||||
// preempt between here and here"), much of the defer
|
// preempt between here and here"), much of the defer
|
||||||
// implementation (untyped info on stack), bulk write
|
// implementation (untyped info on stack), bulk write
|
||||||
// barriers (write barrier check),
|
// barriers (write barrier check), atomic functions in
|
||||||
// reflect.{makeFuncStub,methodValueCall}.
|
// 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
|
// TODO(austin): We should improve this, or opt things
|
||||||
// in incrementally.
|
// in incrementally.
|
||||||
|
|||||||
@@ -556,7 +556,7 @@ type m struct {
|
|||||||
printlock int8
|
printlock int8
|
||||||
incgo bool // m is executing a cgo call
|
incgo bool // m is executing a cgo call
|
||||||
isextra bool // m is an extra m
|
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
|
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)
|
freeWait atomic.Uint32 // Whether it is safe to free g0 and delete m (one of freeMRef, freeMStack, freeMWait)
|
||||||
needextram bool
|
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
|
netBytes uint64
|
||||||
// Extra metrics collected by ReportMetric.
|
// Extra metrics collected by ReportMetric.
|
||||||
extra map[string]float64
|
extra map[string]float64
|
||||||
// For Loop() to be executed in benchFunc.
|
|
||||||
// Loop() has its own control logic that skips the loop scaling.
|
// loop tracks the state of B.Loop
|
||||||
// See issue #61515.
|
loop struct {
|
||||||
loopN int
|
// 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
|
// StartTimer starts timing a test. This function is called automatically
|
||||||
@@ -130,6 +142,7 @@ func (b *B) StartTimer() {
|
|||||||
b.startBytes = memStats.TotalAlloc
|
b.startBytes = memStats.TotalAlloc
|
||||||
b.start = highPrecisionTimeNow()
|
b.start = highPrecisionTimeNow()
|
||||||
b.timerOn = true
|
b.timerOn = true
|
||||||
|
b.loop.i &^= loopPoisonTimer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +155,8 @@ func (b *B) StopTimer() {
|
|||||||
b.netAllocs += memStats.Mallocs - b.startAllocs
|
b.netAllocs += memStats.Mallocs - b.startAllocs
|
||||||
b.netBytes += memStats.TotalAlloc - b.startBytes
|
b.netBytes += memStats.TotalAlloc - b.startBytes
|
||||||
b.timerOn = false
|
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()
|
runtime.GC()
|
||||||
b.resetRaces()
|
b.resetRaces()
|
||||||
b.N = n
|
b.N = n
|
||||||
b.loopN = 0
|
b.loop.n = 0
|
||||||
|
b.loop.i = 0
|
||||||
|
b.loop.done = false
|
||||||
b.ctx = ctx
|
b.ctx = ctx
|
||||||
b.cancelCtx = cancelCtx
|
b.cancelCtx = cancelCtx
|
||||||
|
|
||||||
@@ -203,6 +220,10 @@ func (b *B) runN(n int) {
|
|||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
b.previousN = n
|
b.previousN = n
|
||||||
b.previousDuration = b.duration
|
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
|
// 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.
|
// 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.loop.n is non zero, it means b.Loop has already run.
|
||||||
if b.loopN == 0 {
|
if b.loop.n == 0 {
|
||||||
// Run the benchmark for at least the specified amount of time.
|
// Run the benchmark for at least the specified amount of time.
|
||||||
if b.benchTime.n > 0 {
|
if b.benchTime.n > 0 {
|
||||||
// We already ran a single iteration in run1.
|
// 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 {
|
func (b *B) stopOrScaleBLoop() bool {
|
||||||
timeElapsed := highPrecisionTimeSince(b.start)
|
t := b.Elapsed()
|
||||||
if timeElapsed >= b.benchTime.d {
|
if t >= b.benchTime.d {
|
||||||
// Stop the timer so we don't count cleanup time
|
// Stop the timer so we don't count cleanup time
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
|
// Commit iteration count
|
||||||
|
b.N = int(b.loop.n)
|
||||||
|
b.loop.done = true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// Loop scaling
|
// Loop scaling
|
||||||
goalns := b.benchTime.d.Nanoseconds()
|
goalns := b.benchTime.d.Nanoseconds()
|
||||||
prevIters := int64(b.N)
|
prevIters := int64(b.loop.n)
|
||||||
b.N = predictN(goalns, prevIters, timeElapsed.Nanoseconds(), prevIters)
|
b.loop.n = uint64(predictN(goalns, prevIters, t.Nanoseconds(), prevIters))
|
||||||
b.loopN++
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *B) loopSlowPath() bool {
|
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.
|
// If it's the first call to b.Loop() in the benchmark function.
|
||||||
// Allows more precise measurement of benchmark loop cost counts.
|
// Allows more precise measurement of benchmark loop cost counts.
|
||||||
// Also initialize b.N to 1 to kick start loop scaling.
|
// Also initialize target to 1 to kick start loop scaling.
|
||||||
b.N = 1
|
b.loop.n = 1
|
||||||
b.loopN = 1
|
// Within a b.Loop loop, we don't use b.N (to avoid confusion).
|
||||||
|
b.N = 0
|
||||||
|
b.loop.i++
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// Handles fixed iterations case
|
// Handles fixed iterations case
|
||||||
if b.benchTime.n > 0 {
|
if b.benchTime.n > 0 {
|
||||||
if b.N < b.benchTime.n {
|
if b.loop.n < uint64(b.benchTime.n) {
|
||||||
b.N = b.benchTime.n
|
b.loop.n = uint64(b.benchTime.n)
|
||||||
b.loopN++
|
b.loop.i++
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
|
// Commit iteration count
|
||||||
|
b.N = int(b.loop.n)
|
||||||
|
b.loop.done = true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// Handles fixed time case
|
// 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
|
// whereas b.N-based benchmarks must run the benchmark function (and any
|
||||||
// associated setup and cleanup) several times.
|
// associated setup and cleanup) several times.
|
||||||
func (b *B) Loop() bool {
|
func (b *B) Loop() bool {
|
||||||
if b.loopN != 0 && b.loopN < b.N {
|
// This is written such that the fast path is as fast as possible and can be
|
||||||
b.loopN++
|
// 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 true
|
||||||
}
|
}
|
||||||
return b.loopSlowPath()
|
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.
|
// BenchmarkResult contains the results of a benchmark run.
|
||||||
type BenchmarkResult struct {
|
type BenchmarkResult struct {
|
||||||
N int // The number of iterations.
|
N int // The number of iterations.
|
||||||
|
|||||||
@@ -4,13 +4,22 @@
|
|||||||
|
|
||||||
package testing
|
package testing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// See also TestBenchmarkBLoop* in other files.
|
||||||
|
|
||||||
func TestBenchmarkBLoop(t *T) {
|
func TestBenchmarkBLoop(t *T) {
|
||||||
var initialStart highPrecisionTime
|
var initialStart highPrecisionTime
|
||||||
var firstStart highPrecisionTime
|
var firstStart highPrecisionTime
|
||||||
var lastStart highPrecisionTime
|
var scaledStart highPrecisionTime
|
||||||
var runningEnd bool
|
var runningEnd bool
|
||||||
runs := 0
|
runs := 0
|
||||||
iters := 0
|
iters := 0
|
||||||
|
firstBN := 0
|
||||||
|
restBN := 0
|
||||||
finalBN := 0
|
finalBN := 0
|
||||||
bRet := Benchmark(func(b *B) {
|
bRet := Benchmark(func(b *B) {
|
||||||
initialStart = b.start
|
initialStart = b.start
|
||||||
@@ -18,8 +27,13 @@ func TestBenchmarkBLoop(t *T) {
|
|||||||
for b.Loop() {
|
for b.Loop() {
|
||||||
if iters == 0 {
|
if iters == 0 {
|
||||||
firstStart = b.start
|
firstStart = b.start
|
||||||
|
firstBN = b.N
|
||||||
|
} else {
|
||||||
|
restBN = max(restBN, b.N)
|
||||||
|
}
|
||||||
|
if iters == 1 {
|
||||||
|
scaledStart = b.start
|
||||||
}
|
}
|
||||||
lastStart = b.start
|
|
||||||
iters++
|
iters++
|
||||||
}
|
}
|
||||||
finalBN = b.N
|
finalBN = b.N
|
||||||
@@ -37,6 +51,13 @@ func TestBenchmarkBLoop(t *T) {
|
|||||||
if finalBN != iters || bRet.N != iters {
|
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)
|
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.
|
// Make sure the benchmark ran for an appropriate amount of time.
|
||||||
if bRet.T < benchTime.d {
|
if bRet.T < benchTime.d {
|
||||||
t.Fatalf("benchmark ran for %s, want >= %s", 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 {
|
if firstStart == initialStart {
|
||||||
t.Errorf("b.Loop did not reset the timer")
|
t.Errorf("b.Loop did not reset the timer")
|
||||||
}
|
}
|
||||||
if lastStart != firstStart {
|
if scaledStart != firstStart {
|
||||||
t.Errorf("timer was reset during iteration")
|
t.Errorf("b.Loop stops and restarts the timer during iteration")
|
||||||
}
|
}
|
||||||
// Verify that it stopped the timer after the last loop.
|
// Verify that it stopped the timer after the last loop.
|
||||||
if runningEnd {
|
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.
|
// Copyright 2024 The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// 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
|
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++ {
|
for i := 0; i < b.N; i++ {
|
||||||
caninline(1) // ERROR "inlining call to caninline"
|
caninline(1) // ERROR "inlining call to caninline"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user