diff --git a/api/go1.21.txt b/api/go1.21.txt index 6435d10914..c8ca3df2e6 100644 --- a/api/go1.21.txt +++ b/api/go1.21.txt @@ -174,6 +174,7 @@ pkg go/build, type Package struct, Directives []Directive #56986 pkg go/build, type Package struct, TestDirectives []Directive #56986 pkg go/build, type Package struct, XTestDirectives []Directive #56986 pkg go/token, method (*File) Lines() []int #57708 +pkg go/types, method (*Package) GoVersion() string #61175 pkg html/template, const ErrJSTemplate = 12 #59584 pkg html/template, const ErrJSTemplate ErrorCode #59584 pkg io/fs, func FormatDirEntry(DirEntry) string #54451 diff --git a/src/cmd/asm/internal/lex/slice.go b/src/cmd/asm/internal/lex/slice.go index 8ee0c7035f..61b15dd963 100644 --- a/src/cmd/asm/internal/lex/slice.go +++ b/src/cmd/asm/internal/lex/slice.go @@ -65,7 +65,7 @@ func (s *Slice) Col() int { // #define A #define B(x) x // and // #define A #define B (x) x - // The first has definition of B has an argument, the second doesn't. Because we let + // The first definition of B has an argument, the second doesn't. Because we let // text/scanner strip the blanks for us, this is extremely rare, hard to fix, and not worth it. return s.pos } diff --git a/src/cmd/compile/internal/ssa/_gen/RISCV64.rules b/src/cmd/compile/internal/ssa/_gen/RISCV64.rules index eb1f10de96..9a6fcebdc5 100644 --- a/src/cmd/compile/internal/ssa/_gen/RISCV64.rules +++ b/src/cmd/compile/internal/ssa/_gen/RISCV64.rules @@ -836,11 +836,11 @@ // // Key: // -// [+ -](x * y) [+ -] z. -// _ N A S -// D U -// D B +// [+ -](x * y [+ -] z). +// _ N A S +// D U +// D B // // Note: multiplication commutativity handled by rule generator. -(F(MADD|NMADD|MSUB|NMSUB)D neg:(FNEGD x) y z) && neg.Uses == 1 => (F(NMADD|MADD|NMSUB|MSUB)D x y z) +(F(MADD|NMADD|MSUB|NMSUB)D neg:(FNEGD x) y z) && neg.Uses == 1 => (F(NMSUB|MSUB|NMADD|MADD)D x y z) (F(MADD|NMADD|MSUB|NMSUB)D x y neg:(FNEGD z)) && neg.Uses == 1 => (F(MSUB|NMSUB|MADD|NMADD)D x y z) diff --git a/src/cmd/compile/internal/ssa/rewriteRISCV64.go b/src/cmd/compile/internal/ssa/rewriteRISCV64.go index f1debe0c21..ffbeb1df47 100644 --- a/src/cmd/compile/internal/ssa/rewriteRISCV64.go +++ b/src/cmd/compile/internal/ssa/rewriteRISCV64.go @@ -3322,7 +3322,7 @@ func rewriteValueRISCV64_OpRISCV64FMADDD(v *Value) bool { v_0 := v.Args[0] // match: (FMADDD neg:(FNEGD x) y z) // cond: neg.Uses == 1 - // result: (FNMADDD x y z) + // result: (FNMSUBD x y z) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { neg := v_0 @@ -3335,7 +3335,7 @@ func rewriteValueRISCV64_OpRISCV64FMADDD(v *Value) bool { if !(neg.Uses == 1) { continue } - v.reset(OpRISCV64FNMADDD) + v.reset(OpRISCV64FNMSUBD) v.AddArg3(x, y, z) return true } @@ -3367,7 +3367,7 @@ func rewriteValueRISCV64_OpRISCV64FMSUBD(v *Value) bool { v_0 := v.Args[0] // match: (FMSUBD neg:(FNEGD x) y z) // cond: neg.Uses == 1 - // result: (FNMSUBD x y z) + // result: (FNMADDD x y z) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { neg := v_0 @@ -3380,7 +3380,7 @@ func rewriteValueRISCV64_OpRISCV64FMSUBD(v *Value) bool { if !(neg.Uses == 1) { continue } - v.reset(OpRISCV64FNMSUBD) + v.reset(OpRISCV64FNMADDD) v.AddArg3(x, y, z) return true } @@ -3412,7 +3412,7 @@ func rewriteValueRISCV64_OpRISCV64FNMADDD(v *Value) bool { v_0 := v.Args[0] // match: (FNMADDD neg:(FNEGD x) y z) // cond: neg.Uses == 1 - // result: (FMADDD x y z) + // result: (FMSUBD x y z) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { neg := v_0 @@ -3425,7 +3425,7 @@ func rewriteValueRISCV64_OpRISCV64FNMADDD(v *Value) bool { if !(neg.Uses == 1) { continue } - v.reset(OpRISCV64FMADDD) + v.reset(OpRISCV64FMSUBD) v.AddArg3(x, y, z) return true } @@ -3457,7 +3457,7 @@ func rewriteValueRISCV64_OpRISCV64FNMSUBD(v *Value) bool { v_0 := v.Args[0] // match: (FNMSUBD neg:(FNEGD x) y z) // cond: neg.Uses == 1 - // result: (FMSUBD x y z) + // result: (FMADDD x y z) for { for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { neg := v_0 @@ -3470,7 +3470,7 @@ func rewriteValueRISCV64_OpRISCV64FNMSUBD(v *Value) bool { if !(neg.Uses == 1) { continue } - v.reset(OpRISCV64FMSUBD) + v.reset(OpRISCV64FMADDD) v.AddArg3(x, y, z) return true } diff --git a/src/cmd/compile/internal/types2/check.go b/src/cmd/compile/internal/types2/check.go index b2a9eb0dbc..0a2a49062b 100644 --- a/src/cmd/compile/internal/types2/check.go +++ b/src/cmd/compile/internal/types2/check.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "go/constant" + "internal/goversion" . "internal/types/errors" ) @@ -231,19 +232,19 @@ func NewChecker(conf *Config, pkg *Package, info *Info) *Checker { info = new(Info) } - version, err := parseGoVersion(conf.GoVersion) - if err != nil { - panic(fmt.Sprintf("invalid Go version %q (%v)", conf.GoVersion, err)) - } + // Note: clients may call NewChecker with the Unsafe package, which is + // globally shared and must not be mutated. Therefore NewChecker must not + // mutate *pkg. + // + // (previously, pkg.goVersion was mutated here: go.dev/issue/61212) return &Checker{ - conf: conf, - ctxt: conf.Context, - pkg: pkg, - Info: info, - version: version, - objMap: make(map[Object]*declInfo), - impMap: make(map[importKey]*Package), + conf: conf, + ctxt: conf.Context, + pkg: pkg, + Info: info, + objMap: make(map[Object]*declInfo), + impMap: make(map[importKey]*Package), } } @@ -333,6 +334,20 @@ func (check *Checker) Files(files []*syntax.File) error { return check.checkFile var errBadCgo = errors.New("cannot use FakeImportC and go115UsesCgo together") func (check *Checker) checkFiles(files []*syntax.File) (err error) { + if check.pkg == Unsafe { + // Defensive handling for Unsafe, which cannot be type checked, and must + // not be mutated. See https://go.dev/issue/61212 for an example of where + // Unsafe is passed to NewChecker. + return nil + } + + check.version, err = parseGoVersion(check.conf.GoVersion) + if err != nil { + return err + } + if check.version.after(version{1, goversion.Version}) { + return fmt.Errorf("package requires newer Go version %v", check.version) + } if check.conf.FakeImportC && check.conf.go115UsesCgo { return errBadCgo } @@ -377,6 +392,7 @@ func (check *Checker) checkFiles(files []*syntax.File) (err error) { check.monomorph() } + check.pkg.goVersion = check.conf.GoVersion check.pkg.complete = true // no longer needed - release memory diff --git a/src/cmd/compile/internal/types2/issues_test.go b/src/cmd/compile/internal/types2/issues_test.go index 8bd42a5271..5e0ae213dc 100644 --- a/src/cmd/compile/internal/types2/issues_test.go +++ b/src/cmd/compile/internal/types2/issues_test.go @@ -497,14 +497,14 @@ func TestIssue43088(t *testing.T) { // _ T2 // } // } - n1 := NewTypeName(syntax.Pos{}, nil, "T1", nil) + n1 := NewTypeName(nopos, nil, "T1", nil) T1 := NewNamed(n1, nil, nil) - n2 := NewTypeName(syntax.Pos{}, nil, "T2", nil) + n2 := NewTypeName(nopos, nil, "T2", nil) T2 := NewNamed(n2, nil, nil) - s1 := NewStruct([]*Var{NewField(syntax.Pos{}, nil, "_", T2, false)}, nil) + s1 := NewStruct([]*Var{NewField(nopos, nil, "_", T2, false)}, nil) T1.SetUnderlying(s1) - s2 := NewStruct([]*Var{NewField(syntax.Pos{}, nil, "_", T2, false)}, nil) - s3 := NewStruct([]*Var{NewField(syntax.Pos{}, nil, "_", s2, false)}, nil) + s2 := NewStruct([]*Var{NewField(nopos, nil, "_", T2, false)}, nil) + s3 := NewStruct([]*Var{NewField(nopos, nil, "_", s2, false)}, nil) T2.SetUnderlying(s3) // These calls must terminate (no endless recursion). @@ -535,38 +535,69 @@ func TestIssue44515(t *testing.T) { } func TestIssue43124(t *testing.T) { - testenv.MustHaveGoBuild(t) + // TODO(rFindley) move this to testdata by enhancing support for importing. + + testenv.MustHaveGoBuild(t) // The go command is needed for the importer to determine the locations of stdlib .a files. // All involved packages have the same name (template). Error messages should // disambiguate between text/template and html/template by printing the full // path. const ( asrc = `package a; import "text/template"; func F(template.Template) {}; func G(int) {}` - bsrc = `package b; import ("a"; "html/template"); func _() { a.F(template.Template{}) }` - csrc = `package c; import ("a"; "html/template"); func _() { a.G(template.Template{}) }` + bsrc = ` +package b + +import ( + "a" + "html/template" +) + +func _() { + // Packages should be fully qualified when there is ambiguity within the + // error string itself. + a.F(template /* ERRORx "cannot use.*html/template.* as .*text/template" */ .Template{}) +} +` + csrc = ` +package c + +import ( + "a" + "fmt" + "html/template" +) + +// go.dev/issue/46905: make sure template is not the first package qualified. +var _ fmt.Stringer = 1 // ERRORx "cannot use 1.*as fmt\\.Stringer" + +// Packages should be fully qualified when there is ambiguity in reachable +// packages. In this case both a (and for that matter html/template) import +// text/template. +func _() { a.G(template /* ERRORx "cannot use .*html/template.*Template" */ .Template{}) } +` + + tsrc = ` +package template + +import "text/template" + +type T int + +// Verify that the current package name also causes disambiguation. +var _ T = template /* ERRORx "cannot use.*text/template.* as T value" */.Template{} +` ) a := mustTypecheck(asrc, nil, nil) - conf := Config{Importer: importHelper{pkg: a, fallback: defaultImporter()}} + imp := importHelper{pkg: a, fallback: defaultImporter()} - // Packages should be fully qualified when there is ambiguity within the - // error string itself. - _, err := typecheck(bsrc, &conf, nil) - if err == nil { - t.Fatal("package b had no errors") - } - if !strings.Contains(err.Error(), "text/template") || !strings.Contains(err.Error(), "html/template") { - t.Errorf("type checking error for b does not disambiguate package template: %q", err) + withImporter := func(cfg *Config) { + cfg.Importer = imp } - // ...and also when there is any ambiguity in reachable packages. - _, err = typecheck(csrc, &conf, nil) - if err == nil { - t.Fatal("package c had no errors") - } - if !strings.Contains(err.Error(), "html/template") { - t.Errorf("type checking error for c does not disambiguate package template: %q", err) - } + testFiles(t, []string{"b.go"}, [][]byte{[]byte(bsrc)}, 0, false, withImporter) + testFiles(t, []string{"c.go"}, [][]byte{[]byte(csrc)}, 0, false, withImporter) + testFiles(t, []string{"t.go"}, [][]byte{[]byte(tsrc)}, 0, false, withImporter) } func TestIssue50646(t *testing.T) { diff --git a/src/cmd/compile/internal/types2/package.go b/src/cmd/compile/internal/types2/package.go index 61670f6718..e08099d81f 100644 --- a/src/cmd/compile/internal/types2/package.go +++ b/src/cmd/compile/internal/types2/package.go @@ -10,13 +10,14 @@ import ( // A Package describes a Go package. type Package struct { - path string - name string - scope *Scope - imports []*Package - complete bool - fake bool // scope lookup errors are silently dropped if package is fake (internal use only) - cgo bool // uses of this package will be rewritten into uses of declarations from _cgo_gotypes.go + path string + name string + scope *Scope + imports []*Package + complete bool + fake bool // scope lookup errors are silently dropped if package is fake (internal use only) + cgo bool // uses of this package will be rewritten into uses of declarations from _cgo_gotypes.go + goVersion string // minimum Go version required for package (by Config.GoVersion, typically from go.mod) } // NewPackage returns a new Package for the given package path and name. @@ -35,6 +36,12 @@ func (pkg *Package) Name() string { return pkg.name } // SetName sets the package name. func (pkg *Package) SetName(name string) { pkg.name = name } +// GoVersion returns the minimum Go version required by this package. +// If the minimum version is unknown, GoVersion returns the empty string. +// Individual source files may specify a different minimum Go version, +// as reported in the [go/ast.File.GoVersion] field. +func (pkg *Package) GoVersion() string { return pkg.goVersion } + // Scope returns the (complete or incomplete) package scope // holding the objects declared at package level (TypeNames, // Consts, Vars, and Funcs). diff --git a/src/cmd/compile/internal/types2/sizeof_test.go b/src/cmd/compile/internal/types2/sizeof_test.go index af82b3fa7a..740dbc9276 100644 --- a/src/cmd/compile/internal/types2/sizeof_test.go +++ b/src/cmd/compile/internal/types2/sizeof_test.go @@ -47,7 +47,7 @@ func TestSizeof(t *testing.T) { // Misc {Scope{}, 60, 104}, - {Package{}, 36, 72}, + {Package{}, 44, 88}, {_TypeSet{}, 28, 56}, } diff --git a/src/cmd/compile/internal/types2/version.go b/src/cmd/compile/internal/types2/version.go index 7d01b829a9..e525f16470 100644 --- a/src/cmd/compile/internal/types2/version.go +++ b/src/cmd/compile/internal/types2/version.go @@ -6,7 +6,6 @@ package types2 import ( "cmd/compile/internal/syntax" - "errors" "fmt" "strings" ) @@ -44,23 +43,24 @@ var ( go1_21 = version{1, 21} ) -var errVersionSyntax = errors.New("invalid Go version syntax") - // parseGoVersion parses a Go version string (such as "go1.12") // and returns the version, or an error. If s is the empty // string, the version is 0.0. func parseGoVersion(s string) (v version, err error) { + bad := func() (version, error) { + return version{}, fmt.Errorf("invalid Go version syntax %q", s) + } if s == "" { return } if !strings.HasPrefix(s, "go") { - return version{}, errVersionSyntax + return bad() } s = s[len("go"):] i := 0 for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { if i >= 10 || i == 0 && s[i] == '0' { - return version{}, errVersionSyntax + return bad() } v.major = 10*v.major + int(s[i]) - '0' } @@ -68,7 +68,7 @@ func parseGoVersion(s string) (v version, err error) { return } if i == 0 || s[i] != '.' { - return version{}, errVersionSyntax + return bad() } s = s[i+1:] if s == "0" { @@ -81,14 +81,15 @@ func parseGoVersion(s string) (v version, err error) { i = 0 for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { if i >= 10 || i == 0 && s[i] == '0' { - return version{}, errVersionSyntax + return bad() } v.minor = 10*v.minor + int(s[i]) - '0' } - if i > 0 && i == len(s) { - return - } - return version{}, errVersionSyntax + // Accept any suffix after the minor number. + // We are only looking for the language version (major.minor) + // but want to accept any valid Go version, like go1.21.0 + // and go1.21rc2. + return } // langCompat reports an error if the representation of a numeric diff --git a/src/cmd/compile/internal/types2/version_test.go b/src/cmd/compile/internal/types2/version_test.go new file mode 100644 index 0000000000..651758e1b0 --- /dev/null +++ b/src/cmd/compile/internal/types2/version_test.go @@ -0,0 +1,24 @@ +// Copyright 2023 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 types2 + +import "testing" + +var parseGoVersionTests = []struct { + in string + out version +}{ + {"go1.21", version{1, 21}}, + {"go1.21.0", version{1, 21}}, + {"go1.21rc2", version{1, 21}}, +} + +func TestParseGoVersion(t *testing.T) { + for _, tt := range parseGoVersionTests { + if out, err := parseGoVersion(tt.in); out != tt.out || err != nil { + t.Errorf("parseGoVersion(%q) = %v, %v, want %v, nil", tt.in, out, err, tt.out) + } + } +} diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go index d38a051b2b..13d2a78a97 100644 --- a/src/cmd/go/internal/work/exec.go +++ b/src/cmd/go/internal/work/exec.go @@ -1115,6 +1115,7 @@ type vetConfig struct { PackageVetx map[string]string // map package path to vetx data from earlier vet run VetxOnly bool // only compute vetx data; don't report detected problems VetxOutput string // write vetx data to this output file + GoVersion string // Go version for package SucceedOnTypecheckFailure bool // awful hack; see #18395 and below } @@ -1149,6 +1150,13 @@ func buildVetConfig(a *Action, srcfiles []string) { PackageFile: make(map[string]string), Standard: make(map[string]bool), } + if a.Package.Module != nil { + v := a.Package.Module.GoVersion + if v == "" { + v = gover.DefaultGoModVersion + } + vcfg.GoVersion = "go" + v + } a.vetCfg = vcfg for i, raw := range a.Package.Internal.RawImports { final := a.Package.Imports[i] diff --git a/src/cmd/go/internal/work/gc.go b/src/cmd/go/internal/work/gc.go index 6043ad5353..26b4e0f490 100644 --- a/src/cmd/go/internal/work/gc.go +++ b/src/cmd/go/internal/work/gc.go @@ -85,19 +85,7 @@ func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg if p.Module != nil { v := p.Module.GoVersion if v == "" { - // We started adding a 'go' directive to the go.mod file unconditionally - // as of Go 1.12, so any module that still lacks such a directive must - // either have been authored before then, or have a hand-edited go.mod - // file that hasn't been updated by cmd/go since that edit. - // - // Unfortunately, through at least Go 1.16 we didn't add versions to - // vendor/modules.txt. So this could also be a vendored 1.16 dependency. - // - // Fortunately, there were no breaking changes to the language between Go - // 1.11 and 1.16, so if we assume Go 1.16 semantics we will not introduce - // any spurious errors — we will only mask errors, and not particularly - // important ones at that. - v = "1.16" + v = gover.DefaultGoModVersion } if allowedVersion(v) { defaultGcFlags = append(defaultGcFlags, "-lang=go"+gover.Lang(v)) diff --git a/src/database/sql/sql.go b/src/database/sql/sql.go index a77d63dc5e..836fe83e2e 100644 --- a/src/database/sql/sql.go +++ b/src/database/sql/sql.go @@ -2944,15 +2944,17 @@ func (rs *Rows) initContextClose(ctx, txctx context.Context) { if bypassRowsAwaitDone { return } - ctx, rs.cancel = context.WithCancel(ctx) - go rs.awaitDone(ctx, txctx) + closectx, cancel := context.WithCancel(ctx) + rs.cancel = cancel + go rs.awaitDone(ctx, txctx, closectx) } -// awaitDone blocks until either ctx or txctx is canceled. The ctx is provided -// from the query context and is canceled when the query Rows is closed. +// awaitDone blocks until ctx, txctx, or closectx is canceled. +// The ctx is provided from the query context. // If the query was issued in a transaction, the transaction's context -// is also provided in txctx to ensure Rows is closed if the Tx is closed. -func (rs *Rows) awaitDone(ctx, txctx context.Context) { +// is also provided in txctx, to ensure Rows is closed if the Tx is closed. +// The closectx is closed by an explicit call to rs.Close. +func (rs *Rows) awaitDone(ctx, txctx, closectx context.Context) { var txctxDone <-chan struct{} if txctx != nil { txctxDone = txctx.Done() @@ -2964,6 +2966,9 @@ func (rs *Rows) awaitDone(ctx, txctx context.Context) { case <-txctxDone: err := txctx.Err() rs.contextDone.Store(&err) + case <-closectx.Done(): + // rs.cancel was called via Close(); don't store this into contextDone + // to ensure Err() is unaffected. } rs.close(ctx.Err()) } diff --git a/src/database/sql/sql_test.go b/src/database/sql/sql_test.go index 718056c351..e6a5cd912a 100644 --- a/src/database/sql/sql_test.go +++ b/src/database/sql/sql_test.go @@ -4493,6 +4493,31 @@ func TestContextCancelBetweenNextAndErr(t *testing.T) { } } +func TestNilErrorAfterClose(t *testing.T) { + db := newTestDB(t, "people") + defer closeDB(t, db) + + // This WithCancel is important; Rows contains an optimization to avoid + // spawning a goroutine when the query/transaction context cannot be + // canceled, but this test tests a bug which is caused by said goroutine. + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + r, err := db.QueryContext(ctx, "SELECT|people|name|") + if err != nil { + t.Fatal(err) + } + + if err := r.Close(); err != nil { + t.Fatal(err) + } + + time.Sleep(10 * time.Millisecond) // increase odds of seeing failure + if err := r.Err(); err != nil { + t.Fatal(err) + } +} + // badConn implements a bad driver.Conn, for TestBadDriver. // The Exec method panics. type badConn struct{} diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index be8ac30f9d..2f335068b8 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -286,7 +286,7 @@ var depsRules = ` math/big, go/token < go/constant; - container/heap, go/constant, go/parser, internal/types/errors + container/heap, go/constant, go/parser, internal/goversion, internal/types/errors < go/types; # The vast majority of standard library packages should not be resorting to regexp. diff --git a/src/go/types/check.go b/src/go/types/check.go index 5381b5db68..3b0f5e4fdf 100644 --- a/src/go/types/check.go +++ b/src/go/types/check.go @@ -12,6 +12,7 @@ import ( "go/ast" "go/constant" "go/token" + "internal/goversion" . "internal/types/errors" ) @@ -233,20 +234,20 @@ func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Ch info = new(Info) } - version, err := parseGoVersion(conf.GoVersion) - if err != nil { - panic(fmt.Sprintf("invalid Go version %q (%v)", conf.GoVersion, err)) - } + // Note: clients may call NewChecker with the Unsafe package, which is + // globally shared and must not be mutated. Therefore NewChecker must not + // mutate *pkg. + // + // (previously, pkg.goVersion was mutated here: go.dev/issue/61212) return &Checker{ - conf: conf, - ctxt: conf.Context, - fset: fset, - pkg: pkg, - Info: info, - version: version, - objMap: make(map[Object]*declInfo), - impMap: make(map[importKey]*Package), + conf: conf, + ctxt: conf.Context, + fset: fset, + pkg: pkg, + Info: info, + objMap: make(map[Object]*declInfo), + impMap: make(map[importKey]*Package), } } @@ -342,6 +343,20 @@ func (check *Checker) Files(files []*ast.File) error { return check.checkFiles(f var errBadCgo = errors.New("cannot use FakeImportC and go115UsesCgo together") func (check *Checker) checkFiles(files []*ast.File) (err error) { + if check.pkg == Unsafe { + // Defensive handling for Unsafe, which cannot be type checked, and must + // not be mutated. See https://go.dev/issue/61212 for an example of where + // Unsafe is passed to NewChecker. + return nil + } + + check.version, err = parseGoVersion(check.conf.GoVersion) + if err != nil { + return err + } + if check.version.after(version{1, goversion.Version}) { + return fmt.Errorf("package requires newer Go version %v", check.version) + } if check.conf.FakeImportC && check.conf.go115UsesCgo { return errBadCgo } @@ -386,6 +401,7 @@ func (check *Checker) checkFiles(files []*ast.File) (err error) { check.monomorph() } + check.pkg.goVersion = check.conf.GoVersion check.pkg.complete = true // no longer needed - release memory diff --git a/src/go/types/generate_test.go b/src/go/types/generate_test.go index 05a848be31..75fda025ee 100644 --- a/src/go/types/generate_test.go +++ b/src/go/types/generate_test.go @@ -141,6 +141,7 @@ var filemap = map[string]action{ "universe.go": fixGlobalTypVarDecl, "util_test.go": fixTokenPos, "validtype.go": nil, + "version_test.go": nil, } // TODO(gri) We should be able to make these rewriters more configurable/composable. diff --git a/src/go/types/package.go b/src/go/types/package.go index 7aa62fb7a3..0f52d5f489 100644 --- a/src/go/types/package.go +++ b/src/go/types/package.go @@ -12,13 +12,14 @@ import ( // A Package describes a Go package. type Package struct { - path string - name string - scope *Scope - imports []*Package - complete bool - fake bool // scope lookup errors are silently dropped if package is fake (internal use only) - cgo bool // uses of this package will be rewritten into uses of declarations from _cgo_gotypes.go + path string + name string + scope *Scope + imports []*Package + complete bool + fake bool // scope lookup errors are silently dropped if package is fake (internal use only) + cgo bool // uses of this package will be rewritten into uses of declarations from _cgo_gotypes.go + goVersion string // minimum Go version required for package (by Config.GoVersion, typically from go.mod) } // NewPackage returns a new Package for the given package path and name. @@ -37,6 +38,12 @@ func (pkg *Package) Name() string { return pkg.name } // SetName sets the package name. func (pkg *Package) SetName(name string) { pkg.name = name } +// GoVersion returns the minimum Go version required by this package. +// If the minimum version is unknown, GoVersion returns the empty string. +// Individual source files may specify a different minimum Go version, +// as reported in the [go/ast.File.GoVersion] field. +func (pkg *Package) GoVersion() string { return pkg.goVersion } + // Scope returns the (complete or incomplete) package scope // holding the objects declared at package level (TypeNames, // Consts, Vars, and Funcs). diff --git a/src/go/types/sizeof_test.go b/src/go/types/sizeof_test.go index f17a1781f5..9e5b5f8b20 100644 --- a/src/go/types/sizeof_test.go +++ b/src/go/types/sizeof_test.go @@ -46,7 +46,7 @@ func TestSizeof(t *testing.T) { // Misc {Scope{}, 44, 88}, - {Package{}, 36, 72}, + {Package{}, 44, 88}, {_TypeSet{}, 28, 56}, } for _, test := range tests { diff --git a/src/go/types/version.go b/src/go/types/version.go index 07a42a79ee..108d9b34a0 100644 --- a/src/go/types/version.go +++ b/src/go/types/version.go @@ -5,7 +5,6 @@ package types import ( - "errors" "fmt" "go/ast" "go/token" @@ -45,23 +44,24 @@ var ( go1_21 = version{1, 21} ) -var errVersionSyntax = errors.New("invalid Go version syntax") - // parseGoVersion parses a Go version string (such as "go1.12") // and returns the version, or an error. If s is the empty // string, the version is 0.0. func parseGoVersion(s string) (v version, err error) { + bad := func() (version, error) { + return version{}, fmt.Errorf("invalid Go version syntax %q", s) + } if s == "" { return } if !strings.HasPrefix(s, "go") { - return version{}, errVersionSyntax + return bad() } s = s[len("go"):] i := 0 for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { if i >= 10 || i == 0 && s[i] == '0' { - return version{}, errVersionSyntax + return bad() } v.major = 10*v.major + int(s[i]) - '0' } @@ -69,7 +69,7 @@ func parseGoVersion(s string) (v version, err error) { return } if i == 0 || s[i] != '.' { - return version{}, errVersionSyntax + return bad() } s = s[i+1:] if s == "0" { @@ -82,14 +82,15 @@ func parseGoVersion(s string) (v version, err error) { i = 0 for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { if i >= 10 || i == 0 && s[i] == '0' { - return version{}, errVersionSyntax + return bad() } v.minor = 10*v.minor + int(s[i]) - '0' } - if i > 0 && i == len(s) { - return - } - return version{}, errVersionSyntax + // Accept any suffix after the minor number. + // We are only looking for the language version (major.minor) + // but want to accept any valid Go version, like go1.21.0 + // and go1.21rc2. + return } // langCompat reports an error if the representation of a numeric diff --git a/src/go/types/version_test.go b/src/go/types/version_test.go new file mode 100644 index 0000000000..d25f7f5e67 --- /dev/null +++ b/src/go/types/version_test.go @@ -0,0 +1,26 @@ +// Code generated by "go test -run=Generate -write=all"; DO NOT EDIT. + +// Copyright 2023 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 types + +import "testing" + +var parseGoVersionTests = []struct { + in string + out version +}{ + {"go1.21", version{1, 21}}, + {"go1.21.0", version{1, 21}}, + {"go1.21rc2", version{1, 21}}, +} + +func TestParseGoVersion(t *testing.T) { + for _, tt := range parseGoVersionTests { + if out, err := parseGoVersion(tt.in); out != tt.out || err != nil { + t.Errorf("parseGoVersion(%q) = %v, %v, want %v, nil", tt.in, out, err, tt.out) + } + } +} diff --git a/src/log/slog/handler.go b/src/log/slog/handler.go index dc4c2d92bd..e479ca8a4c 100644 --- a/src/log/slog/handler.go +++ b/src/log/slog/handler.go @@ -221,6 +221,11 @@ func (h *commonHandler) enabled(l Level) bool { } func (h *commonHandler) withAttrs(as []Attr) *commonHandler { + // We are going to ignore empty groups, so if the entire slice consists of + // them, there is nothing to do. + if countEmptyGroups(as) == len(as) { + return h + } h2 := h.clone() // Pre-format the attributes as an optimization. state := h2.newHandleState((*buffer.Buffer)(&h2.preformattedAttrs), false, "") @@ -308,15 +313,20 @@ func (s *handleState) appendNonBuiltIns(r Record) { } // Attrs in Record -- unlike the built-in ones, they are in groups started // from WithGroup. - s.prefix.WriteString(s.h.groupPrefix) - s.openGroups() - r.Attrs(func(a Attr) bool { - s.appendAttr(a) - return true - }) + // If the record has no Attrs, don't output any groups. + nOpenGroups := s.h.nOpenGroups + if r.NumAttrs() > 0 { + s.prefix.WriteString(s.h.groupPrefix) + s.openGroups() + nOpenGroups = len(s.h.groups) + r.Attrs(func(a Attr) bool { + s.appendAttr(a) + return true + }) + } if s.h.json { // Close all open groups. - for range s.h.groups { + for range s.h.groups[:nOpenGroups] { s.buf.WriteByte('}') } // Close the top-level object. diff --git a/src/log/slog/handler_test.go b/src/log/slog/handler_test.go index 741e86a826..f43d841483 100644 --- a/src/log/slog/handler_test.go +++ b/src/log/slog/handler_test.go @@ -214,6 +214,28 @@ func TestJSONAndTextHandlers(t *testing.T) { wantText: "msg=message h.a=1", wantJSON: `{"msg":"message","h":{"a":1}}`, }, + { + name: "nested empty group", + replace: removeKeys(TimeKey, LevelKey), + attrs: []Attr{ + Group("g", + Group("h", + Group("i"), Group("j"))), + }, + wantText: `msg=message`, + wantJSON: `{"msg":"message"}`, + }, + { + name: "nested non-empty group", + replace: removeKeys(TimeKey, LevelKey), + attrs: []Attr{ + Group("g", + Group("h", + Group("i"), Group("j", Int("a", 1)))), + }, + wantText: `msg=message g.h.j.a=1`, + wantJSON: `{"msg":"message","g":{"h":{"j":{"a":1}}}}`, + }, { name: "escapes", replace: removeKeys(TimeKey, LevelKey), @@ -281,6 +303,34 @@ func TestJSONAndTextHandlers(t *testing.T) { wantText: "msg=message p1=1 s1.s2.a=one s1.s2.b=2", wantJSON: `{"msg":"message","p1":1,"s1":{"s2":{"a":"one","b":2}}}`, }, + { + name: "empty with-groups", + replace: removeKeys(TimeKey, LevelKey), + with: func(h Handler) Handler { + return h.WithGroup("x").WithGroup("y") + }, + wantText: "msg=message", + wantJSON: `{"msg":"message"}`, + }, + { + name: "empty with-groups, no non-empty attrs", + replace: removeKeys(TimeKey, LevelKey), + with: func(h Handler) Handler { + return h.WithGroup("x").WithAttrs([]Attr{Group("g")}).WithGroup("y") + }, + wantText: "msg=message", + wantJSON: `{"msg":"message"}`, + }, + { + name: "one empty with-group", + replace: removeKeys(TimeKey, LevelKey), + with: func(h Handler) Handler { + return h.WithGroup("x").WithAttrs([]Attr{Int("a", 1)}).WithGroup("y") + }, + attrs: []Attr{Group("g", Group("h"))}, + wantText: "msg=message x.a=1", + wantJSON: `{"msg":"message","x":{"a":1}}`, + }, { name: "GroupValue as Attr value", replace: removeKeys(TimeKey, LevelKey), diff --git a/src/log/slog/logger_test.go b/src/log/slog/logger_test.go index d151c0490c..130f2e6ac8 100644 --- a/src/log/slog/logger_test.go +++ b/src/log/slog/logger_test.go @@ -106,7 +106,7 @@ func TestConnections(t *testing.T) { // log.Logger's output goes through the handler. SetDefault(New(NewTextHandler(&slogbuf, &HandlerOptions{AddSource: true}))) log.Print("msg2") - checkLogOutput(t, slogbuf.String(), "time="+timeRE+` level=INFO source=.*logger_test.go:\d{3} msg=msg2`) + checkLogOutput(t, slogbuf.String(), "time="+timeRE+` level=INFO source=.*logger_test.go:\d{3}"? msg=msg2`) // The default log.Logger always outputs at Info level. slogbuf.Reset() diff --git a/src/log/slog/record.go b/src/log/slog/record.go index 972552d519..67b76f34e1 100644 --- a/src/log/slog/record.go +++ b/src/log/slog/record.go @@ -93,9 +93,17 @@ func (r Record) Attrs(f func(Attr) bool) { } // AddAttrs appends the given Attrs to the Record's list of Attrs. +// It omits empty groups. func (r *Record) AddAttrs(attrs ...Attr) { - n := copy(r.front[r.nFront:], attrs) - r.nFront += n + var i int + for i = 0; i < len(attrs) && r.nFront < len(r.front); i++ { + a := attrs[i] + if a.Value.isEmptyGroup() { + continue + } + r.front[r.nFront] = a + r.nFront++ + } // Check if a copy was modified by slicing past the end // and seeing if the Attr there is non-zero. if cap(r.back) > len(r.back) { @@ -104,15 +112,25 @@ func (r *Record) AddAttrs(attrs ...Attr) { panic("copies of a slog.Record were both modified") } } - r.back = append(r.back, attrs[n:]...) + ne := countEmptyGroups(attrs[i:]) + r.back = slices.Grow(r.back, len(attrs[i:])-ne) + for _, a := range attrs[i:] { + if !a.Value.isEmptyGroup() { + r.back = append(r.back, a) + } + } } // Add converts the args to Attrs as described in [Logger.Log], // then appends the Attrs to the Record's list of Attrs. +// It omits empty groups. func (r *Record) Add(args ...any) { var a Attr for len(args) > 0 { a, args = argsToAttr(args) + if a.Value.isEmptyGroup() { + continue + } if r.nFront < len(r.front) { r.front[r.nFront] = a r.nFront++ diff --git a/src/log/slog/value.go b/src/log/slog/value.go index 71a59d2639..224848f695 100644 --- a/src/log/slog/value.go +++ b/src/log/slog/value.go @@ -164,9 +164,32 @@ func DurationValue(v time.Duration) Value { // GroupValue returns a new Value for a list of Attrs. // The caller must not subsequently mutate the argument slice. func GroupValue(as ...Attr) Value { + // Remove empty groups. + // It is simpler overall to do this at construction than + // to check each Group recursively for emptiness. + if n := countEmptyGroups(as); n > 0 { + as2 := make([]Attr, 0, len(as)-n) + for _, a := range as { + if !a.Value.isEmptyGroup() { + as2 = append(as2, a) + } + } + as = as2 + } return Value{num: uint64(len(as)), any: groupptr(unsafe.SliceData(as))} } +// countEmptyGroups returns the number of empty group values in its argument. +func countEmptyGroups(as []Attr) int { + n := 0 + for _, a := range as { + if a.Value.isEmptyGroup() { + n++ + } + } + return n +} + // AnyValue returns a Value for the supplied value. // // If the supplied value is of type Value, it is returned @@ -399,6 +422,17 @@ func (v Value) Equal(w Value) bool { } } +// isEmptyGroup reports whether v is a group that has no attributes. +func (v Value) isEmptyGroup() bool { + if v.Kind() != KindGroup { + return false + } + // We do not need to recursively examine the group's Attrs for emptiness, + // because GroupValue removed them when the group was constructed, and + // groups are immutable. + return len(v.group()) == 0 +} + // append appends a text representation of v to dst. // v is formatted as with fmt.Sprint. func (v Value) append(dst []byte) []byte { diff --git a/src/log/slog/value_test.go b/src/log/slog/value_test.go index 615bed79d9..923a4e0ccc 100644 --- a/src/log/slog/value_test.go +++ b/src/log/slog/value_test.go @@ -229,6 +229,18 @@ func TestZeroTime(t *testing.T) { } } +func TestEmptyGroup(t *testing.T) { + g := GroupValue( + Int("a", 1), + Group("g1", Group("g2")), + Group("g3", Group("g4", Int("b", 2)))) + got := g.Group() + want := []Attr{Int("a", 1), Group("g3", Group("g4", Int("b", 2)))} + if !attrsEqual(got, want) { + t.Errorf("\ngot %v\nwant %v", got, want) + } +} + type replace struct { v Value } diff --git a/src/math/all_test.go b/src/math/all_test.go index 886267bc17..af3c38c2a6 100644 --- a/src/math/all_test.go +++ b/src/math/all_test.go @@ -2059,6 +2059,9 @@ var fmaC = []struct{ x, y, z, want float64 }{ // Special {0, 0, 0, 0}, + {Copysign(0, -1), 0, 0, 0}, + {0, 0, Copysign(0, -1), 0}, + {Copysign(0, -1), 0, Copysign(0, -1), Copysign(0, -1)}, {-1.1754226043408471e-38, NaN(), Inf(0), NaN()}, {0, 0, 2.22507385643494e-308, 2.22507385643494e-308}, {-8.65697792e+09, NaN(), -7.516192799999999e+09, NaN()}, @@ -2077,6 +2080,10 @@ var fmaC = []struct{ x, y, z, want float64 }{ {4.612811918325842e+18, 1.4901161193847641e-08, 2.6077032311277997e-08, 6.873625395187494e+10}, {-9.094947033611148e-13, 4.450691014249257e-308, 2.086006742350485e-308, 2.086006742346437e-308}, {-7.751454006381804e-05, 5.588653777189071e-308, -2.2207280111272877e-308, -2.2211612130544025e-308}, + + // Issue #61130 + {-1, 1, 1, 0}, + {1, 1, -1, 0}, } var sqrt32 = []float32{ @@ -3099,6 +3106,45 @@ func TestFMA(t *testing.T) { } } +//go:noinline +func fmsub(x, y, z float64) float64 { + return FMA(x, y, -z) +} + +//go:noinline +func fnmsub(x, y, z float64) float64 { + return FMA(-x, y, z) +} + +//go:noinline +func fnmadd(x, y, z float64) float64 { + return FMA(-x, y, -z) +} + +func TestFMANegativeArgs(t *testing.T) { + // Some architectures have instructions for fused multiply-subtract and + // also negated variants of fused multiply-add and subtract. This test + // aims to check that the optimizations that generate those instructions + // are applied correctly, if they exist. + for _, c := range fmaC { + want := PortableFMA(c.x, c.y, -c.z) + got := fmsub(c.x, c.y, c.z) + if !alike(got, want) { + t.Errorf("FMA(%g, %g, -(%g)) == %g, want %g", c.x, c.y, c.z, got, want) + } + want = PortableFMA(-c.x, c.y, c.z) + got = fnmsub(c.x, c.y, c.z) + if !alike(got, want) { + t.Errorf("FMA(-(%g), %g, %g) == %g, want %g", c.x, c.y, c.z, got, want) + } + want = PortableFMA(-c.x, c.y, -c.z) + got = fnmadd(c.x, c.y, c.z) + if !alike(got, want) { + t.Errorf("FMA(-(%g), %g, -(%g)) == %g, want %g", c.x, c.y, c.z, got, want) + } + } +} + // Check that math functions of high angle values // return accurate results. [Since (vf[i] + large) - large != vf[i], // testing for Trig(vf[i] + large) == Trig(vf[i]), where large is diff --git a/src/math/fma.go b/src/math/fma.go index ca0bf99f21..ba03fbe8a9 100644 --- a/src/math/fma.go +++ b/src/math/fma.go @@ -132,6 +132,11 @@ func FMA(x, y, z float64) float64 { ps, pe, pm1, pm2, zs, ze, zm1, zm2 = zs, ze, zm1, zm2, ps, pe, pm1, pm2 } + // Special case: if p == -z the result is always +0 since neither operand is zero. + if ps != zs && pe == ze && pm1 == zm1 && pm2 == zm2 { + return 0 + } + // Align significands zm1, zm2 = shrcompress(zm1, zm2, uint(pe-ze)) diff --git a/src/net/cgo_stub.go b/src/net/cgo_stub.go index bd483110b5..b26b11af8b 100644 --- a/src/net/cgo_stub.go +++ b/src/net/cgo_stub.go @@ -3,12 +3,13 @@ // license that can be found in the LICENSE file. // This file holds stub versions of the cgo functions called on Unix systems. -// We build this file if using the netgo build tag, or if cgo is not -// enabled and we are using a Unix system other than Darwin. -// Darwin is exempted because it always provides the cgo routines, -// in cgo_unix_syscall.go. +// We build this file: +// - if using the netgo build tag on a Unix system +// - on a Unix system without the cgo resolver functions +// (Darwin always provides the cgo functions, in cgo_unix_syscall.go) +// - on wasip1, where cgo is never available -//go:build netgo || (!cgo && unix && !darwin) +//go:build (netgo && unix) || (unix && !cgo && !darwin) || wasip1 package net diff --git a/src/net/conf.go b/src/net/conf.go index 1db166c9e3..77cc635592 100644 --- a/src/net/conf.go +++ b/src/net/conf.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !js && !wasip1 +//go:build !js package net diff --git a/src/net/dnsclient_unix.go b/src/net/dnsclient_unix.go index f3c075c83f..dab5144e5d 100644 --- a/src/net/dnsclient_unix.go +++ b/src/net/dnsclient_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !js && !wasip1 +//go:build !js // DNS client: see RFC 1035. // Has to be linked into package net for Dial. diff --git a/src/net/dnsconfig_unix.go b/src/net/dnsconfig_unix.go index d5f34e5300..69b300410a 100644 --- a/src/net/dnsconfig_unix.go +++ b/src/net/dnsconfig_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !js && !wasip1 && !windows +//go:build !js && !windows // Read system DNS config from /etc/resolv.conf diff --git a/src/net/http/http_test.go b/src/net/http/http_test.go index 1c9fb33b69..91bb1b2620 100644 --- a/src/net/http/http_test.go +++ b/src/net/http/http_test.go @@ -48,35 +48,6 @@ func TestForeachHeaderElement(t *testing.T) { } } -func TestCleanHost(t *testing.T) { - tests := []struct { - in, want string - }{ - {"www.google.com", "www.google.com"}, - {"www.google.com foo", "www.google.com"}, - {"www.google.com/foo", "www.google.com"}, - {" first character is a space", ""}, - {"[1::6]:8080", "[1::6]:8080"}, - - // Punycode: - {"гофер.рф/foo", "xn--c1ae0ajs.xn--p1ai"}, - {"bücher.de", "xn--bcher-kva.de"}, - {"bücher.de:8080", "xn--bcher-kva.de:8080"}, - // Verify we convert to lowercase before punycode: - {"BÜCHER.de", "xn--bcher-kva.de"}, - {"BÜCHER.de:8080", "xn--bcher-kva.de:8080"}, - // Verify we normalize to NFC before punycode: - {"gophér.nfc", "xn--gophr-esa.nfc"}, // NFC input; no work needed - {"goph\u0065\u0301r.nfd", "xn--gophr-esa.nfd"}, // NFD input - } - for _, tt := range tests { - got := cleanHost(tt.in) - if tt.want != got { - t.Errorf("cleanHost(%q) = %q, want %q", tt.in, got, tt.want) - } - } -} - // Test that cmd/go doesn't link in the HTTP server. // // This catches accidental dependencies between the HTTP transport and diff --git a/src/net/http/request.go b/src/net/http/request.go index 4e9190493c..bd868373c5 100644 --- a/src/net/http/request.go +++ b/src/net/http/request.go @@ -17,7 +17,6 @@ import ( "io" "mime" "mime/multipart" - "net" "net/http/httptrace" "net/http/internal/ascii" "net/textproto" @@ -27,6 +26,7 @@ import ( "strings" "sync" + "golang.org/x/net/http/httpguts" "golang.org/x/net/idna" ) @@ -580,12 +580,19 @@ func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitF // is not given, use the host from the request URL. // // Clean the host, in case it arrives with unexpected stuff in it. - host := cleanHost(r.Host) + host := r.Host if host == "" { if r.URL == nil { return errMissingHost } - host = cleanHost(r.URL.Host) + host = r.URL.Host + } + host, err = httpguts.PunycodeHostPort(host) + if err != nil { + return err + } + if !httpguts.ValidHostHeader(host) { + return errors.New("http: invalid Host header") } // According to RFC 6874, an HTTP client, proxy, or other @@ -742,40 +749,6 @@ func idnaASCII(v string) (string, error) { return idna.Lookup.ToASCII(v) } -// cleanHost cleans up the host sent in request's Host header. -// -// It both strips anything after '/' or ' ', and puts the value -// into Punycode form, if necessary. -// -// Ideally we'd clean the Host header according to the spec: -// -// https://tools.ietf.org/html/rfc7230#section-5.4 (Host = uri-host [ ":" port ]") -// https://tools.ietf.org/html/rfc7230#section-2.7 (uri-host -> rfc3986's host) -// https://tools.ietf.org/html/rfc3986#section-3.2.2 (definition of host) -// -// But practically, what we are trying to avoid is the situation in -// issue 11206, where a malformed Host header used in the proxy context -// would create a bad request. So it is enough to just truncate at the -// first offending character. -func cleanHost(in string) string { - if i := strings.IndexAny(in, " /"); i != -1 { - in = in[:i] - } - host, port, err := net.SplitHostPort(in) - if err != nil { // input was just a host - a, err := idnaASCII(in) - if err != nil { - return in // garbage in, garbage out - } - return a - } - a, err := idnaASCII(host) - if err != nil { - return in // garbage in, garbage out - } - return net.JoinHostPort(a, port) -} - // removeZone removes IPv6 zone identifier from host. // E.g., "[fe80::1%en0]:8080" to "[fe80::1]:8080" func removeZone(host string) string { diff --git a/src/net/http/request_test.go b/src/net/http/request_test.go index 78b968f23c..0892bc255f 100644 --- a/src/net/http/request_test.go +++ b/src/net/http/request_test.go @@ -775,15 +775,8 @@ func TestRequestBadHost(t *testing.T) { } req.Host = "foo.com with spaces" req.URL.Host = "foo.com with spaces" - req.Write(logWrites{t, &got}) - want := []string{ - "GET /after HTTP/1.1\r\n", - "Host: foo.com\r\n", - "User-Agent: " + DefaultUserAgent + "\r\n", - "\r\n", - } - if !reflect.DeepEqual(got, want) { - t.Errorf("Writes = %q\n Want = %q", got, want) + if err := req.Write(logWrites{t, &got}); err == nil { + t.Errorf("Writing request with invalid Host: succeded, want error") } } diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go index 172aba679b..028fecc961 100644 --- a/src/net/http/transport_test.go +++ b/src/net/http/transport_test.go @@ -6731,3 +6731,22 @@ func testHandlerAbortRacesBodyRead(t *testing.T, mode testMode) { } wg.Wait() } + +func TestRequestSanitization(t *testing.T) { run(t, testRequestSanitization) } +func testRequestSanitization(t *testing.T, mode testMode) { + if mode == http2Mode { + // Remove this after updating x/net. + t.Skip("https://go.dev/issue/60374 test fails when run with HTTP/2") + } + ts := newClientServerTest(t, mode, HandlerFunc(func(rw ResponseWriter, req *Request) { + if h, ok := req.Header["X-Evil"]; ok { + t.Errorf("request has X-Evil header: %q", h) + } + })).ts + req, _ := NewRequest("GET", ts.URL, nil) + req.Host = "go.dev\r\nX-Evil:evil" + resp, _ := ts.Client().Do(req) + if resp != nil { + resp.Body.Close() + } +} diff --git a/src/net/lookup_fake.go b/src/net/lookup_fake.go index 45146e1c95..c27eae4ba5 100644 --- a/src/net/lookup_fake.go +++ b/src/net/lookup_fake.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build (js && wasm) || wasip1 +//go:build js && wasm package net diff --git a/src/net/lookup_unix.go b/src/net/lookup_unix.go index dc75e0a3b6..56ae11e961 100644 --- a/src/net/lookup_unix.go +++ b/src/net/lookup_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build unix +//go:build unix || wasip1 package net diff --git a/src/net/net_fake.go b/src/net/net_fake.go index 68d36966ca..908767a1f6 100644 --- a/src/net/net_fake.go +++ b/src/net/net_fake.go @@ -15,8 +15,6 @@ import ( "sync" "syscall" "time" - - "golang.org/x/net/dns/dnsmessage" ) var listenersMu sync.Mutex @@ -406,7 +404,3 @@ func (fd *fakeNetFD) writeMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, func (fd *fakeNetFD) dup() (f *os.File, err error) { return nil, syscall.ENOSYS } - -func (r *Resolver) lookup(ctx context.Context, name string, qtype dnsmessage.Type, conf *dnsConfig) (dnsmessage.Parser, string, error) { - panic("unreachable") -} diff --git a/src/net/net_fake_js.go b/src/net/net_fake_js.go index 1fc0b50b7d..7ba108b664 100644 --- a/src/net/net_fake_js.go +++ b/src/net/net_fake_js.go @@ -8,7 +8,12 @@ package net -import "internal/poll" +import ( + "context" + "internal/poll" + + "golang.org/x/net/dns/dnsmessage" +) // Network file descriptor. type netFD struct { @@ -25,3 +30,7 @@ type netFD struct { pfd poll.FD isConnected bool // handshake completed or use of association with peer } + +func (r *Resolver) lookup(ctx context.Context, name string, qtype dnsmessage.Type, conf *dnsConfig) (dnsmessage.Parser, string, error) { + panic("unreachable") +} diff --git a/src/os/dir_darwin.go b/src/os/dir_darwin.go index deba3eb37f..e6d5bda24b 100644 --- a/src/os/dir_darwin.go +++ b/src/os/dir_darwin.go @@ -54,6 +54,15 @@ func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEn if entptr == nil { // EOF break } + // Darwin may return a zero inode when a directory entry has been + // deleted but not yet removed from the directory. The man page for + // getdirentries(2) states that programs are responsible for skipping + // those entries: + // + // Users of getdirentries() should skip entries with d_fileno = 0, + // as such entries represent files which have been deleted but not + // yet removed from the directory entry. + // if dirent.Ino == 0 { continue } diff --git a/src/os/dir_unix.go b/src/os/dir_unix.go index 004b9fbb2b..266a78acaf 100644 --- a/src/os/dir_unix.go +++ b/src/os/dir_unix.go @@ -89,7 +89,11 @@ func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEn if !ok { break } - if ino == 0 { + // When building to wasip1, the host runtime might be running on Windows + // or might expose a remote file system which does not have the concept + // of inodes. Therefore, we cannot make the assumption that it is safe + // to skip entries with zero inodes. + if ino == 0 && runtime.GOOS != "wasip1" { continue } const namoff = uint64(unsafe.Offsetof(syscall.Dirent{}.Name)) diff --git a/src/os/env_test.go b/src/os/env_test.go index 1b9e26594c..5809f4b866 100644 --- a/src/os/env_test.go +++ b/src/os/env_test.go @@ -130,7 +130,7 @@ func TestClearenv(t *testing.T) { defer func(origEnv []string) { for _, pair := range origEnv { // Environment variables on Windows can begin with = - // https://blogs.msdn.com/b/oldnewthing/archive/2010/05/06/10008132.aspx + // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 i := strings.Index(pair[1:], "=") + 1 if err := Setenv(pair[:i], pair[i+1:]); err != nil { t.Errorf("Setenv(%q, %q) failed during reset: %v", pair[:i], pair[i+1:], err) diff --git a/src/runtime/crash_test.go b/src/runtime/crash_test.go index cd978cc34b..8f11333b46 100644 --- a/src/runtime/crash_test.go +++ b/src/runtime/crash_test.go @@ -534,7 +534,7 @@ func TestMemPprof(t *testing.T) { got, err := testenv.CleanCmdEnv(exec.Command(exe, "MemProf")).CombinedOutput() if err != nil { - t.Fatal(err) + t.Fatalf("testprog failed: %s, output:\n%s", err, got) } fn := strings.TrimSpace(string(got)) defer os.Remove(fn) diff --git a/src/runtime/crash_unix_test.go b/src/runtime/crash_unix_test.go index 07060b8fab..6bca2ac66e 100644 --- a/src/runtime/crash_unix_test.go +++ b/src/runtime/crash_unix_test.go @@ -76,7 +76,7 @@ func TestCrashDumpsAllThreads(t *testing.T) { testenv.MustHaveGoBuild(t) - if strings.Contains(os.Getenv("GCFLAGS"), "mayMoreStackPreempt") { + if strings.Contains(os.Getenv("GOFLAGS"), "mayMoreStackPreempt") { // This test occasionally times out in this debug mode. This is probably // revealing a real bug in the scheduler, but since it seems to only // affect this test and this is itself a test of a debug mode, it's not diff --git a/src/runtime/metrics/description.go b/src/runtime/metrics/description.go index aea51c7f75..745691b24f 100644 --- a/src/runtime/metrics/description.go +++ b/src/runtime/metrics/description.go @@ -339,9 +339,11 @@ var allDesc = []Description{ Kind: KindUint64, }, { - Name: "/memory/classes/heap/stacks:bytes", - Description: "Memory allocated from the heap that is reserved for stack space, whether or not it is currently in-use.", - Kind: KindUint64, + Name: "/memory/classes/heap/stacks:bytes", + Description: "Memory allocated from the heap that is reserved for stack space, whether or not it is currently in-use. " + + "Currently, this represents all stack memory for goroutines. It also includes all OS thread stacks in non-cgo programs. " + + "Note that stacks may be allocated differently in the future, and this may change.", + Kind: KindUint64, }, { Name: "/memory/classes/heap/unused:bytes", @@ -374,9 +376,13 @@ var allDesc = []Description{ Kind: KindUint64, }, { - Name: "/memory/classes/os-stacks:bytes", - Description: "Stack memory allocated by the underlying operating system.", - Kind: KindUint64, + Name: "/memory/classes/os-stacks:bytes", + Description: "Stack memory allocated by the underlying operating system. " + + "In non-cgo programs this metric is currently zero. This may change in the future." + + "In cgo programs this metric includes OS thread stacks allocated directly from the OS. " + + "Currently, this only accounts for one stack in c-shared and c-archive build modes, " + + "and other sources of stacks from the OS are not measured. This too may change in the future.", + Kind: KindUint64, }, { Name: "/memory/classes/other:bytes", diff --git a/src/runtime/metrics/doc.go b/src/runtime/metrics/doc.go index 5238bcea8e..5c52f78477 100644 --- a/src/runtime/metrics/doc.go +++ b/src/runtime/metrics/doc.go @@ -318,7 +318,10 @@ Below is the full list of supported metrics, ordered lexicographically. /memory/classes/heap/stacks:bytes Memory allocated from the heap that is reserved for stack space, - whether or not it is currently in-use. + whether or not it is currently in-use. Currently, this + represents all stack memory for goroutines. It also includes all + OS thread stacks in non-cgo programs. Note that stacks may be + allocated differently in the future, and this may change. /memory/classes/heap/unused:bytes Memory that is reserved for heap objects but is not currently @@ -345,6 +348,12 @@ Below is the full list of supported metrics, ordered lexicographically. /memory/classes/os-stacks:bytes Stack memory allocated by the underlying operating system. + In non-cgo programs this metric is currently zero. This may + change in the future.In cgo programs this metric includes + OS thread stacks allocated directly from the OS. Currently, + this only accounts for one stack in c-shared and c-archive build + modes, and other sources of stacks from the OS are not measured. + This too may change in the future. /memory/classes/other:bytes Memory used by execution trace buffers, structures for debugging diff --git a/src/runtime/mstats.go b/src/runtime/mstats.go index ab383dd8e3..9cdc565137 100644 --- a/src/runtime/mstats.go +++ b/src/runtime/mstats.go @@ -199,7 +199,17 @@ type MemStats struct { // StackSys is bytes of stack memory obtained from the OS. // // StackSys is StackInuse, plus any memory obtained directly - // from the OS for OS thread stacks (which should be minimal). + // from the OS for OS thread stacks. + // + // In non-cgo programs this metric is currently equal to StackInuse + // (but this should not be relied upon, and the value may change in + // the future). + // + // In cgo programs this metric includes OS thread stacks allocated + // directly from the OS. Currently, this only accounts for one stack in + // c-shared and c-archive build modes and other sources of stacks from + // the OS (notably, any allocated by C code) are not currently measured. + // Note this too may change in the future. StackSys uint64 // Off-heap memory statistics. @@ -347,6 +357,7 @@ func init() { // which is a snapshot as of the most recently completed garbage // collection cycle. func ReadMemStats(m *MemStats) { + _ = m.Alloc // nil check test before we switch stacks, see issue 61158 stopTheWorld(stwReadMemStats) systemstack(func() { diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go index d6f89210a4..86df1155b5 100644 --- a/src/runtime/traceback.go +++ b/src/runtime/traceback.go @@ -1147,6 +1147,7 @@ func showfuncinfo(sf srcFunc, firstFrame bool, calleeID abi.FuncID) bool { // isExportedRuntime reports whether name is an exported runtime function. // It is only for runtime functions, so ASCII A-Z is fine. +// TODO: this handles exported functions but not exported methods. func isExportedRuntime(name string) bool { const n = len("runtime.") return len(name) > n && name[:n] == "runtime." && 'A' <= name[n] && name[n] <= 'Z' diff --git a/src/syscall/dirent.go b/src/syscall/dirent.go index eee94bf73c..1a0f1eec11 100644 --- a/src/syscall/dirent.go +++ b/src/syscall/dirent.go @@ -6,7 +6,10 @@ package syscall -import "unsafe" +import ( + "runtime" + "unsafe" +) // readInt returns the size-bytes unsigned integer in native byte order at offset off. func readInt(b []byte, off, size uintptr) (u uint64, ok bool) { @@ -75,7 +78,9 @@ func ParseDirent(buf []byte, max int, names []string) (consumed int, count int, if !ok { break } - if ino == 0 { // File absent in directory. + // See src/os/dir_unix.go for the reason why this condition is + // excluded on wasip1. + if ino == 0 && runtime.GOOS != "wasip1" { // File absent in directory. continue } const namoff = uint64(unsafe.Offsetof(Dirent{}.Name)) diff --git a/src/syscall/env_windows.go b/src/syscall/env_windows.go index 20d74b51e0..220a005e1a 100644 --- a/src/syscall/env_windows.go +++ b/src/syscall/env_windows.go @@ -62,7 +62,7 @@ func Clearenv() { for _, s := range Environ() { // Environment variables can begin with = // so start looking for the separator = at j=1. - // https://blogs.msdn.com/b/oldnewthing/archive/2010/05/06/10008132.aspx + // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 for j := 1; j < len(s); j++ { if s[j] == '=' { Unsetenv(s[0:j]) diff --git a/src/syscall/syscall_wasip1.go b/src/syscall/syscall_wasip1.go index 5d19c000ae..e66afee5e9 100644 --- a/src/syscall/syscall_wasip1.go +++ b/src/syscall/syscall_wasip1.go @@ -478,3 +478,16 @@ func SetNonblock(fd int, nonblocking bool) error { errno := fd_fdstat_set_flags(int32(fd), flags) return errnoErr(errno) } + +type Rlimit struct { + Cur uint64 + Max uint64 +} + +const ( + RLIMIT_NOFILE = iota +) + +func Getrlimit(which int, lim *Rlimit) error { + return ENOSYS +} diff --git a/test/codegen/floats.go b/test/codegen/floats.go index 81471082d4..9cb62e031a 100644 --- a/test/codegen/floats.go +++ b/test/codegen/floats.go @@ -20,6 +20,7 @@ func Mul2(f float64) float64 { // arm/7:"ADDD",-"MULD" // arm64:"FADDD",-"FMULD" // ppc64x:"FADD",-"FMUL" + // riscv64:"FADDD",-"FMULD" return f * 2.0 } @@ -29,6 +30,7 @@ func DivPow2(f1, f2, f3 float64) (float64, float64, float64) { // arm/7:"MULD",-"DIVD" // arm64:"FMULD",-"FDIVD" // ppc64x:"FMUL",-"FDIV" + // riscv64:"FMULD",-"FDIVD" x := f1 / 16.0 // 386/sse2:"MULSD",-"DIVSD" @@ -36,6 +38,7 @@ func DivPow2(f1, f2, f3 float64) (float64, float64, float64) { // arm/7:"MULD",-"DIVD" // arm64:"FMULD",-"FDIVD" // ppc64x:"FMUL",-"FDIVD" + // riscv64:"FMULD",-"FDIVD" y := f2 / 0.125 // 386/sse2:"ADDSD",-"DIVSD",-"MULSD" @@ -43,6 +46,7 @@ func DivPow2(f1, f2, f3 float64) (float64, float64, float64) { // arm/7:"ADDD",-"MULD",-"DIVD" // arm64:"FADDD",-"FMULD",-"FDIVD" // ppc64x:"FADD",-"FMUL",-"FDIV" + // riscv64:"FADDD",-"FMULD",-"FDIVD" z := f3 / 0.5 return x, y, z diff --git a/test/codegen/math.go b/test/codegen/math.go index 6a7d304afd..331ebbe609 100644 --- a/test/codegen/math.go +++ b/test/codegen/math.go @@ -143,13 +143,13 @@ func fms(x, y, z float64) float64 { return math.FMA(x, y, -z) } -func fnma(x, y, z float64) float64 { - // riscv64:"FNMADDD" +func fnms(x, y, z float64) float64 { + // riscv64:"FNMSUBD",-"FNMADDD" return math.FMA(-x, y, z) } -func fnms(x, y, z float64) float64 { - // riscv64:"FNMSUBD" +func fnma(x, y, z float64) float64 { + // riscv64:"FNMADDD",-"FNMSUBD" return math.FMA(x, -y, -z) }