1// Copyright 2020 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
5package modload
6
7import (
8	"context"
9	"errors"
10	"fmt"
11	"path/filepath"
12	"strings"
13	"sync"
14	"unicode"
15
16	"cmd/go/internal/base"
17	"cmd/go/internal/cfg"
18	"cmd/go/internal/lockedfile"
19	"cmd/go/internal/modfetch"
20	"cmd/go/internal/par"
21	"cmd/go/internal/trace"
22
23	"golang.org/x/mod/modfile"
24	"golang.org/x/mod/module"
25	"golang.org/x/mod/semver"
26)
27
28// narrowAllVersionV is the Go version (plus leading "v") at which the
29// module-module "all" pattern no longer closes over the dependencies of
30// tests outside of the main module.
31const narrowAllVersionV = "v1.16"
32const go116EnableNarrowAll = true
33
34var modFile *modfile.File
35
36// A modFileIndex is an index of data corresponding to a modFile
37// at a specific point in time.
38type modFileIndex struct {
39	data            []byte
40	dataNeedsFix    bool // true if fixVersion applied a change while parsing data
41	module          module.Version
42	goVersionV      string // GoVersion with "v" prefix
43	require         map[module.Version]requireMeta
44	replace         map[module.Version]module.Version
45	highestReplaced map[string]string // highest replaced version of each module path; empty string for wildcard-only replacements
46	exclude         map[module.Version]bool
47}
48
49// index is the index of the go.mod file as of when it was last read or written.
50var index *modFileIndex
51
52type requireMeta struct {
53	indirect bool
54}
55
56// CheckAllowed returns an error equivalent to ErrDisallowed if m is excluded by
57// the main module's go.mod or retracted by its author. Most version queries use
58// this to filter out versions that should not be used.
59func CheckAllowed(ctx context.Context, m module.Version) error {
60	if err := CheckExclusions(ctx, m); err != nil {
61		return err
62	}
63	if err := CheckRetractions(ctx, m); err != nil {
64		return err
65	}
66	return nil
67}
68
69// ErrDisallowed is returned by version predicates passed to Query and similar
70// functions to indicate that a version should not be considered.
71var ErrDisallowed = errors.New("disallowed module version")
72
73// CheckExclusions returns an error equivalent to ErrDisallowed if module m is
74// excluded by the main module's go.mod file.
75func CheckExclusions(ctx context.Context, m module.Version) error {
76	if index != nil && index.exclude[m] {
77		return module.VersionError(m, errExcluded)
78	}
79	return nil
80}
81
82var errExcluded = &excludedError{}
83
84type excludedError struct{}
85
86func (e *excludedError) Error() string     { return "excluded by go.mod" }
87func (e *excludedError) Is(err error) bool { return err == ErrDisallowed }
88
89// CheckRetractions returns an error if module m has been retracted by
90// its author.
91func CheckRetractions(ctx context.Context, m module.Version) error {
92	if m.Version == "" {
93		// Main module, standard library, or file replacement module.
94		// Cannot be retracted.
95		return nil
96	}
97
98	// Look up retraction information from the latest available version of
99	// the module. Cache retraction information so we don't parse the go.mod
100	// file repeatedly.
101	type entry struct {
102		retract []retraction
103		err     error
104	}
105	path := m.Path
106	e := retractCache.Do(path, func() (v interface{}) {
107		ctx, span := trace.StartSpan(ctx, "checkRetractions "+path)
108		defer span.Done()
109
110		if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
111			// All versions of the module were replaced with a local directory.
112			// Don't load retractions.
113			return &entry{nil, nil}
114		}
115
116		// Find the latest version of the module.
117		// Ignore exclusions from the main module's go.mod.
118		const ignoreSelected = ""
119		var allowAll AllowedFunc
120		rev, err := Query(ctx, path, "latest", ignoreSelected, allowAll)
121		if err != nil {
122			return &entry{nil, err}
123		}
124
125		// Load go.mod for that version.
126		// If the version is replaced, we'll load retractions from the replacement.
127		//
128		// If there's an error loading the go.mod, we'll return it here.
129		// These errors should generally be ignored by callers of checkRetractions,
130		// since they happen frequently when we're offline. These errors are not
131		// equivalent to ErrDisallowed, so they may be distinguished from
132		// retraction errors.
133		//
134		// We load the raw file here: the go.mod file may have a different module
135		// path that we expect if the module or its repository was renamed.
136		// We still want to apply retractions to other aliases of the module.
137		rm := module.Version{Path: path, Version: rev.Version}
138		if repl := Replacement(rm); repl.Path != "" {
139			rm = repl
140		}
141		summary, err := rawGoModSummary(rm)
142		if err != nil {
143			return &entry{nil, err}
144		}
145		return &entry{summary.retract, nil}
146	}).(*entry)
147
148	if err := e.err; err != nil {
149		// Attribute the error to the version being checked, not the version from
150		// which the retractions were to be loaded.
151		var mErr *module.ModuleError
152		if errors.As(err, &mErr) {
153			err = mErr.Err
154		}
155		return &retractionLoadingError{m: m, err: err}
156	}
157
158	var rationale []string
159	isRetracted := false
160	for _, r := range e.retract {
161		if semver.Compare(r.Low, m.Version) <= 0 && semver.Compare(m.Version, r.High) <= 0 {
162			isRetracted = true
163			if r.Rationale != "" {
164				rationale = append(rationale, r.Rationale)
165			}
166		}
167	}
168	if isRetracted {
169		return module.VersionError(m, &ModuleRetractedError{Rationale: rationale})
170	}
171	return nil
172}
173
174var retractCache par.Cache
175
176type ModuleRetractedError struct {
177	Rationale []string
178}
179
180func (e *ModuleRetractedError) Error() string {
181	msg := "retracted by module author"
182	if len(e.Rationale) > 0 {
183		// This is meant to be a short error printed on a terminal, so just
184		// print the first rationale.
185		msg += ": " + ShortRetractionRationale(e.Rationale[0])
186	}
187	return msg
188}
189
190func (e *ModuleRetractedError) Is(err error) bool {
191	return err == ErrDisallowed
192}
193
194type retractionLoadingError struct {
195	m   module.Version
196	err error
197}
198
199func (e *retractionLoadingError) Error() string {
200	return fmt.Sprintf("loading module retractions for %v: %v", e.m, e.err)
201}
202
203func (e *retractionLoadingError) Unwrap() error {
204	return e.err
205}
206
207// ShortRetractionRationale returns a retraction rationale string that is safe
208// to print in a terminal. It returns hard-coded strings if the rationale
209// is empty, too long, or contains non-printable characters.
210func ShortRetractionRationale(rationale string) string {
211	const maxRationaleBytes = 500
212	if i := strings.Index(rationale, "\n"); i >= 0 {
213		rationale = rationale[:i]
214	}
215	rationale = strings.TrimSpace(rationale)
216	if rationale == "" {
217		return "retracted by module author"
218	}
219	if len(rationale) > maxRationaleBytes {
220		return "(rationale omitted: too long)"
221	}
222	for _, r := range rationale {
223		if !unicode.IsGraphic(r) && !unicode.IsSpace(r) {
224			return "(rationale omitted: contains non-printable characters)"
225		}
226	}
227	// NOTE: the go.mod parser rejects invalid UTF-8, so we don't check that here.
228	return rationale
229}
230
231// Replacement returns the replacement for mod, if any, from go.mod.
232// If there is no replacement for mod, Replacement returns
233// a module.Version with Path == "".
234func Replacement(mod module.Version) module.Version {
235	if index != nil {
236		if r, ok := index.replace[mod]; ok {
237			return r
238		}
239		if r, ok := index.replace[module.Version{Path: mod.Path}]; ok {
240			return r
241		}
242	}
243	return module.Version{}
244}
245
246// indexModFile rebuilds the index of modFile.
247// If modFile has been changed since it was first read,
248// modFile.Cleanup must be called before indexModFile.
249func indexModFile(data []byte, modFile *modfile.File, needsFix bool) *modFileIndex {
250	i := new(modFileIndex)
251	i.data = data
252	i.dataNeedsFix = needsFix
253
254	i.module = module.Version{}
255	if modFile.Module != nil {
256		i.module = modFile.Module.Mod
257	}
258
259	i.goVersionV = ""
260	if modFile.Go != nil {
261		// We're going to use the semver package to compare Go versions, so go ahead
262		// and add the "v" prefix it expects once instead of every time.
263		i.goVersionV = "v" + modFile.Go.Version
264	}
265
266	i.require = make(map[module.Version]requireMeta, len(modFile.Require))
267	for _, r := range modFile.Require {
268		i.require[r.Mod] = requireMeta{indirect: r.Indirect}
269	}
270
271	i.replace = make(map[module.Version]module.Version, len(modFile.Replace))
272	for _, r := range modFile.Replace {
273		if prev, dup := i.replace[r.Old]; dup && prev != r.New {
274			base.Fatalf("go: conflicting replacements for %v:\n\t%v\n\t%v", r.Old, prev, r.New)
275		}
276		i.replace[r.Old] = r.New
277	}
278
279	i.highestReplaced = make(map[string]string)
280	for _, r := range modFile.Replace {
281		v, ok := i.highestReplaced[r.Old.Path]
282		if !ok || semver.Compare(r.Old.Version, v) > 0 {
283			i.highestReplaced[r.Old.Path] = r.Old.Version
284		}
285	}
286
287	i.exclude = make(map[module.Version]bool, len(modFile.Exclude))
288	for _, x := range modFile.Exclude {
289		i.exclude[x.Mod] = true
290	}
291
292	return i
293}
294
295// allPatternClosesOverTests reports whether the "all" pattern includes
296// dependencies of tests outside the main module (as in Go 1.11–1.15).
297// (Otherwise — as in Go 1.16+ — the "all" pattern includes only the packages
298// transitively *imported by* the packages and tests in the main module.)
299func (i *modFileIndex) allPatternClosesOverTests() bool {
300	if !go116EnableNarrowAll {
301		return true
302	}
303	if i != nil && semver.Compare(i.goVersionV, narrowAllVersionV) < 0 {
304		// The module explicitly predates the change in "all" for lazy loading, so
305		// continue to use the older interpretation. (If i == nil, we not in any
306		// module at all and should use the latest semantics.)
307		return true
308	}
309	return false
310}
311
312// modFileIsDirty reports whether the go.mod file differs meaningfully
313// from what was indexed.
314// If modFile has been changed (even cosmetically) since it was first read,
315// modFile.Cleanup must be called before modFileIsDirty.
316func (i *modFileIndex) modFileIsDirty(modFile *modfile.File) bool {
317	if i == nil {
318		return modFile != nil
319	}
320
321	if i.dataNeedsFix {
322		return true
323	}
324
325	if modFile.Module == nil {
326		if i.module != (module.Version{}) {
327			return true
328		}
329	} else if modFile.Module.Mod != i.module {
330		return true
331	}
332
333	if modFile.Go == nil {
334		if i.goVersionV != "" {
335			return true
336		}
337	} else if "v"+modFile.Go.Version != i.goVersionV {
338		if i.goVersionV == "" && cfg.BuildMod == "readonly" {
339			// go.mod files did not always require a 'go' version, so do not error out
340			// if one is missing — we may be inside an older module in the module
341			// cache, and should bias toward providing useful behavior.
342		} else {
343			return true
344		}
345	}
346
347	if len(modFile.Require) != len(i.require) ||
348		len(modFile.Replace) != len(i.replace) ||
349		len(modFile.Exclude) != len(i.exclude) {
350		return true
351	}
352
353	for _, r := range modFile.Require {
354		if meta, ok := i.require[r.Mod]; !ok {
355			return true
356		} else if r.Indirect != meta.indirect {
357			if cfg.BuildMod == "readonly" {
358				// The module's requirements are consistent; only the "// indirect"
359				// comments that are wrong. But those are only guaranteed to be accurate
360				// after a "go mod tidy" — it's a good idea to run those before
361				// committing a change, but it's certainly not mandatory.
362			} else {
363				return true
364			}
365		}
366	}
367
368	for _, r := range modFile.Replace {
369		if r.New != i.replace[r.Old] {
370			return true
371		}
372	}
373
374	for _, x := range modFile.Exclude {
375		if !i.exclude[x.Mod] {
376			return true
377		}
378	}
379
380	return false
381}
382
383// rawGoVersion records the Go version parsed from each module's go.mod file.
384//
385// If a module is replaced, the version of the replacement is keyed by the
386// replacement module.Version, not the version being replaced.
387var rawGoVersion sync.Map // map[module.Version]string
388
389// A modFileSummary is a summary of a go.mod file for which we do not need to
390// retain complete information — for example, the go.mod file of a dependency
391// module.
392type modFileSummary struct {
393	module     module.Version
394	goVersionV string // GoVersion with "v" prefix
395	require    []module.Version
396	retract    []retraction
397}
398
399// A retraction consists of a retracted version interval and rationale.
400// retraction is like modfile.Retract, but it doesn't point to the syntax tree.
401type retraction struct {
402	modfile.VersionInterval
403	Rationale string
404}
405
406// goModSummary returns a summary of the go.mod file for module m,
407// taking into account any replacements for m, exclusions of its dependencies,
408// and/or vendoring.
409//
410// m must be a version in the module graph, reachable from the Target module.
411// In readonly mode, the go.sum file must contain an entry for m's go.mod file
412// (or its replacement). goModSummary must not be called for the Target module
413// itself, as its requirements may change. Use rawGoModSummary for other
414// module versions.
415//
416// The caller must not modify the returned summary.
417func goModSummary(m module.Version) (*modFileSummary, error) {
418	if m == Target {
419		panic("internal error: goModSummary called on the Target module")
420	}
421
422	if cfg.BuildMod == "vendor" {
423		summary := &modFileSummary{
424			module: module.Version{Path: m.Path},
425		}
426		if vendorVersion[m.Path] != m.Version {
427			// This module is not vendored, so packages cannot be loaded from it and
428			// it cannot be relevant to the build.
429			return summary, nil
430		}
431
432		// For every module other than the target,
433		// return the full list of modules from modules.txt.
434		readVendorList()
435
436		// TODO(#36876): Load the "go" version from vendor/modules.txt and store it
437		// in rawGoVersion with the appropriate key.
438
439		// We don't know what versions the vendored module actually relies on,
440		// so assume that it requires everything.
441		summary.require = vendorList
442		return summary, nil
443	}
444
445	actual := Replacement(m)
446	if actual.Path == "" {
447		actual = m
448	}
449	if HasModRoot() && cfg.BuildMod == "readonly" && actual.Version != "" {
450		key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"}
451		if !modfetch.HaveSum(key) {
452			suggestion := fmt.Sprintf("; to add it:\n\tgo mod download %s", m.Path)
453			return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion})
454		}
455	}
456	summary, err := rawGoModSummary(actual)
457	if err != nil {
458		return nil, err
459	}
460
461	if actual.Version == "" {
462		// The actual module is a filesystem-local replacement, for which we have
463		// unfortunately not enforced any sort of invariants about module lines or
464		// matching module paths. Anything goes.
465		//
466		// TODO(bcmills): Remove this special-case, update tests, and add a
467		// release note.
468	} else {
469		if summary.module.Path == "" {
470			return nil, module.VersionError(actual, errors.New("parsing go.mod: missing module line"))
471		}
472
473		// In theory we should only allow mpath to be unequal to m.Path here if the
474		// version that we fetched lacks an explicit go.mod file: if the go.mod file
475		// is explicit, then it should match exactly (to ensure that imports of other
476		// packages within the module are interpreted correctly). Unfortunately, we
477		// can't determine that information from the module proxy protocol: we'll have
478		// to leave that validation for when we load actual packages from within the
479		// module.
480		if mpath := summary.module.Path; mpath != m.Path && mpath != actual.Path {
481			return nil, module.VersionError(actual, fmt.Errorf(`parsing go.mod:
482	module declares its path as: %s
483	        but was required as: %s`, mpath, m.Path))
484		}
485	}
486
487	if index != nil && len(index.exclude) > 0 {
488		// Drop any requirements on excluded versions.
489		// Don't modify the cached summary though, since we might need the raw
490		// summary separately.
491		haveExcludedReqs := false
492		for _, r := range summary.require {
493			if index.exclude[r] {
494				haveExcludedReqs = true
495				break
496			}
497		}
498		if haveExcludedReqs {
499			s := new(modFileSummary)
500			*s = *summary
501			s.require = make([]module.Version, 0, len(summary.require))
502			for _, r := range summary.require {
503				if !index.exclude[r] {
504					s.require = append(s.require, r)
505				}
506			}
507			summary = s
508		}
509	}
510	return summary, nil
511}
512
513// rawGoModSummary returns a new summary of the go.mod file for module m,
514// ignoring all replacements that may apply to m and excludes that may apply to
515// its dependencies.
516//
517// rawGoModSummary cannot be used on the Target module.
518func rawGoModSummary(m module.Version) (*modFileSummary, error) {
519	if m == Target {
520		panic("internal error: rawGoModSummary called on the Target module")
521	}
522
523	type cached struct {
524		summary *modFileSummary
525		err     error
526	}
527	c := rawGoModSummaryCache.Do(m, func() interface{} {
528		summary := new(modFileSummary)
529		var f *modfile.File
530		if m.Version == "" {
531			// m is a replacement module with only a file path.
532			dir := m.Path
533			if !filepath.IsAbs(dir) {
534				dir = filepath.Join(ModRoot(), dir)
535			}
536			gomod := filepath.Join(dir, "go.mod")
537
538			data, err := lockedfile.Read(gomod)
539			if err != nil {
540				return cached{nil, module.VersionError(m, fmt.Errorf("reading %s: %v", base.ShortPath(gomod), err))}
541			}
542			f, err = modfile.ParseLax(gomod, data, nil)
543			if err != nil {
544				return cached{nil, module.VersionError(m, fmt.Errorf("parsing %s: %v", base.ShortPath(gomod), err))}
545			}
546		} else {
547			if !semver.IsValid(m.Version) {
548				// Disallow the broader queries supported by fetch.Lookup.
549				base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version)
550			}
551
552			data, err := modfetch.GoMod(m.Path, m.Version)
553			if err != nil {
554				return cached{nil, err}
555			}
556			f, err = modfile.ParseLax("go.mod", data, nil)
557			if err != nil {
558				return cached{nil, module.VersionError(m, fmt.Errorf("parsing go.mod: %v", err))}
559			}
560		}
561
562		if f.Module != nil {
563			summary.module = f.Module.Mod
564		}
565		if f.Go != nil && f.Go.Version != "" {
566			rawGoVersion.LoadOrStore(m, f.Go.Version)
567			summary.goVersionV = "v" + f.Go.Version
568		}
569		if len(f.Require) > 0 {
570			summary.require = make([]module.Version, 0, len(f.Require))
571			for _, req := range f.Require {
572				summary.require = append(summary.require, req.Mod)
573			}
574		}
575		if len(f.Retract) > 0 {
576			summary.retract = make([]retraction, 0, len(f.Retract))
577			for _, ret := range f.Retract {
578				summary.retract = append(summary.retract, retraction{
579					VersionInterval: ret.VersionInterval,
580					Rationale:       ret.Rationale,
581				})
582			}
583		}
584
585		return cached{summary, nil}
586	}).(cached)
587
588	return c.summary, c.err
589}
590
591var rawGoModSummaryCache par.Cache // module.Version → rawGoModSummary result
592