]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: refuse to download zip files for too-new modules
authorRuss Cox <rsc@golang.org>
Mon, 22 May 2023 16:34:17 +0000 (12:34 -0400)
committerGopher Robot <gobot@golang.org>
Thu, 25 May 2023 17:07:09 +0000 (17:07 +0000)
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>

src/cmd/go/gotoolchain.go
src/cmd/go/internal/gover/toolchain.go
src/cmd/go/internal/modfetch/coderepo.go
src/cmd/go/internal/modfetch/fetch.go
src/cmd/go/internal/modload/init.go
src/cmd/go/internal/modload/modfile.go
src/cmd/go/internal/modload/vendor.go
src/cmd/go/testdata/mod/rsc.io_future_v1.0.0.txt [new file with mode: 0644]
src/cmd/go/testdata/script/gotoolchain.txt
src/cmd/go/testdata/script/mod_get_future.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_go_version.txt

index 850777a8547084d11836674587639218ea9cf15c..26d80db246c6dc454d78c9f892dbe36483a56461 100644 (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.
index ed1572ab59346decfb89a8b5ae1806779bcdaeaa..bf5a64d056dbd4a9da99cb608a9d64d8d4c01e33 100644 (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
+}
index 85e791a4355b3e0ab68fecae577c8defdfc06eef..50f4bb2b37a27075215d446d9ce335ac6ecdd146 100644 (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
index b872c9320f5d1dbcf243b6a9cce2ebf51483c4b5..0eeb3bf190602b1cf61a9e05641f6c77339c3b92 100644 (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")
index 240a9d29e00154af8992e621cbd921aab4fbe929..58747e7129f05c7240c23d730b47786bd2a1482f 100644 (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 {
index 02d921201b37c05f5a0f0e6551e2ba74bf050e8d..cb1101630b3a76ae7af9c7bdf20d08f62f6dfc90 100644 (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 {
index 2fb834b86654ddef068b35c2947b52dd108f8a50..4bc0dd5725ecb8e5580beea49746596526af5b94 100644 (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.
diff --git a/src/cmd/go/testdata/mod/rsc.io_future_v1.0.0.txt b/src/cmd/go/testdata/mod/rsc.io_future_v1.0.0.txt
new file mode 100644 (file)
index 0000000..d3826a3
--- /dev/null
@@ -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
index b29dcc6520ada1f718081371e9924d26bcf57cf8..97e6d5fd1bf23b72f8658aeaee2bf001b945b0f8 100644 (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
diff --git a/src/cmd/go/testdata/script/mod_get_future.txt b/src/cmd/go/testdata/script/mod_get_future.txt
new file mode 100644 (file)
index 0000000..997e5cb
--- /dev/null
@@ -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"
index 005c43ca6e23896b291cc80d4b3d901e4db0ed94..e78ef3c13996bab83daf97371bb45e0558177eaf 100644 (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 --