cmd/go: refuse to download zip files for too-new modules

In general an older version of Go does not know how to construct
a module written against a newer version of Go: the details may
change over time, such as for issues like #42965 (an ignore mechanism).

For #57001.

Change-Id: Id43fcfb71497375ad2eb5dfd292bad0adca0652e
Reviewed-on: https://go-review.googlesource.com/c/go/+/497795
Run-TryBot: Russ Cox <rsc@golang.org>
Auto-Submit: Russ Cox <rsc@golang.org>
Reviewed-by: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
Russ Cox 2023-05-22 12:34:17 -04:00 committed by Gopher Robot
parent f4086d3686
commit a5be6dbbaf
11 changed files with 130 additions and 15 deletions

View File

@ -78,6 +78,7 @@ func switchGoToolchain() {
// and diagnose the problem.
return
}
gover.Startup.GOTOOLCHAIN = gotoolchain
var minToolchain, minVers string
if x, y, ok := strings.Cut(gotoolchain, "+"); ok { // go1.2.3+auto
@ -111,7 +112,7 @@ func switchGoToolchain() {
gotoolchain = "go" + goVers
}
} else {
goVers, toolchain := modGoToolchain()
file, goVers, toolchain := modGoToolchain()
if toolchain == "local" {
// Local means always use the default local toolchain,
// which is already set, so nothing to do here.
@ -136,6 +137,9 @@ func switchGoToolchain() {
gotoolchain = "go" + goVers
}
}
gover.Startup.AutoFile = file
gover.Startup.AutoGoVersion = goVers
gover.Startup.AutoToolchain = toolchain
}
}
@ -280,9 +284,9 @@ func execGoToolchain(gotoolchain, dir, exe string) {
// modGoToolchain finds the enclosing go.work or go.mod file
// and returns the go version and toolchain lines from the file.
// The toolchain line overrides the version line
func modGoToolchain() (goVers, toolchain string) {
func modGoToolchain() (file, goVers, toolchain string) {
wd := base.UncachedCwd()
file := modload.FindGoWork(wd)
file = modload.FindGoWork(wd)
// $GOWORK can be set to a file that does not yet exist, if we are running 'go work init'.
// Do not try to load the file in that case
if _, err := os.Stat(file); err != nil {
@ -292,14 +296,14 @@ func modGoToolchain() (goVers, toolchain string) {
file = modload.FindGoMod(wd)
}
if file == "" {
return "", ""
return "", "", ""
}
data, err := os.ReadFile(file)
if err != nil {
base.Fatalf("%v", err)
}
return gover.GoModLookup(data, "go"), gover.GoModLookup(data, "toolchain")
return file, gover.GoModLookup(data, "go"), gover.GoModLookup(data, "toolchain")
}
// goInstallVersion looks at the command line to see if it is go install m@v or go run m@v.

View File

@ -4,7 +4,12 @@
package gover
import "strings"
import (
"cmd/go/internal/base"
"errors"
"fmt"
"strings"
)
// ToolchainVersion returns the Go version for the named toolchain,
// derived from the name itself (not by running the toolchain).
@ -23,3 +28,44 @@ func ToolchainVersion(name string) string {
}
return v
}
// Startup records the information that went into the startup-time version switch.
// It is initialized by switchGoToolchain.
var Startup struct {
GOTOOLCHAIN string // $GOTOOLCHAIN setting
AutoFile string // go.mod or go.work file consulted
AutoGoVersion string // go line found in file
AutoToolchain string // toolchain line found in file
}
// A TooNewError explains that a module is too new for this version of Go.
type TooNewError struct {
What string
GoVersion string
}
func (e *TooNewError) Error() string {
var explain string
if Startup.GOTOOLCHAIN != "" && Startup.GOTOOLCHAIN != "auto" {
explain = "; GOTOOLCHAIN=" + Startup.GOTOOLCHAIN
}
if Startup.AutoFile != "" && (Startup.AutoGoVersion != "" || Startup.AutoToolchain != "") {
explain += fmt.Sprintf("; %s sets ", base.ShortPath(Startup.AutoFile))
if Startup.AutoGoVersion != "" {
explain += "go " + Startup.AutoGoVersion
if Startup.AutoToolchain != "" {
explain += ", "
}
}
if Startup.AutoToolchain != "" {
explain += "toolchain " + Startup.AutoToolchain
}
}
return fmt.Sprintf("%v requires go %v (running go %v%v)", e.What, e.GoVersion, Local(), explain)
}
var ErrTooNew = errors.New("module too new")
func (e *TooNewError) Is(err error) bool {
return err == ErrTooNew
}

View File

@ -14,10 +14,12 @@ import (
"io/fs"
"os"
"path"
"path/filepath"
"sort"
"strings"
"time"
"cmd/go/internal/gover"
"cmd/go/internal/modfetch/codehost"
"golang.org/x/mod/modfile"
@ -1046,6 +1048,16 @@ func (r *codeRepo) Zip(ctx context.Context, dst io.Writer, version string) error
if err != nil {
return err
}
if gomod, err := r.code.ReadFile(ctx, rev, filepath.Join(subdir, "go.mod"), codehost.MaxGoMod); err == nil {
goVers := gover.GoModLookup(gomod, "go")
if gover.Compare(goVers, gover.Local()) > 0 {
return &gover.TooNewError{What: r.ModulePath() + "@" + version, GoVersion: goVers}
}
} else if !errors.Is(err, fs.ErrNotExist) {
return err
}
dl, err := r.code.ReadZip(ctx, rev, subdir, codehost.MaxZipFile)
if err != nil {
return err

View File

@ -57,6 +57,17 @@ func Download(ctx context.Context, mod module.Version) (dir string, err error) {
return "", err
}
checkMod(ctx, mod)
// If go.mod exists (not an old legacy module), check version is not too new.
if data, err := os.ReadFile(filepath.Join(dir, "go.mod")); err == nil {
goVersion := gover.GoModLookup(data, "go")
if gover.Compare(goVersion, gover.Local()) > 0 {
return "", &gover.TooNewError{What: mod.String(), GoVersion: goVersion}
}
} else if !errors.Is(err, fs.ErrNotExist) {
return "", err
}
return dir, nil
})
}
@ -554,7 +565,7 @@ func HaveSum(mod module.Version) bool {
return false
}
// checkMod checks the given module's checksum.
// checkMod checks the given module's checksum and Go version.
func checkMod(ctx context.Context, mod module.Version) {
// Do the file I/O before acquiring the go.sum lock.
ziphash, err := CachePath(ctx, mod, "ziphash")

View File

@ -630,7 +630,7 @@ func ReadWorkFile(path string) (*modfile.WorkFile, error) {
return nil, err
}
if f.Go != nil && gover.Compare(f.Go.Version, gover.Local()) > 0 {
base.Fatalf("go: %s requires go %v (running go %v)", base.ShortPath(path), f.Go.Version, gover.Local())
base.Fatalf("go: %v", &gover.TooNewError{What: base.ShortPath(path), GoVersion: f.Go.Version})
}
return f, nil
}
@ -1498,6 +1498,12 @@ func commitRequirements(ctx context.Context) (err error) {
if modFile.Go == nil || modFile.Go.Version == "" {
modFile.AddGoStmt(modFileGoVersion(modFile))
}
if gover.Compare(modFile.Go.Version, gover.Local()) > 0 {
// TODO: Reinvoke the newer toolchain if GOTOOLCHAIN=auto.
base.Fatalf("go: %v", &gover.TooNewError{What: "updating go.mod", GoVersion: modFile.Go.Version})
}
if gover.Compare(modFileGoVersion(modFile), separateIndirectVersion) < 0 {
modFile.SetRequire(list)
} else {

View File

@ -75,7 +75,7 @@ func ReadModFile(gomod string, fix modfile.VersionFixer) (data []byte, f *modfil
return nil, nil, fmt.Errorf("errors parsing go.mod:\n%s\n", err)
}
if f.Go != nil && gover.Compare(f.Go.Version, gover.Local()) > 0 {
base.Fatalf("go: %s requires go %v (running go %v)", base.ShortPath(gomod), f.Go.Version, gover.Local())
base.Fatalf("go: %v", &gover.TooNewError{What: base.ShortPath(gomod), GoVersion: f.Go.Version})
}
if f.Module == nil {
// No module declaration. Must add module path.
@ -717,6 +717,11 @@ func rawGoModSummary(m module.Version) (*modFileSummary, error) {
summary.require = append(summary.require, req.Mod)
}
}
if summary.goVersion != "" && gover.Compare(summary.goVersion, "1.21") >= 0 {
if gover.Compare(summary.goVersion, gover.Local()) > 0 {
return nil, &gover.TooNewError{What: summary.module.String(), GoVersion: summary.goVersion}
}
}
if len(f.Retract) > 0 {
summary.retract = make([]retraction, 0, len(f.Retract))
for _, ret := range f.Retract {

View File

@ -112,7 +112,7 @@ func readVendorList(mainModule module.Version) {
meta.GoVersion = goVersion
rawGoVersion.Store(mod, meta.GoVersion)
if gover.Compare(goVersion, gover.Local()) > 0 {
base.Fatalf("go: %s in %s requires go %v (running go %v)", mod.Path, base.ShortPath(vendorFile), goVersion, gover.Local())
base.Fatalf("go: %v", &gover.TooNewError{What: mod.Path + " in " + base.ShortPath(vendorFile), GoVersion: goVersion})
}
}
// All other tokens are reserved for future use.

View File

@ -0,0 +1,16 @@
rsc.io/future v1.0.0
written by hand
-- .mod --
module rsc.io/future
go 1.999
-- .info --
{"Version":"v1.0.0"}
-- main.go --
package main
func main() {
}
-- go.mod --
module rsc.io/future
go 1.999

View File

@ -52,13 +52,13 @@ stdout go1.999
# toolchain local in go.mod
cp go1999toolchainlocal go.mod
! go build
stderr '^go: go.mod requires go 1.999 \(running go 1\.100\)$'
stderr '^go: go.mod requires go 1.999 \(running go 1.100; go.mod sets go 1.999, toolchain local\)$'
# toolchain local in go.work
cp empty go.mod
cp go1999toolchainlocal go.work
! go build
stderr '^go: go.work requires go 1.999 \(running go 1\.100\)$'
stderr '^go: go.work requires go 1.999 \(running go 1.100; go.work sets go 1.999, toolchain local\)$'
rm go.work
# toolchain line in go.work

View File

@ -0,0 +1,12 @@
env TESTGO_VERSION=go1.21
! go mod download rsc.io/future@v1.0.0
stderr '^go: rsc.io/future@v1.0.0 requires go 1.999 \(running go 1.21; go.mod sets go 1.21\)$'
-- go.mod --
module m
go 1.21
-- x.go --
package p
import "rsc.io/future/foo"

View File

@ -1,10 +1,13 @@
# Test support for declaring needed Go version in module.
env GO111MODULE=on
env TESTGO_VERSION=go1.21
go list
# TODO(rsc): go list prints the error twice. Why?
! go list
stderr '^go: sub@v1.0.0: sub requires go 1.999 \(running go 1.21; go.mod sets go 1.1\)$'
! go build sub
stderr '^sub: module requires Go 1.999 or later$'
stderr '^go: sub@v1.0.0: sub requires go 1.999 \(running go 1.21; go.mod sets go 1.1\)$'
-- go.mod --
module m
@ -20,7 +23,7 @@ replace (
package x
-- sub/go.mod --
module m
module sub
go 1.999
-- sub/x.go --