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
5package modload
6
7import (
8	"context"
9	"encoding/hex"
10	"errors"
11	"fmt"
12	"io/fs"
13	"os"
14	"path/filepath"
15	"strings"
16
17	"cmd/go/internal/base"
18	"cmd/go/internal/cfg"
19	"cmd/go/internal/gover"
20	"cmd/go/internal/modfetch"
21	"cmd/go/internal/modfetch/codehost"
22	"cmd/go/internal/modindex"
23	"cmd/go/internal/modinfo"
24	"cmd/go/internal/search"
25
26	"golang.org/x/mod/module"
27)
28
29var (
30	infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6")
31	infoEnd, _   = hex.DecodeString("f932433186182072008242104116d8f2")
32)
33
34func isStandardImportPath(path string) bool {
35	return findStandardImportPath(path) != ""
36}
37
38func findStandardImportPath(path string) string {
39	if path == "" {
40		panic("findStandardImportPath called with empty path")
41	}
42	if search.IsStandardImportPath(path) {
43		if modindex.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
44			return filepath.Join(cfg.GOROOT, "src", path)
45		}
46	}
47	return ""
48}
49
50// PackageModuleInfo returns information about the module that provides
51// a given package. If modules are not enabled or if the package is in the
52// standard library or if the package was not successfully loaded with
53// LoadPackages or ImportFromFiles, nil is returned.
54func PackageModuleInfo(ctx context.Context, pkgpath string) *modinfo.ModulePublic {
55	if isStandardImportPath(pkgpath) || !Enabled() {
56		return nil
57	}
58	m, ok := findModule(loaded, pkgpath)
59	if !ok {
60		return nil
61	}
62
63	rs := LoadModFile(ctx)
64	return moduleInfo(ctx, rs, m, 0, nil)
65}
66
67// PackageModRoot returns the module root directory for the module that provides
68// a given package. If modules are not enabled or if the package is in the
69// standard library or if the package was not successfully loaded with
70// LoadPackages or ImportFromFiles, the empty string is returned.
71func PackageModRoot(ctx context.Context, pkgpath string) string {
72	if isStandardImportPath(pkgpath) || !Enabled() || cfg.BuildMod == "vendor" {
73		return ""
74	}
75	m, ok := findModule(loaded, pkgpath)
76	if !ok {
77		return ""
78	}
79	root, _, err := fetch(ctx, m)
80	if err != nil {
81		return ""
82	}
83	return root
84}
85
86func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic {
87	if !Enabled() {
88		return nil
89	}
90
91	if path, vers, found := strings.Cut(path, "@"); found {
92		m := module.Version{Path: path, Version: vers}
93		return moduleInfo(ctx, nil, m, 0, nil)
94	}
95
96	rs := LoadModFile(ctx)
97
98	var (
99		v  string
100		ok bool
101	)
102	if rs.pruning == pruned {
103		v, ok = rs.rootSelected(path)
104	}
105	if !ok {
106		mg, err := rs.Graph(ctx)
107		if err != nil {
108			base.Fatal(err)
109		}
110		v = mg.Selected(path)
111	}
112
113	if v == "none" {
114		return &modinfo.ModulePublic{
115			Path: path,
116			Error: &modinfo.ModuleError{
117				Err: "module not in current build",
118			},
119		}
120	}
121
122	return moduleInfo(ctx, rs, module.Version{Path: path, Version: v}, 0, nil)
123}
124
125// addUpdate fills in m.Update if an updated version is available.
126func addUpdate(ctx context.Context, m *modinfo.ModulePublic) {
127	if m.Version == "" {
128		return
129	}
130
131	info, err := Query(ctx, m.Path, "upgrade", m.Version, CheckAllowed)
132	var noVersionErr *NoMatchingVersionError
133	if errors.Is(err, ErrDisallowed) ||
134		errors.Is(err, fs.ErrNotExist) ||
135		errors.As(err, &noVersionErr) {
136		// Ignore "not found" and "no matching version" errors.
137		// This means the proxy has no matching version or no versions at all.
138		//
139		// Ignore "disallowed" errors. This means the current version is
140		// excluded or retracted and there are no higher allowed versions.
141		//
142		// We should report other errors though. An attacker that controls the
143		// network shouldn't be able to hide versions by interfering with
144		// the HTTPS connection. An attacker that controls the proxy may still
145		// hide versions, since the "list" and "latest" endpoints are not
146		// authenticated.
147		return
148	} else if err != nil {
149		if m.Error == nil {
150			m.Error = &modinfo.ModuleError{Err: err.Error()}
151		}
152		return
153	}
154
155	if gover.ModCompare(m.Path, info.Version, m.Version) > 0 {
156		m.Update = &modinfo.ModulePublic{
157			Path:    m.Path,
158			Version: info.Version,
159			Time:    &info.Time,
160		}
161	}
162}
163
164// mergeOrigin returns the union of data from two origins,
165// returning either a new origin or one of its unmodified arguments.
166// If the two origins conflict including if either is nil,
167// mergeOrigin returns nil.
168func mergeOrigin(m1, m2 *codehost.Origin) *codehost.Origin {
169	if m1 == nil || m2 == nil {
170		return nil
171	}
172
173	if m2.VCS != m1.VCS ||
174		m2.URL != m1.URL ||
175		m2.Subdir != m1.Subdir {
176		return nil
177	}
178
179	merged := *m1
180	if m2.Hash != "" {
181		if m1.Hash != "" && m1.Hash != m2.Hash {
182			return nil
183		}
184		merged.Hash = m2.Hash
185	}
186	if m2.TagSum != "" {
187		if m1.TagSum != "" && (m1.TagSum != m2.TagSum || m1.TagPrefix != m2.TagPrefix) {
188			return nil
189		}
190		merged.TagSum = m2.TagSum
191		merged.TagPrefix = m2.TagPrefix
192	}
193	if m2.Ref != "" {
194		if m1.Ref != "" && m1.Ref != m2.Ref {
195			return nil
196		}
197		merged.Ref = m2.Ref
198	}
199
200	switch {
201	case merged == *m1:
202		return m1
203	case merged == *m2:
204		return m2
205	default:
206		// Clone the result to avoid an alloc for merged
207		// if the result is equal to one of the arguments.
208		clone := merged
209		return &clone
210	}
211}
212
213// addVersions fills in m.Versions with the list of known versions.
214// Excluded versions will be omitted. If listRetracted is false, retracted
215// versions will also be omitted.
216func addVersions(ctx context.Context, m *modinfo.ModulePublic, listRetracted bool) {
217	// TODO(bcmills): Would it make sense to check for reuse here too?
218	// Perhaps that doesn't buy us much, though: we would always have to fetch
219	// all of the version tags to list the available versions anyway.
220
221	allowed := CheckAllowed
222	if listRetracted {
223		allowed = CheckExclusions
224	}
225	v, origin, err := versions(ctx, m.Path, allowed)
226	if err != nil && m.Error == nil {
227		m.Error = &modinfo.ModuleError{Err: err.Error()}
228	}
229	m.Versions = v
230	m.Origin = mergeOrigin(m.Origin, origin)
231}
232
233// addRetraction fills in m.Retracted if the module was retracted by its author.
234// m.Error is set if there's an error loading retraction information.
235func addRetraction(ctx context.Context, m *modinfo.ModulePublic) {
236	if m.Version == "" {
237		return
238	}
239
240	err := CheckRetractions(ctx, module.Version{Path: m.Path, Version: m.Version})
241	var noVersionErr *NoMatchingVersionError
242	var retractErr *ModuleRetractedError
243	if err == nil || errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) {
244		// Ignore "not found" and "no matching version" errors.
245		// This means the proxy has no matching version or no versions at all.
246		//
247		// We should report other errors though. An attacker that controls the
248		// network shouldn't be able to hide versions by interfering with
249		// the HTTPS connection. An attacker that controls the proxy may still
250		// hide versions, since the "list" and "latest" endpoints are not
251		// authenticated.
252		return
253	} else if errors.As(err, &retractErr) {
254		if len(retractErr.Rationale) == 0 {
255			m.Retracted = []string{"retracted by module author"}
256		} else {
257			m.Retracted = retractErr.Rationale
258		}
259	} else if m.Error == nil {
260		m.Error = &modinfo.ModuleError{Err: err.Error()}
261	}
262}
263
264// addDeprecation fills in m.Deprecated if the module was deprecated by its
265// author. m.Error is set if there's an error loading deprecation information.
266func addDeprecation(ctx context.Context, m *modinfo.ModulePublic) {
267	deprecation, err := CheckDeprecation(ctx, module.Version{Path: m.Path, Version: m.Version})
268	var noVersionErr *NoMatchingVersionError
269	if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) {
270		// Ignore "not found" and "no matching version" errors.
271		// This means the proxy has no matching version or no versions at all.
272		//
273		// We should report other errors though. An attacker that controls the
274		// network shouldn't be able to hide versions by interfering with
275		// the HTTPS connection. An attacker that controls the proxy may still
276		// hide versions, since the "list" and "latest" endpoints are not
277		// authenticated.
278		return
279	}
280	if err != nil {
281		if m.Error == nil {
282			m.Error = &modinfo.ModuleError{Err: err.Error()}
283		}
284		return
285	}
286	m.Deprecated = deprecation
287}
288
289// moduleInfo returns information about module m, loaded from the requirements
290// in rs (which may be nil to indicate that m was not loaded from a requirement
291// graph).
292func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode ListMode, reuse map[module.Version]*modinfo.ModulePublic) *modinfo.ModulePublic {
293	if m.Version == "" && MainModules.Contains(m.Path) {
294		info := &modinfo.ModulePublic{
295			Path:    m.Path,
296			Version: m.Version,
297			Main:    true,
298		}
299		if v, ok := rawGoVersion.Load(m); ok {
300			info.GoVersion = v.(string)
301		} else {
302			panic("internal error: GoVersion not set for main module")
303		}
304		if modRoot := MainModules.ModRoot(m); modRoot != "" {
305			info.Dir = modRoot
306			info.GoMod = modFilePath(modRoot)
307		}
308		return info
309	}
310
311	info := &modinfo.ModulePublic{
312		Path:     m.Path,
313		Version:  m.Version,
314		Indirect: rs != nil && !rs.direct[m.Path],
315	}
316	if v, ok := rawGoVersion.Load(m); ok {
317		info.GoVersion = v.(string)
318	}
319
320	// completeFromModCache fills in the extra fields in m using the module cache.
321	completeFromModCache := func(m *modinfo.ModulePublic) {
322		if gover.IsToolchain(m.Path) {
323			return
324		}
325
326		checksumOk := func(suffix string) bool {
327			return rs == nil || m.Version == "" || !mustHaveSums() ||
328				modfetch.HaveSum(module.Version{Path: m.Path, Version: m.Version + suffix})
329		}
330
331		mod := module.Version{Path: m.Path, Version: m.Version}
332
333		if m.Version != "" {
334			if old := reuse[mod]; old != nil {
335				if err := checkReuse(ctx, mod, old.Origin); err == nil {
336					*m = *old
337					m.Query = ""
338					m.Dir = ""
339					return
340				}
341			}
342
343			if q, err := Query(ctx, m.Path, m.Version, "", nil); err != nil {
344				m.Error = &modinfo.ModuleError{Err: err.Error()}
345			} else {
346				m.Version = q.Version
347				m.Time = &q.Time
348			}
349		}
350
351		if m.GoVersion == "" && checksumOk("/go.mod") {
352			// Load the go.mod file to determine the Go version, since it hasn't
353			// already been populated from rawGoVersion.
354			if summary, err := rawGoModSummary(mod); err == nil && summary.goVersion != "" {
355				m.GoVersion = summary.goVersion
356			}
357		}
358
359		if m.Version != "" {
360			if checksumOk("/go.mod") {
361				gomod, err := modfetch.CachePath(ctx, mod, "mod")
362				if err == nil {
363					if info, err := os.Stat(gomod); err == nil && info.Mode().IsRegular() {
364						m.GoMod = gomod
365					}
366				}
367				if gomodsum, ok := modfetch.RecordedSum(modkey(mod)); ok {
368					m.GoModSum = gomodsum
369				}
370			}
371			if checksumOk("") {
372				dir, err := modfetch.DownloadDir(ctx, mod)
373				if err == nil {
374					m.Dir = dir
375				}
376				if sum, ok := modfetch.RecordedSum(mod); ok {
377					m.Sum = sum
378				}
379			}
380
381			if mode&ListRetracted != 0 {
382				addRetraction(ctx, m)
383			}
384		}
385	}
386
387	if rs == nil {
388		// If this was an explicitly-versioned argument to 'go mod download' or
389		// 'go list -m', report the actual requested version, not its replacement.
390		completeFromModCache(info) // Will set m.Error in vendor mode.
391		return info
392	}
393
394	r := Replacement(m)
395	if r.Path == "" {
396		if cfg.BuildMod == "vendor" {
397			// It's tempting to fill in the "Dir" field to point within the vendor
398			// directory, but that would be misleading: the vendor directory contains
399			// a flattened package tree, not complete modules, and it can even
400			// interleave packages from different modules if one module path is a
401			// prefix of the other.
402		} else {
403			completeFromModCache(info)
404		}
405		return info
406	}
407
408	// Don't hit the network to fill in extra data for replaced modules.
409	// The original resolved Version and Time don't matter enough to be
410	// worth the cost, and we're going to overwrite the GoMod and Dir from the
411	// replacement anyway. See https://golang.org/issue/27859.
412	info.Replace = &modinfo.ModulePublic{
413		Path:    r.Path,
414		Version: r.Version,
415	}
416	if v, ok := rawGoVersion.Load(m); ok {
417		info.Replace.GoVersion = v.(string)
418	}
419	if r.Version == "" {
420		if filepath.IsAbs(r.Path) {
421			info.Replace.Dir = r.Path
422		} else {
423			info.Replace.Dir = filepath.Join(replaceRelativeTo(), r.Path)
424		}
425		info.Replace.GoMod = filepath.Join(info.Replace.Dir, "go.mod")
426	}
427	if cfg.BuildMod != "vendor" {
428		completeFromModCache(info.Replace)
429		info.Dir = info.Replace.Dir
430		info.GoMod = info.Replace.GoMod
431		info.Retracted = info.Replace.Retracted
432	}
433	info.GoVersion = info.Replace.GoVersion
434	return info
435}
436
437// findModule searches for the module that contains the package at path.
438// If the package was loaded, its containing module and true are returned.
439// Otherwise, module.Version{} and false are returned.
440func findModule(ld *loader, path string) (module.Version, bool) {
441	if pkg, ok := ld.pkgCache.Get(path); ok {
442		return pkg.mod, pkg.mod != module.Version{}
443	}
444	return module.Version{}, false
445}
446
447func ModInfoProg(info string, isgccgo bool) []byte {
448	// Inject an init function to set runtime.modinfo.
449	// This is only used for gccgo - with gc we hand the info directly to the linker.
450	// The init function has the drawback that packages may want to
451	// look at the module info in their init functions (see issue 29628),
452	// which won't work. See also issue 30344.
453	if isgccgo {
454		return fmt.Appendf(nil, `package main
455import _ "unsafe"
456//go:linkname __set_debug_modinfo__ runtime.setmodinfo
457func __set_debug_modinfo__(string)
458func init() { __set_debug_modinfo__(%q) }
459`, ModInfoData(info))
460	}
461	return nil
462}
463
464func ModInfoData(info string) []byte {
465	return []byte(string(infoStart) + info + string(infoEnd))
466}
467