Black Lives Matter. Support the Equal Justice Initiative.

Source file src/cmd/go/internal/modload/query.go

Documentation: cmd/go/internal/modload

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package modload
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"io/fs"
    12  	"os"
    13  	pathpkg "path"
    14  	"path/filepath"
    15  	"sort"
    16  	"strings"
    17  	"sync"
    18  	"time"
    19  
    20  	"cmd/go/internal/cfg"
    21  	"cmd/go/internal/imports"
    22  	"cmd/go/internal/modfetch"
    23  	"cmd/go/internal/search"
    24  	"cmd/go/internal/str"
    25  	"cmd/go/internal/trace"
    26  
    27  	"golang.org/x/mod/module"
    28  	"golang.org/x/mod/semver"
    29  )
    30  
    31  // Query looks up a revision of a given module given a version query string.
    32  // The module must be a complete module path.
    33  // The version must take one of the following forms:
    34  //
    35  // - the literal string "latest", denoting the latest available, allowed
    36  //   tagged version, with non-prereleases preferred over prereleases.
    37  //   If there are no tagged versions in the repo, latest returns the most
    38  //   recent commit.
    39  // - the literal string "upgrade", equivalent to "latest" except that if
    40  //   current is a newer version, current will be returned (see below).
    41  // - the literal string "patch", denoting the latest available tagged version
    42  //   with the same major and minor number as current (see below).
    43  // - v1, denoting the latest available tagged version v1.x.x.
    44  // - v1.2, denoting the latest available tagged version v1.2.x.
    45  // - v1.2.3, a semantic version string denoting that tagged version.
    46  // - <v1.2.3, <=v1.2.3, >v1.2.3, >=v1.2.3,
    47  //   denoting the version closest to the target and satisfying the given operator,
    48  //   with non-prereleases preferred over prereleases.
    49  // - a repository commit identifier or tag, denoting that commit.
    50  //
    51  // current denotes the currently-selected version of the module; it may be
    52  // "none" if no version is currently selected, or "" if the currently-selected
    53  // version is unknown or should not be considered. If query is
    54  // "upgrade" or "patch", current will be returned if it is a newer
    55  // semantic version or a chronologically later pseudo-version than the
    56  // version that would otherwise be chosen. This prevents accidental downgrades
    57  // from newer pre-release or development versions.
    58  //
    59  // The allowed function (which may be nil) is used to filter out unsuitable
    60  // versions (see AllowedFunc documentation for details). If the query refers to
    61  // a specific revision (for example, "master"; see IsRevisionQuery), and the
    62  // revision is disallowed by allowed, Query returns the error. If the query
    63  // does not refer to a specific revision (for example, "latest"), Query
    64  // acts as if versions disallowed by allowed do not exist.
    65  //
    66  // If path is the path of the main module and the query is "latest",
    67  // Query returns Target.Version as the version.
    68  func Query(ctx context.Context, path, query, current string, allowed AllowedFunc) (*modfetch.RevInfo, error) {
    69  	var info *modfetch.RevInfo
    70  	err := modfetch.TryProxies(func(proxy string) (err error) {
    71  		info, err = queryProxy(ctx, proxy, path, query, current, allowed)
    72  		return err
    73  	})
    74  	return info, err
    75  }
    76  
    77  // AllowedFunc is used by Query and other functions to filter out unsuitable
    78  // versions, for example, those listed in exclude directives in the main
    79  // module's go.mod file.
    80  //
    81  // An AllowedFunc returns an error equivalent to ErrDisallowed for an unsuitable
    82  // version. Any other error indicates the function was unable to determine
    83  // whether the version should be allowed, for example, the function was unable
    84  // to fetch or parse a go.mod file containing retractions. Typically, errors
    85  // other than ErrDisallowd may be ignored.
    86  type AllowedFunc func(context.Context, module.Version) error
    87  
    88  var errQueryDisabled error = queryDisabledError{}
    89  
    90  type queryDisabledError struct{}
    91  
    92  func (queryDisabledError) Error() string {
    93  	if cfg.BuildModReason == "" {
    94  		return fmt.Sprintf("cannot query module due to -mod=%s", cfg.BuildMod)
    95  	}
    96  	return fmt.Sprintf("cannot query module due to -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason)
    97  }
    98  
    99  func queryProxy(ctx context.Context, proxy, path, query, current string, allowed AllowedFunc) (*modfetch.RevInfo, error) {
   100  	ctx, span := trace.StartSpan(ctx, "modload.queryProxy "+path+" "+query)
   101  	defer span.Done()
   102  
   103  	if current != "" && current != "none" && !semver.IsValid(current) {
   104  		return nil, fmt.Errorf("invalid previous version %q", current)
   105  	}
   106  	if cfg.BuildMod == "vendor" {
   107  		return nil, errQueryDisabled
   108  	}
   109  	if allowed == nil {
   110  		allowed = func(context.Context, module.Version) error { return nil }
   111  	}
   112  
   113  	if path == Target.Path && (query == "upgrade" || query == "patch") {
   114  		if err := allowed(ctx, Target); err != nil {
   115  			return nil, fmt.Errorf("internal error: main module version is not allowed: %w", err)
   116  		}
   117  		return &modfetch.RevInfo{Version: Target.Version}, nil
   118  	}
   119  
   120  	if path == "std" || path == "cmd" {
   121  		return nil, fmt.Errorf("can't query specific version (%q) of standard-library module %q", query, path)
   122  	}
   123  
   124  	repo, err := lookupRepo(proxy, path)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	// Parse query to detect parse errors (and possibly handle query)
   130  	// before any network I/O.
   131  	qm, err := newQueryMatcher(path, query, current, allowed)
   132  	if (err == nil && qm.canStat) || err == errRevQuery {
   133  		// Direct lookup of a commit identifier or complete (non-prefix) semantic
   134  		// version.
   135  
   136  		// If the identifier is not a canonical semver tag — including if it's a
   137  		// semver tag with a +metadata suffix — then modfetch.Stat will populate
   138  		// info.Version with a suitable pseudo-version.
   139  		info, err := repo.Stat(query)
   140  		if err != nil {
   141  			queryErr := err
   142  			// The full query doesn't correspond to a tag. If it is a semantic version
   143  			// with a +metadata suffix, see if there is a tag without that suffix:
   144  			// semantic versioning defines them to be equivalent.
   145  			canonicalQuery := module.CanonicalVersion(query)
   146  			if canonicalQuery != "" && query != canonicalQuery {
   147  				info, err = repo.Stat(canonicalQuery)
   148  				if err != nil && !errors.Is(err, fs.ErrNotExist) {
   149  					return info, err
   150  				}
   151  			}
   152  			if err != nil {
   153  				return nil, queryErr
   154  			}
   155  		}
   156  		if err := allowed(ctx, module.Version{Path: path, Version: info.Version}); errors.Is(err, ErrDisallowed) {
   157  			return nil, err
   158  		}
   159  		return info, nil
   160  	} else if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	// Load versions and execute query.
   165  	versions, err := repo.Versions(qm.prefix)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	releases, prereleases, err := qm.filterVersions(ctx, versions)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	lookup := func(v string) (*modfetch.RevInfo, error) {
   175  		rev, err := repo.Stat(v)
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  
   180  		if (query == "upgrade" || query == "patch") && modfetch.IsPseudoVersion(current) && !rev.Time.IsZero() {
   181  			// Don't allow "upgrade" or "patch" to move from a pseudo-version
   182  			// to a chronologically older version or pseudo-version.
   183  			//
   184  			// If the current version is a pseudo-version from an untagged branch, it
   185  			// may be semantically lower than the "latest" release or the latest
   186  			// pseudo-version on the main branch. A user on such a version is unlikely
   187  			// to intend to “upgrade” to a version that already existed at that point
   188  			// in time.
   189  			//
   190  			// We do this only if the current version is a pseudo-version: if the
   191  			// version is tagged, the author of the dependency module has given us
   192  			// explicit information about their intended precedence of this version
   193  			// relative to other versions, and we shouldn't contradict that
   194  			// information. (For example, v1.0.1 might be a backport of a fix already
   195  			// incorporated into v1.1.0, in which case v1.0.1 would be chronologically
   196  			// newer but v1.1.0 is still an “upgrade”; or v1.0.2 might be a revert of
   197  			// an unsuccessful fix in v1.0.1, in which case the v1.0.2 commit may be
   198  			// older than the v1.0.1 commit despite the tag itself being newer.)
   199  			currentTime, err := modfetch.PseudoVersionTime(current)
   200  			if err == nil && rev.Time.Before(currentTime) {
   201  				if err := allowed(ctx, module.Version{Path: path, Version: current}); errors.Is(err, ErrDisallowed) {
   202  					return nil, err
   203  				}
   204  				return repo.Stat(current)
   205  			}
   206  		}
   207  
   208  		return rev, nil
   209  	}
   210  
   211  	if qm.preferLower {
   212  		if len(releases) > 0 {
   213  			return lookup(releases[0])
   214  		}
   215  		if len(prereleases) > 0 {
   216  			return lookup(prereleases[0])
   217  		}
   218  	} else {
   219  		if len(releases) > 0 {
   220  			return lookup(releases[len(releases)-1])
   221  		}
   222  		if len(prereleases) > 0 {
   223  			return lookup(prereleases[len(prereleases)-1])
   224  		}
   225  	}
   226  
   227  	if qm.mayUseLatest {
   228  		latest, err := repo.Latest()
   229  		if err == nil {
   230  			if qm.allowsVersion(ctx, latest.Version) {
   231  				return lookup(latest.Version)
   232  			}
   233  		} else if !errors.Is(err, fs.ErrNotExist) {
   234  			return nil, err
   235  		}
   236  	}
   237  
   238  	if (query == "upgrade" || query == "patch") && current != "" && current != "none" {
   239  		// "upgrade" and "patch" may stay on the current version if allowed.
   240  		if err := allowed(ctx, module.Version{Path: path, Version: current}); errors.Is(err, ErrDisallowed) {
   241  			return nil, err
   242  		}
   243  		return lookup(current)
   244  	}
   245  
   246  	return nil, &NoMatchingVersionError{query: query, current: current}
   247  }
   248  
   249  // IsRevisionQuery returns true if vers is a version query that may refer to
   250  // a particular version or revision in a repository like "v1.0.0", "master",
   251  // or "0123abcd". IsRevisionQuery returns false if vers is a query that
   252  // chooses from among available versions like "latest" or ">v1.0.0".
   253  func IsRevisionQuery(vers string) bool {
   254  	if vers == "latest" ||
   255  		vers == "upgrade" ||
   256  		vers == "patch" ||
   257  		strings.HasPrefix(vers, "<") ||
   258  		strings.HasPrefix(vers, ">") ||
   259  		(semver.IsValid(vers) && isSemverPrefix(vers)) {
   260  		return false
   261  	}
   262  	return true
   263  }
   264  
   265  // isSemverPrefix reports whether v is a semantic version prefix: v1 or v1.2 (not v1.2.3).
   266  // The caller is assumed to have checked that semver.IsValid(v) is true.
   267  func isSemverPrefix(v string) bool {
   268  	dots := 0
   269  	for i := 0; i < len(v); i++ {
   270  		switch v[i] {
   271  		case '-', '+':
   272  			return false
   273  		case '.':
   274  			dots++
   275  			if dots >= 2 {
   276  				return false
   277  			}
   278  		}
   279  	}
   280  	return true
   281  }
   282  
   283  type queryMatcher struct {
   284  	path               string
   285  	prefix             string
   286  	filter             func(version string) bool
   287  	allowed            AllowedFunc
   288  	canStat            bool // if true, the query can be resolved by repo.Stat
   289  	preferLower        bool // if true, choose the lowest matching version
   290  	mayUseLatest       bool
   291  	preferIncompatible bool
   292  }
   293  
   294  var errRevQuery = errors.New("query refers to a non-semver revision")
   295  
   296  // newQueryMatcher returns a new queryMatcher that matches the versions
   297  // specified by the given query on the module with the given path.
   298  //
   299  // If the query can only be resolved by statting a non-SemVer revision,
   300  // newQueryMatcher returns errRevQuery.
   301  func newQueryMatcher(path string, query, current string, allowed AllowedFunc) (*queryMatcher, error) {
   302  	badVersion := func(v string) (*queryMatcher, error) {
   303  		return nil, fmt.Errorf("invalid semantic version %q in range %q", v, query)
   304  	}
   305  
   306  	matchesMajor := func(v string) bool {
   307  		_, pathMajor, ok := module.SplitPathVersion(path)
   308  		if !ok {
   309  			return false
   310  		}
   311  		return module.CheckPathMajor(v, pathMajor) == nil
   312  	}
   313  
   314  	qm := &queryMatcher{
   315  		path:               path,
   316  		allowed:            allowed,
   317  		preferIncompatible: strings.HasSuffix(current, "+incompatible"),
   318  	}
   319  
   320  	switch {
   321  	case query == "latest":
   322  		qm.mayUseLatest = true
   323  
   324  	case query == "upgrade":
   325  		if current == "" || current == "none" {
   326  			qm.mayUseLatest = true
   327  		} else {
   328  			qm.mayUseLatest = modfetch.IsPseudoVersion(current)
   329  			qm.filter = func(mv string) bool { return semver.Compare(mv, current) >= 0 }
   330  		}
   331  
   332  	case query == "patch":
   333  		if current == "none" {
   334  			return nil, &NoPatchBaseError{path}
   335  		}
   336  		if current == "" {
   337  			qm.mayUseLatest = true
   338  		} else {
   339  			qm.mayUseLatest = modfetch.IsPseudoVersion(current)
   340  			qm.prefix = semver.MajorMinor(current) + "."
   341  			qm.filter = func(mv string) bool { return semver.Compare(mv, current) >= 0 }
   342  		}
   343  
   344  	case strings.HasPrefix(query, "<="):
   345  		v := query[len("<="):]
   346  		if !semver.IsValid(v) {
   347  			return badVersion(v)
   348  		}
   349  		if isSemverPrefix(v) {
   350  			// Refuse to say whether <=v1.2 allows v1.2.3 (remember, @v1.2 might mean v1.2.3).
   351  			return nil, fmt.Errorf("ambiguous semantic version %q in range %q", v, query)
   352  		}
   353  		qm.filter = func(mv string) bool { return semver.Compare(mv, v) <= 0 }
   354  		if !matchesMajor(v) {
   355  			qm.preferIncompatible = true
   356  		}
   357  
   358  	case strings.HasPrefix(query, "<"):
   359  		v := query[len("<"):]
   360  		if !semver.IsValid(v) {
   361  			return badVersion(v)
   362  		}
   363  		qm.filter = func(mv string) bool { return semver.Compare(mv, v) < 0 }
   364  		if !matchesMajor(v) {
   365  			qm.preferIncompatible = true
   366  		}
   367  
   368  	case strings.HasPrefix(query, ">="):
   369  		v := query[len(">="):]
   370  		if !semver.IsValid(v) {
   371  			return badVersion(v)
   372  		}
   373  		qm.filter = func(mv string) bool { return semver.Compare(mv, v) >= 0 }
   374  		qm.preferLower = true
   375  		if !matchesMajor(v) {
   376  			qm.preferIncompatible = true
   377  		}
   378  
   379  	case strings.HasPrefix(query, ">"):
   380  		v := query[len(">"):]
   381  		if !semver.IsValid(v) {
   382  			return badVersion(v)
   383  		}
   384  		if isSemverPrefix(v) {
   385  			// Refuse to say whether >v1.2 allows v1.2.3 (remember, @v1.2 might mean v1.2.3).
   386  			return nil, fmt.Errorf("ambiguous semantic version %q in range %q", v, query)
   387  		}
   388  		qm.filter = func(mv string) bool { return semver.Compare(mv, v) > 0 }
   389  		qm.preferLower = true
   390  		if !matchesMajor(v) {
   391  			qm.preferIncompatible = true
   392  		}
   393  
   394  	case semver.IsValid(query):
   395  		if isSemverPrefix(query) {
   396  			qm.prefix = query + "."
   397  			// Do not allow the query "v1.2" to match versions lower than "v1.2.0",
   398  			// such as prereleases for that version. (https://golang.org/issue/31972)
   399  			qm.filter = func(mv string) bool { return semver.Compare(mv, query) >= 0 }
   400  		} else {
   401  			qm.canStat = true
   402  			qm.filter = func(mv string) bool { return semver.Compare(mv, query) == 0 }
   403  			qm.prefix = semver.Canonical(query)
   404  		}
   405  		if !matchesMajor(query) {
   406  			qm.preferIncompatible = true
   407  		}
   408  
   409  	default:
   410  		return nil, errRevQuery
   411  	}
   412  
   413  	return qm, nil
   414  }
   415  
   416  // allowsVersion reports whether version v is allowed by the prefix, filter, and
   417  // AllowedFunc of qm.
   418  func (qm *queryMatcher) allowsVersion(ctx context.Context, v string) bool {
   419  	if qm.prefix != "" && !strings.HasPrefix(v, qm.prefix) {
   420  		return false
   421  	}
   422  	if qm.filter != nil && !qm.filter(v) {
   423  		return false
   424  	}
   425  	if qm.allowed != nil {
   426  		if err := qm.allowed(ctx, module.Version{Path: qm.path, Version: v}); errors.Is(err, ErrDisallowed) {
   427  			return false
   428  		}
   429  	}
   430  	return true
   431  }
   432  
   433  // filterVersions classifies versions into releases and pre-releases, filtering
   434  // out:
   435  // 	1. versions that do not satisfy the 'allowed' predicate, and
   436  // 	2. "+incompatible" versions, if a compatible one satisfies the predicate
   437  // 	   and the incompatible version is not preferred.
   438  //
   439  // If the allowed predicate returns an error not equivalent to ErrDisallowed,
   440  // filterVersions returns that error.
   441  func (qm *queryMatcher) filterVersions(ctx context.Context, versions []string) (releases, prereleases []string, err error) {
   442  	needIncompatible := qm.preferIncompatible
   443  
   444  	var lastCompatible string
   445  	for _, v := range versions {
   446  		if !qm.allowsVersion(ctx, v) {
   447  			continue
   448  		}
   449  
   450  		if !needIncompatible {
   451  			// We're not yet sure whether we need to include +incomptaible versions.
   452  			// Keep track of the last compatible version we've seen, and use the
   453  			// presence (or absence) of a go.mod file in that version to decide: a
   454  			// go.mod file implies that the module author is supporting modules at a
   455  			// compatible version (and we should ignore +incompatible versions unless
   456  			// requested explicitly), while a lack of go.mod file implies the
   457  			// potential for legacy (pre-modules) versioning without semantic import
   458  			// paths (and thus *with* +incompatible versions).
   459  			//
   460  			// This isn't strictly accurate if the latest compatible version has been
   461  			// replaced by a local file path, because we do not allow file-path
   462  			// replacements without a go.mod file: the user would have needed to add
   463  			// one. However, replacing the last compatible version while
   464  			// simultaneously expecting to upgrade implicitly to a +incompatible
   465  			// version seems like an extreme enough corner case to ignore for now.
   466  
   467  			if !strings.HasSuffix(v, "+incompatible") {
   468  				lastCompatible = v
   469  			} else if lastCompatible != "" {
   470  				// If the latest compatible version is allowed and has a go.mod file,
   471  				// ignore any version with a higher (+incompatible) major version. (See
   472  				// https://golang.org/issue/34165.) Note that we even prefer a
   473  				// compatible pre-release over an incompatible release.
   474  				ok, err := versionHasGoMod(ctx, module.Version{Path: qm.path, Version: lastCompatible})
   475  				if err != nil {
   476  					return nil, nil, err
   477  				}
   478  				if ok {
   479  					// The last compatible version has a go.mod file, so that's the
   480  					// highest version we're willing to consider. Don't bother even
   481  					// looking at higher versions, because they're all +incompatible from
   482  					// here onward.
   483  					break
   484  				}
   485  
   486  				// No acceptable compatible release has a go.mod file, so the versioning
   487  				// for the module might not be module-aware, and we should respect
   488  				// legacy major-version tags.
   489  				needIncompatible = true
   490  			}
   491  		}
   492  
   493  		if semver.Prerelease(v) != "" {
   494  			prereleases = append(prereleases, v)
   495  		} else {
   496  			releases = append(releases, v)
   497  		}
   498  	}
   499  
   500  	return releases, prereleases, nil
   501  }
   502  
   503  type QueryResult struct {
   504  	Mod      module.Version
   505  	Rev      *modfetch.RevInfo
   506  	Packages []string
   507  }
   508  
   509  // QueryPackages is like QueryPattern, but requires that the pattern match at
   510  // least one package and omits the non-package result (if any).
   511  func QueryPackages(ctx context.Context, pattern, query string, current func(string) string, allowed AllowedFunc) ([]QueryResult, error) {
   512  	pkgMods, modOnly, err := QueryPattern(ctx, pattern, query, current, allowed)
   513  
   514  	if len(pkgMods) == 0 && err == nil {
   515  		return nil, &PackageNotInModuleError{
   516  			Mod:         modOnly.Mod,
   517  			Replacement: Replacement(modOnly.Mod),
   518  			Query:       query,
   519  			Pattern:     pattern,
   520  		}
   521  	}
   522  
   523  	return pkgMods, err
   524  }
   525  
   526  // QueryPattern looks up the module(s) containing at least one package matching
   527  // the given pattern at the given version. The results are sorted by module path
   528  // length in descending order. If any proxy provides a non-empty set of candidate
   529  // modules, no further proxies are tried.
   530  //
   531  // For wildcard patterns, QueryPattern looks in modules with package paths up to
   532  // the first "..." in the pattern. For the pattern "example.com/a/b.../c",
   533  // QueryPattern would consider prefixes of "example.com/a".
   534  //
   535  // If any matching package is in the main module, QueryPattern considers only
   536  // the main module and only the version "latest", without checking for other
   537  // possible modules.
   538  //
   539  // QueryPattern always returns at least one QueryResult (which may be only
   540  // modOnly) or a non-nil error.
   541  func QueryPattern(ctx context.Context, pattern, query string, current func(string) string, allowed AllowedFunc) (pkgMods []QueryResult, modOnly *QueryResult, err error) {
   542  	ctx, span := trace.StartSpan(ctx, "modload.QueryPattern "+pattern+" "+query)
   543  	defer span.Done()
   544  
   545  	base := pattern
   546  
   547  	firstError := func(m *search.Match) error {
   548  		if len(m.Errs) == 0 {
   549  			return nil
   550  		}
   551  		return m.Errs[0]
   552  	}
   553  
   554  	var match func(mod module.Version, root string, isLocal bool) *search.Match
   555  	matchPattern := search.MatchPattern(pattern)
   556  
   557  	if i := strings.Index(pattern, "..."); i >= 0 {
   558  		base = pathpkg.Dir(pattern[:i+3])
   559  		if base == "." {
   560  			return nil, nil, &WildcardInFirstElementError{Pattern: pattern, Query: query}
   561  		}
   562  		match = func(mod module.Version, root string, isLocal bool) *search.Match {
   563  			m := search.NewMatch(pattern)
   564  			matchPackages(ctx, m, imports.AnyTags(), omitStd, []module.Version{mod})
   565  			return m
   566  		}
   567  	} else {
   568  		match = func(mod module.Version, root string, isLocal bool) *search.Match {
   569  			m := search.NewMatch(pattern)
   570  			prefix := mod.Path
   571  			if mod == Target {
   572  				prefix = targetPrefix
   573  			}
   574  			if _, ok, err := dirInModule(pattern, prefix, root, isLocal); err != nil {
   575  				m.AddError(err)
   576  			} else if ok {
   577  				m.Pkgs = []string{pattern}
   578  			}
   579  			return m
   580  		}
   581  	}
   582  
   583  	var queryMatchesMainModule bool
   584  	if HasModRoot() {
   585  		m := match(Target, modRoot, true)
   586  		if len(m.Pkgs) > 0 {
   587  			if query != "upgrade" && query != "patch" {
   588  				return nil, nil, &QueryMatchesPackagesInMainModuleError{
   589  					Pattern:  pattern,
   590  					Query:    query,
   591  					Packages: m.Pkgs,
   592  				}
   593  			}
   594  			if err := allowed(ctx, Target); err != nil {
   595  				return nil, nil, fmt.Errorf("internal error: package %s is in the main module (%s), but version is not allowed: %w", pattern, Target.Path, err)
   596  			}
   597  			return []QueryResult{{
   598  				Mod:      Target,
   599  				Rev:      &modfetch.RevInfo{Version: Target.Version},
   600  				Packages: m.Pkgs,
   601  			}}, nil, nil
   602  		}
   603  		if err := firstError(m); err != nil {
   604  			return nil, nil, err
   605  		}
   606  
   607  		if matchPattern(Target.Path) {
   608  			queryMatchesMainModule = true
   609  		}
   610  
   611  		if (query == "upgrade" || query == "patch") && queryMatchesMainModule {
   612  			if err := allowed(ctx, Target); err == nil {
   613  				modOnly = &QueryResult{
   614  					Mod: Target,
   615  					Rev: &modfetch.RevInfo{Version: Target.Version},
   616  				}
   617  			}
   618  		}
   619  	}
   620  
   621  	var (
   622  		results          []QueryResult
   623  		candidateModules = modulePrefixesExcludingTarget(base)
   624  	)
   625  	if len(candidateModules) == 0 {
   626  		if modOnly != nil {
   627  			return nil, modOnly, nil
   628  		} else if queryMatchesMainModule {
   629  			return nil, nil, &QueryMatchesMainModuleError{
   630  				Pattern: pattern,
   631  				Query:   query,
   632  			}
   633  		} else {
   634  			return nil, nil, &PackageNotInModuleError{
   635  				Mod:     Target,
   636  				Query:   query,
   637  				Pattern: pattern,
   638  			}
   639  		}
   640  	}
   641  
   642  	err = modfetch.TryProxies(func(proxy string) error {
   643  		queryModule := func(ctx context.Context, path string) (r QueryResult, err error) {
   644  			ctx, span := trace.StartSpan(ctx, "modload.QueryPattern.queryModule ["+proxy+"] "+path)
   645  			defer span.Done()
   646  
   647  			pathCurrent := current(path)
   648  			r.Mod.Path = path
   649  			r.Rev, err = queryProxy(ctx, proxy, path, query, pathCurrent, allowed)
   650  			if err != nil {
   651  				return r, err
   652  			}
   653  			r.Mod.Version = r.Rev.Version
   654  			needSum := true
   655  			root, isLocal, err := fetch(ctx, r.Mod, needSum)
   656  			if err != nil {
   657  				return r, err
   658  			}
   659  			m := match(r.Mod, root, isLocal)
   660  			r.Packages = m.Pkgs
   661  			if len(r.Packages) == 0 && !matchPattern(path) {
   662  				if err := firstError(m); err != nil {
   663  					return r, err
   664  				}
   665  				return r, &PackageNotInModuleError{
   666  					Mod:         r.Mod,
   667  					Replacement: Replacement(r.Mod),
   668  					Query:       query,
   669  					Pattern:     pattern,
   670  				}
   671  			}
   672  			return r, nil
   673  		}
   674  
   675  		allResults, err := queryPrefixModules(ctx, candidateModules, queryModule)
   676  		results = allResults[:0]
   677  		for _, r := range allResults {
   678  			if len(r.Packages) == 0 {
   679  				modOnly = &r
   680  			} else {
   681  				results = append(results, r)
   682  			}
   683  		}
   684  		return err
   685  	})
   686  
   687  	if queryMatchesMainModule && len(results) == 0 && modOnly == nil && errors.Is(err, fs.ErrNotExist) {
   688  		return nil, nil, &QueryMatchesMainModuleError{
   689  			Pattern: pattern,
   690  			Query:   query,
   691  		}
   692  	}
   693  	return results[:len(results):len(results)], modOnly, err
   694  }
   695  
   696  // modulePrefixesExcludingTarget returns all prefixes of path that may plausibly
   697  // exist as a module, excluding targetPrefix but otherwise including path
   698  // itself, sorted by descending length.
   699  func modulePrefixesExcludingTarget(path string) []string {
   700  	prefixes := make([]string, 0, strings.Count(path, "/")+1)
   701  
   702  	for {
   703  		if path != targetPrefix {
   704  			if _, _, ok := module.SplitPathVersion(path); ok {
   705  				prefixes = append(prefixes, path)
   706  			}
   707  		}
   708  
   709  		j := strings.LastIndexByte(path, '/')
   710  		if j < 0 {
   711  			break
   712  		}
   713  		path = path[:j]
   714  	}
   715  
   716  	return prefixes
   717  }
   718  
   719  func queryPrefixModules(ctx context.Context, candidateModules []string, queryModule func(ctx context.Context, path string) (QueryResult, error)) (found []QueryResult, err error) {
   720  	ctx, span := trace.StartSpan(ctx, "modload.queryPrefixModules")
   721  	defer span.Done()
   722  
   723  	// If the path we're attempting is not in the module cache and we don't have a
   724  	// fetch result cached either, we'll end up making a (potentially slow)
   725  	// request to the proxy or (often even slower) the origin server.
   726  	// To minimize latency, execute all of those requests in parallel.
   727  	type result struct {
   728  		QueryResult
   729  		err error
   730  	}
   731  	results := make([]result, len(candidateModules))
   732  	var wg sync.WaitGroup
   733  	wg.Add(len(candidateModules))
   734  	for i, p := range candidateModules {
   735  		ctx := trace.StartGoroutine(ctx)
   736  		go func(p string, r *result) {
   737  			r.QueryResult, r.err = queryModule(ctx, p)
   738  			wg.Done()
   739  		}(p, &results[i])
   740  	}
   741  	wg.Wait()
   742  
   743  	// Classify the results. In case of failure, identify the error that the user
   744  	// is most likely to find helpful: the most useful class of error at the
   745  	// longest matching path.
   746  	var (
   747  		noPackage   *PackageNotInModuleError
   748  		noVersion   *NoMatchingVersionError
   749  		noPatchBase *NoPatchBaseError
   750  		notExistErr error
   751  	)
   752  	for _, r := range results {
   753  		switch rErr := r.err.(type) {
   754  		case nil:
   755  			found = append(found, r.QueryResult)
   756  		case *PackageNotInModuleError:
   757  			// Given the option, prefer to attribute “package not in module”
   758  			// to modules other than the main one.
   759  			if noPackage == nil || noPackage.Mod == Target {
   760  				noPackage = rErr
   761  			}
   762  		case *NoMatchingVersionError:
   763  			if noVersion == nil {
   764  				noVersion = rErr
   765  			}
   766  		case *NoPatchBaseError:
   767  			if noPatchBase == nil {
   768  				noPatchBase = rErr
   769  			}
   770  		default:
   771  			if errors.Is(rErr, fs.ErrNotExist) {
   772  				if notExistErr == nil {
   773  					notExistErr = rErr
   774  				}
   775  			} else if err == nil {
   776  				if len(found) > 0 || noPackage != nil {
   777  					// golang.org/issue/34094: If we have already found a module that
   778  					// could potentially contain the target package, ignore unclassified
   779  					// errors for modules with shorter paths.
   780  
   781  					// golang.org/issue/34383 is a special case of this: if we have
   782  					// already found example.com/foo/v2@v2.0.0 with a matching go.mod
   783  					// file, ignore the error from example.com/foo@v2.0.0.
   784  				} else {
   785  					err = r.err
   786  				}
   787  			}
   788  		}
   789  	}
   790  
   791  	// TODO(#26232): If len(found) == 0 and some of the errors are 4xx HTTP
   792  	// codes, have the auth package recheck the failed paths.
   793  	// If we obtain new credentials for any of them, re-run the above loop.
   794  
   795  	if len(found) == 0 && err == nil {
   796  		switch {
   797  		case noPackage != nil:
   798  			err = noPackage
   799  		case noVersion != nil:
   800  			err = noVersion
   801  		case noPatchBase != nil:
   802  			err = noPatchBase
   803  		case notExistErr != nil:
   804  			err = notExistErr
   805  		default:
   806  			panic("queryPrefixModules: no modules found, but no error detected")
   807  		}
   808  	}
   809  
   810  	return found, err
   811  }
   812  
   813  // A NoMatchingVersionError indicates that Query found a module at the requested
   814  // path, but not at any versions satisfying the query string and allow-function.
   815  //
   816  // NOTE: NoMatchingVersionError MUST NOT implement Is(fs.ErrNotExist).
   817  //
   818  // If the module came from a proxy, that proxy had to return a successful status
   819  // code for the versions it knows about, and thus did not have the opportunity
   820  // to return a non-400 status code to suppress fallback.
   821  type NoMatchingVersionError struct {
   822  	query, current string
   823  }
   824  
   825  func (e *NoMatchingVersionError) Error() string {
   826  	currentSuffix := ""
   827  	if (e.query == "upgrade" || e.query == "patch") && e.current != "" && e.current != "none" {
   828  		currentSuffix = fmt.Sprintf(" (current version is %s)", e.current)
   829  	}
   830  	return fmt.Sprintf("no matching versions for query %q", e.query) + currentSuffix
   831  }
   832  
   833  // A NoPatchBaseError indicates that Query was called with the query "patch"
   834  // but with a current version of "" or "none".
   835  type NoPatchBaseError struct {
   836  	path string
   837  }
   838  
   839  func (e *NoPatchBaseError) Error() string {
   840  	return fmt.Sprintf(`can't query version "patch" of module %s: no existing version is required`, e.path)
   841  }
   842  
   843  // A WildcardInFirstElementError indicates that a pattern passed to QueryPattern
   844  // had a wildcard in its first path element, and therefore had no pattern-prefix
   845  // modules to search in.
   846  type WildcardInFirstElementError struct {
   847  	Pattern string
   848  	Query   string
   849  }
   850  
   851  func (e *WildcardInFirstElementError) Error() string {
   852  	return fmt.Sprintf("no modules to query for %s@%s because first path element contains a wildcard", e.Pattern, e.Query)
   853  }
   854  
   855  // A PackageNotInModuleError indicates that QueryPattern found a candidate
   856  // module at the requested version, but that module did not contain any packages
   857  // matching the requested pattern.
   858  //
   859  // NOTE: PackageNotInModuleError MUST NOT implement Is(fs.ErrNotExist).
   860  //
   861  // If the module came from a proxy, that proxy had to return a successful status
   862  // code for the versions it knows about, and thus did not have the opportunity
   863  // to return a non-400 status code to suppress fallback.
   864  type PackageNotInModuleError struct {
   865  	Mod         module.Version
   866  	Replacement module.Version
   867  	Query       string
   868  	Pattern     string
   869  }
   870  
   871  func (e *PackageNotInModuleError) Error() string {
   872  	if e.Mod == Target {
   873  		if strings.Contains(e.Pattern, "...") {
   874  			return fmt.Sprintf("main module (%s) does not contain packages matching %s", Target.Path, e.Pattern)
   875  		}
   876  		return fmt.Sprintf("main module (%s) does not contain package %s", Target.Path, e.Pattern)
   877  	}
   878  
   879  	found := ""
   880  	if r := e.Replacement; r.Path != "" {
   881  		replacement := r.Path
   882  		if r.Version != "" {
   883  			replacement = fmt.Sprintf("%s@%s", r.Path, r.Version)
   884  		}
   885  		if e.Query == e.Mod.Version {
   886  			found = fmt.Sprintf(" (replaced by %s)", replacement)
   887  		} else {
   888  			found = fmt.Sprintf(" (%s, replaced by %s)", e.Mod.Version, replacement)
   889  		}
   890  	} else if e.Query != e.Mod.Version {
   891  		found = fmt.Sprintf(" (%s)", e.Mod.Version)
   892  	}
   893  
   894  	if strings.Contains(e.Pattern, "...") {
   895  		return fmt.Sprintf("module %s@%s found%s, but does not contain packages matching %s", e.Mod.Path, e.Query, found, e.Pattern)
   896  	}
   897  	return fmt.Sprintf("module %s@%s found%s, but does not contain package %s", e.Mod.Path, e.Query, found, e.Pattern)
   898  }
   899  
   900  func (e *PackageNotInModuleError) ImportPath() string {
   901  	if !strings.Contains(e.Pattern, "...") {
   902  		return e.Pattern
   903  	}
   904  	return ""
   905  }
   906  
   907  // ModuleHasRootPackage returns whether module m contains a package m.Path.
   908  func ModuleHasRootPackage(ctx context.Context, m module.Version) (bool, error) {
   909  	needSum := false
   910  	root, isLocal, err := fetch(ctx, m, needSum)
   911  	if err != nil {
   912  		return false, err
   913  	}
   914  	_, ok, err := dirInModule(m.Path, m.Path, root, isLocal)
   915  	return ok, err
   916  }
   917  
   918  func versionHasGoMod(ctx context.Context, m module.Version) (bool, error) {
   919  	needSum := false
   920  	root, _, err := fetch(ctx, m, needSum)
   921  	if err != nil {
   922  		return false, err
   923  	}
   924  	fi, err := os.Stat(filepath.Join(root, "go.mod"))
   925  	return err == nil && !fi.IsDir(), nil
   926  }
   927  
   928  // A versionRepo is a subset of modfetch.Repo that can report information about
   929  // available versions, but cannot fetch specific source files.
   930  type versionRepo interface {
   931  	ModulePath() string
   932  	Versions(prefix string) ([]string, error)
   933  	Stat(rev string) (*modfetch.RevInfo, error)
   934  	Latest() (*modfetch.RevInfo, error)
   935  }
   936  
   937  var _ versionRepo = modfetch.Repo(nil)
   938  
   939  func lookupRepo(proxy, path string) (repo versionRepo, err error) {
   940  	err = module.CheckPath(path)
   941  	if err == nil {
   942  		repo = modfetch.Lookup(proxy, path)
   943  	} else {
   944  		repo = emptyRepo{path: path, err: err}
   945  	}
   946  
   947  	if index == nil {
   948  		return repo, err
   949  	}
   950  	if _, ok := index.highestReplaced[path]; !ok {
   951  		return repo, err
   952  	}
   953  
   954  	return &replacementRepo{repo: repo}, nil
   955  }
   956  
   957  // An emptyRepo is a versionRepo that contains no versions.
   958  type emptyRepo struct {
   959  	path string
   960  	err  error
   961  }
   962  
   963  var _ versionRepo = emptyRepo{}
   964  
   965  func (er emptyRepo) ModulePath() string                         { return er.path }
   966  func (er emptyRepo) Versions(prefix string) ([]string, error)   { return nil, nil }
   967  func (er emptyRepo) Stat(rev string) (*modfetch.RevInfo, error) { return nil, er.err }
   968  func (er emptyRepo) Latest() (*modfetch.RevInfo, error)         { return nil, er.err }
   969  
   970  // A replacementRepo augments a versionRepo to include the replacement versions
   971  // (if any) found in the main module's go.mod file.
   972  //
   973  // A replacementRepo suppresses "not found" errors for otherwise-nonexistent
   974  // modules, so a replacementRepo should only be constructed for a module that
   975  // actually has one or more valid replacements.
   976  type replacementRepo struct {
   977  	repo versionRepo
   978  }
   979  
   980  var _ versionRepo = (*replacementRepo)(nil)
   981  
   982  func (rr *replacementRepo) ModulePath() string { return rr.repo.ModulePath() }
   983  
   984  // Versions returns the versions from rr.repo augmented with any matching
   985  // replacement versions.
   986  func (rr *replacementRepo) Versions(prefix string) ([]string, error) {
   987  	repoVersions, err := rr.repo.Versions(prefix)
   988  	if err != nil && !errors.Is(err, os.ErrNotExist) {
   989  		return nil, err
   990  	}
   991  
   992  	versions := repoVersions
   993  	if index != nil && len(index.replace) > 0 {
   994  		path := rr.ModulePath()
   995  		for m, _ := range index.replace {
   996  			if m.Path == path && strings.HasPrefix(m.Version, prefix) && m.Version != "" && !modfetch.IsPseudoVersion(m.Version) {
   997  				versions = append(versions, m.Version)
   998  			}
   999  		}
  1000  	}
  1001  
  1002  	if len(versions) == len(repoVersions) { // No replacement versions added.
  1003  		return versions, nil
  1004  	}
  1005  
  1006  	sort.Slice(versions, func(i, j int) bool {
  1007  		return semver.Compare(versions[i], versions[j]) < 0
  1008  	})
  1009  	str.Uniq(&versions)
  1010  	return versions, nil
  1011  }
  1012  
  1013  func (rr *replacementRepo) Stat(rev string) (*modfetch.RevInfo, error) {
  1014  	info, err := rr.repo.Stat(rev)
  1015  	if err == nil || index == nil || len(index.replace) == 0 {
  1016  		return info, err
  1017  	}
  1018  
  1019  	v := module.CanonicalVersion(rev)
  1020  	if v != rev {
  1021  		// The replacements in the go.mod file list only canonical semantic versions,
  1022  		// so a non-canonical version can't possibly have a replacement.
  1023  		return info, err
  1024  	}
  1025  
  1026  	path := rr.ModulePath()
  1027  	_, pathMajor, ok := module.SplitPathVersion(path)
  1028  	if ok && pathMajor == "" {
  1029  		if err := module.CheckPathMajor(v, pathMajor); err != nil && semver.Build(v) == "" {
  1030  			v += "+incompatible"
  1031  		}
  1032  	}
  1033  
  1034  	if r := Replacement(module.Version{Path: path, Version: v}); r.Path == "" {
  1035  		return info, err
  1036  	}
  1037  	return rr.replacementStat(v)
  1038  }
  1039  
  1040  func (rr *replacementRepo) Latest() (*modfetch.RevInfo, error) {
  1041  	info, err := rr.repo.Latest()
  1042  
  1043  	if index != nil {
  1044  		path := rr.ModulePath()
  1045  		if v, ok := index.highestReplaced[path]; ok {
  1046  			if v == "" {
  1047  				// The only replacement is a wildcard that doesn't specify a version, so
  1048  				// synthesize a pseudo-version with an appropriate major version and a
  1049  				// timestamp below any real timestamp. That way, if the main module is
  1050  				// used from within some other module, the user will be able to upgrade
  1051  				// the requirement to any real version they choose.
  1052  				if _, pathMajor, ok := module.SplitPathVersion(path); ok && len(pathMajor) > 0 {
  1053  					v = modfetch.PseudoVersion(pathMajor[1:], "", time.Time{}, "000000000000")
  1054  				} else {
  1055  					v = modfetch.PseudoVersion("v0", "", time.Time{}, "000000000000")
  1056  				}
  1057  			}
  1058  
  1059  			if err != nil || semver.Compare(v, info.Version) > 0 {
  1060  				return rr.replacementStat(v)
  1061  			}
  1062  		}
  1063  	}
  1064  
  1065  	return info, err
  1066  }
  1067  
  1068  func (rr *replacementRepo) replacementStat(v string) (*modfetch.RevInfo, error) {
  1069  	rev := &modfetch.RevInfo{Version: v}
  1070  	if modfetch.IsPseudoVersion(v) {
  1071  		rev.Time, _ = modfetch.PseudoVersionTime(v)
  1072  		rev.Short, _ = modfetch.PseudoVersionRev(v)
  1073  	}
  1074  	return rev, nil
  1075  }
  1076  
  1077  // A QueryMatchesMainModuleError indicates that a query requests
  1078  // a version of the main module that cannot be satisfied.
  1079  // (The main module's version cannot be changed.)
  1080  type QueryMatchesMainModuleError struct {
  1081  	Pattern string
  1082  	Query   string
  1083  }
  1084  
  1085  func (e *QueryMatchesMainModuleError) Error() string {
  1086  	if e.Pattern == Target.Path {
  1087  		return fmt.Sprintf("can't request version %q of the main module (%s)", e.Query, e.Pattern)
  1088  	}
  1089  
  1090  	return fmt.Sprintf("can't request version %q of pattern %q that includes the main module (%s)", e.Query, e.Pattern, Target.Path)
  1091  }
  1092  
  1093  // A QueryMatchesPackagesInMainModuleError indicates that a query cannot be
  1094  // satisfied because it matches one or more packages found in the main module.
  1095  type QueryMatchesPackagesInMainModuleError struct {
  1096  	Pattern  string
  1097  	Query    string
  1098  	Packages []string
  1099  }
  1100  
  1101  func (e *QueryMatchesPackagesInMainModuleError) Error() string {
  1102  	if len(e.Packages) > 1 {
  1103  		return fmt.Sprintf("pattern %s matches %d packages in the main module, so can't request version %s", e.Pattern, len(e.Packages), e.Query)
  1104  	}
  1105  
  1106  	if search.IsMetaPackage(e.Pattern) || strings.Contains(e.Pattern, "...") {
  1107  		return fmt.Sprintf("pattern %s matches package %s in the main module, so can't request version %s", e.Pattern, e.Packages[0], e.Query)
  1108  	}
  1109  
  1110  	return fmt.Sprintf("package %s is in the main module, so can't request version %s", e.Packages[0], e.Query)
  1111  }
  1112  

View as plain text