]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go/internal/modload: don't report path errors when loading retractions
authorJay Conrod <jayconrod@google.com>
Fri, 18 Sep 2020 15:48:18 +0000 (11:48 -0400)
committerJay Conrod <jayconrod@google.com>
Fri, 18 Sep 2020 16:07:01 +0000 (16:07 +0000)
When we load module retractions from the latest version of a module,
it's possible that the latest version has a different module path than
the version we're loading. This can happen when a module is renamed or
a go.mod file is added for the first time.

We should not report an error in this case. Retractions should still
apply to old aliases of a module.

Fixes #41350

Change-Id: If1bc0b6b2b26fc7023e02fc211aa0cd8eb00796e
Reviewed-on: https://go-review.googlesource.com/c/go/+/255961
Trust: Jay Conrod <jayconrod@google.com>
Run-TryBot: Jay Conrod <jayconrod@google.com>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Go Bot <gobot@golang.org>

src/cmd/go/internal/modload/modfile.go
src/cmd/go/testdata/mod/example.com_retract_rename_v1.0.0-bad.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/example.com_retract_rename_v1.9.0-new.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_retract_rename.txt [new file with mode: 0644]

index 18dd293ac92b1627eb654b5d2907349900a26766..6457a7d9684f2b3d78c68ed63c13104e00808fac 100644 (file)
@@ -122,12 +122,21 @@ func checkRetractions(ctx context.Context, m module.Version) error {
 
                // Load go.mod for that version.
                // If the version is replaced, we'll load retractions from the replacement.
+               //
                // If there's an error loading the go.mod, we'll return it here.
                // These errors should generally be ignored by callers of checkRetractions,
                // since they happen frequently when we're offline. These errors are not
                // equivalent to ErrDisallowed, so they may be distinguished from
                // retraction errors.
-               summary, err := goModSummary(module.Version{Path: path, Version: rev.Version})
+               //
+               // We load the raw file here: the go.mod file may have a different module
+               // path that we expect if the module or its repository was renamed.
+               // We still want to apply retractions to other aliases of the module.
+               rm := module.Version{Path: path, Version: rev.Version}
+               if repl := Replacement(rm); repl.Path != "" {
+                       rm = repl
+               }
+               summary, err := rawGoModSummary(rm)
                if err != nil {
                        return &entry{err: err}
                }
@@ -378,87 +387,90 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
                panic("internal error: goModSummary called on the Target module")
        }
 
-       type cached struct {
-               summary *modFileSummary
-               err     error
-       }
-       c := goModSummaryCache.Do(m, func() interface{} {
-               if cfg.BuildMod == "vendor" {
-                       summary := &modFileSummary{
-                               module: module.Version{Path: m.Path},
-                       }
-                       if vendorVersion[m.Path] != m.Version {
-                               // This module is not vendored, so packages cannot be loaded from it and
-                               // it cannot be relevant to the build.
-                               return cached{summary, nil}
-                       }
+       if cfg.BuildMod == "vendor" {
+               summary := &modFileSummary{
+                       module: module.Version{Path: m.Path},
+               }
+               if vendorVersion[m.Path] != m.Version {
+                       // This module is not vendored, so packages cannot be loaded from it and
+                       // it cannot be relevant to the build.
+                       return summary, nil
+               }
 
-                       // For every module other than the target,
-                       // return the full list of modules from modules.txt.
-                       readVendorList()
+               // For every module other than the target,
+               // return the full list of modules from modules.txt.
+               readVendorList()
 
-                       // TODO(#36876): Load the "go" version from vendor/modules.txt and store it
-                       // in rawGoVersion with the appropriate key.
+               // TODO(#36876): Load the "go" version from vendor/modules.txt and store it
+               // in rawGoVersion with the appropriate key.
 
-                       // We don't know what versions the vendored module actually relies on,
-                       // so assume that it requires everything.
-                       summary.require = vendorList
-                       return cached{summary, nil}
-               }
+               // We don't know what versions the vendored module actually relies on,
+               // so assume that it requires everything.
+               summary.require = vendorList
+               return summary, nil
+       }
 
-               actual := Replacement(m)
-               if actual.Path == "" {
-                       actual = m
-               }
-               summary, err := rawGoModSummary(actual)
-               if err != nil {
-                       return cached{nil, err}
-               }
+       actual := Replacement(m)
+       if actual.Path == "" {
+               actual = m
+       }
+       summary, err := rawGoModSummary(actual)
+       if err != nil {
+               return nil, err
+       }
 
-               if actual.Version == "" {
-                       // The actual module is a filesystem-local replacement, for which we have
-                       // unfortunately not enforced any sort of invariants about module lines or
-                       // matching module paths. Anything goes.
-                       //
-                       // TODO(bcmills): Remove this special-case, update tests, and add a
-                       // release note.
-               } else {
-                       if summary.module.Path == "" {
-                               return cached{nil, module.VersionError(actual, errors.New("parsing go.mod: missing module line"))}
-                       }
+       if actual.Version == "" {
+               // The actual module is a filesystem-local replacement, for which we have
+               // unfortunately not enforced any sort of invariants about module lines or
+               // matching module paths. Anything goes.
+               //
+               // TODO(bcmills): Remove this special-case, update tests, and add a
+               // release note.
+       } else {
+               if summary.module.Path == "" {
+                       return nil, module.VersionError(actual, errors.New("parsing go.mod: missing module line"))
+               }
 
-                       // In theory we should only allow mpath to be unequal to m.Path here if the
-                       // version that we fetched lacks an explicit go.mod file: if the go.mod file
-                       // is explicit, then it should match exactly (to ensure that imports of other
-                       // packages within the module are interpreted correctly). Unfortunately, we
-                       // can't determine that information from the module proxy protocol: we'll have
-                       // to leave that validation for when we load actual packages from within the
-                       // module.
-                       if mpath := summary.module.Path; mpath != m.Path && mpath != actual.Path {
-                               return cached{nil, module.VersionError(actual, fmt.Errorf(`parsing go.mod:
+               // In theory we should only allow mpath to be unequal to m.Path here if the
+               // version that we fetched lacks an explicit go.mod file: if the go.mod file
+               // is explicit, then it should match exactly (to ensure that imports of other
+               // packages within the module are interpreted correctly). Unfortunately, we
+               // can't determine that information from the module proxy protocol: we'll have
+               // to leave that validation for when we load actual packages from within the
+               // module.
+               if mpath := summary.module.Path; mpath != m.Path && mpath != actual.Path {
+                       return nil, module.VersionError(actual, fmt.Errorf(`parsing go.mod:
        module declares its path as: %s
-               but was required as: %s`, mpath, m.Path))}
-                       }
+               but was required as: %s`, mpath, m.Path))
                }
+       }
 
-               if index != nil && len(index.exclude) > 0 {
-                       // Drop any requirements on excluded versions.
-                       nonExcluded := summary.require[:0]
+       if index != nil && len(index.exclude) > 0 {
+               // Drop any requirements on excluded versions.
+               // Don't modify the cached summary though, since we might need the raw
+               // summary separately.
+               haveExcludedReqs := false
+               for _, r := range summary.require {
+                       if index.exclude[r] {
+                               haveExcludedReqs = true
+                               break
+                       }
+               }
+               if haveExcludedReqs {
+                       s := new(modFileSummary)
+                       *s = *summary
+                       s.require = make([]module.Version, 0, len(summary.require))
                        for _, r := range summary.require {
                                if !index.exclude[r] {
-                                       nonExcluded = append(nonExcluded, r)
+                                       s.require = append(s.require, r)
                                }
                        }
-                       summary.require = nonExcluded
+                       summary = s
                }
-               return cached{summary, nil}
-       }).(cached)
-
-       return c.summary, c.err
+       }
+       return summary, nil
 }
 
-var goModSummaryCache par.Cache // module.Version → goModSummary result
-
 // rawGoModSummary returns a new summary of the go.mod file for module m,
 // ignoring all replacements that may apply to m and excludes that may apply to
 // its dependencies.
@@ -469,62 +481,72 @@ func rawGoModSummary(m module.Version) (*modFileSummary, error) {
                panic("internal error: rawGoModSummary called on the Target module")
        }
 
-       summary := new(modFileSummary)
-       var f *modfile.File
-       if m.Version == "" {
-               // m is a replacement module with only a file path.
-               dir := m.Path
-               if !filepath.IsAbs(dir) {
-                       dir = filepath.Join(ModRoot(), dir)
-               }
-               gomod := filepath.Join(dir, "go.mod")
+       type cached struct {
+               summary *modFileSummary
+               err     error
+       }
+       c := rawGoModSummaryCache.Do(m, func() interface{} {
+               summary := new(modFileSummary)
+               var f *modfile.File
+               if m.Version == "" {
+                       // m is a replacement module with only a file path.
+                       dir := m.Path
+                       if !filepath.IsAbs(dir) {
+                               dir = filepath.Join(ModRoot(), dir)
+                       }
+                       gomod := filepath.Join(dir, "go.mod")
 
-               data, err := lockedfile.Read(gomod)
-               if err != nil {
-                       return nil, module.VersionError(m, fmt.Errorf("reading %s: %v", base.ShortPath(gomod), err))
-               }
-               f, err = modfile.ParseLax(gomod, data, nil)
-               if err != nil {
-                       return nil, module.VersionError(m, fmt.Errorf("parsing %s: %v", base.ShortPath(gomod), err))
-               }
-       } else {
-               if !semver.IsValid(m.Version) {
-                       // Disallow the broader queries supported by fetch.Lookup.
-                       base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version)
+                       data, err := lockedfile.Read(gomod)
+                       if err != nil {
+                               return cached{nil, module.VersionError(m, fmt.Errorf("reading %s: %v", base.ShortPath(gomod), err))}
+                       }
+                       f, err = modfile.ParseLax(gomod, data, nil)
+                       if err != nil {
+                               return cached{nil, module.VersionError(m, fmt.Errorf("parsing %s: %v", base.ShortPath(gomod), err))}
+                       }
+               } else {
+                       if !semver.IsValid(m.Version) {
+                               // Disallow the broader queries supported by fetch.Lookup.
+                               base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version)
+                       }
+
+                       data, err := modfetch.GoMod(m.Path, m.Version)
+                       if err != nil {
+                               return cached{nil, err}
+                       }
+                       f, err = modfile.ParseLax("go.mod", data, nil)
+                       if err != nil {
+                               return cached{nil, module.VersionError(m, fmt.Errorf("parsing go.mod: %v", err))}
+                       }
                }
 
-               data, err := modfetch.GoMod(m.Path, m.Version)
-               if err != nil {
-                       return nil, err
+               if f.Module != nil {
+                       summary.module = f.Module.Mod
                }
-               f, err = modfile.ParseLax("go.mod", data, nil)
-               if err != nil {
-                       return nil, module.VersionError(m, fmt.Errorf("parsing go.mod: %v", err))
+               if f.Go != nil && f.Go.Version != "" {
+                       rawGoVersion.LoadOrStore(m, f.Go.Version)
+                       summary.goVersionV = "v" + f.Go.Version
                }
-       }
-
-       if f.Module != nil {
-               summary.module = f.Module.Mod
-       }
-       if f.Go != nil && f.Go.Version != "" {
-               rawGoVersion.LoadOrStore(m, f.Go.Version)
-               summary.goVersionV = "v" + f.Go.Version
-       }
-       if len(f.Require) > 0 {
-               summary.require = make([]module.Version, 0, len(f.Require))
-               for _, req := range f.Require {
-                       summary.require = append(summary.require, req.Mod)
+               if len(f.Require) > 0 {
+                       summary.require = make([]module.Version, 0, len(f.Require))
+                       for _, req := range f.Require {
+                               summary.require = append(summary.require, req.Mod)
+                       }
                }
-       }
-       if len(f.Retract) > 0 {
-               summary.retract = make([]retraction, 0, len(f.Retract))
-               for _, ret := range f.Retract {
-                       summary.retract = append(summary.retract, retraction{
-                               VersionInterval: ret.VersionInterval,
-                               Rationale:       ret.Rationale,
-                       })
+               if len(f.Retract) > 0 {
+                       summary.retract = make([]retraction, 0, len(f.Retract))
+                       for _, ret := range f.Retract {
+                               summary.retract = append(summary.retract, retraction{
+                                       VersionInterval: ret.VersionInterval,
+                                       Rationale:       ret.Rationale,
+                               })
+                       }
                }
-       }
 
-       return summary, nil
+               return cached{summary, nil}
+       }).(cached)
+
+       return c.summary, c.err
 }
+
+var rawGoModSummaryCache par.Cache // module.Version → rawGoModSummary result
diff --git a/src/cmd/go/testdata/mod/example.com_retract_rename_v1.0.0-bad.txt b/src/cmd/go/testdata/mod/example.com_retract_rename_v1.0.0-bad.txt
new file mode 100644 (file)
index 0000000..4936475
--- /dev/null
@@ -0,0 +1,10 @@
+Module example.com/retract/rename is renamed in a later version.
+
+This happens frequently when a repository is renamed or when a go.mod file
+is added for the first time with a custom module path.
+-- .info --
+{"Version":"v1.0.0-bad"}
+-- .mod --
+module example.com/retract/rename
+
+go 1.16
diff --git a/src/cmd/go/testdata/mod/example.com_retract_rename_v1.9.0-new.txt b/src/cmd/go/testdata/mod/example.com_retract_rename_v1.9.0-new.txt
new file mode 100644 (file)
index 0000000..fcbdfda
--- /dev/null
@@ -0,0 +1,13 @@
+Module example.com/retract/rename is renamed in this version.
+
+This happens frequently when a repository is renamed or when a go.mod file
+is added for the first time with a custom module path.
+-- .info --
+{"Version":"v1.9.0-new"}
+-- .mod --
+module example.com/retract/newname
+
+go 1.16
+
+// bad
+retract v1.0.0-bad
diff --git a/src/cmd/go/testdata/script/mod_retract_rename.txt b/src/cmd/go/testdata/script/mod_retract_rename.txt
new file mode 100644 (file)
index 0000000..b75bfe9
--- /dev/null
@@ -0,0 +1,28 @@
+# Populate go.sum.
+go get -d
+
+# 'go list -m -retracted' should load retractions, even if the version
+# containing retractions has a different module path.
+go list -m -retracted -f '{{with .Retracted}}retracted{{end}}' example.com/retract/rename
+
+# 'go list -m -u' should load retractions, too.
+go list -m -u -f '{{with .Retracted}}retracted{{end}}' example.com/retract/rename
+
+# 'go get' should warn about the retracted version.
+go get -d
+stderr '^go: warning: example.com/retract/rename@v1.0.0-bad is retracted: bad$'
+
+# We can't upgrade, since this latest version has a different module path.
+! go get -d example.com/retract/rename
+stderr 'module declares its path as: example.com/retract/newname'
+
+-- go.mod --
+module example.com/use
+
+go 1.16
+
+require example.com/retract/rename v1.0.0-bad
+-- use.go --
+package use
+
+import _ "example.com/retract/rename"