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	"bytes"
9	"context"
10	"encoding/hex"
11	"errors"
12	"fmt"
13	"internal/goroot"
14	"io/fs"
15	"os"
16	"path/filepath"
17	"strings"
18
19	"cmd/go/internal/base"
20	"cmd/go/internal/cfg"
21	"cmd/go/internal/modfetch"
22	"cmd/go/internal/modinfo"
23	"cmd/go/internal/search"
24
25	"golang.org/x/mod/module"
26	"golang.org/x/mod/semver"
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 goroot.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)
65}
66
67func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic {
68	if !Enabled() {
69		return nil
70	}
71
72	if i := strings.Index(path, "@"); i >= 0 {
73		m := module.Version{Path: path[:i], Version: path[i+1:]}
74		return moduleInfo(ctx, nil, m, 0)
75	}
76
77	rs := LoadModFile(ctx)
78
79	var (
80		v  string
81		ok bool
82	)
83	if rs.depth == lazy {
84		v, ok = rs.rootSelected(path)
85	}
86	if !ok {
87		mg, err := rs.Graph(ctx)
88		if err != nil {
89			base.Fatalf("go: %v", err)
90		}
91		v = mg.Selected(path)
92	}
93
94	if v == "none" {
95		return &modinfo.ModulePublic{
96			Path: path,
97			Error: &modinfo.ModuleError{
98				Err: "module not in current build",
99			},
100		}
101	}
102
103	return moduleInfo(ctx, rs, module.Version{Path: path, Version: v}, 0)
104}
105
106// addUpdate fills in m.Update if an updated version is available.
107func addUpdate(ctx context.Context, m *modinfo.ModulePublic) {
108	if m.Version == "" {
109		return
110	}
111
112	info, err := Query(ctx, m.Path, "upgrade", m.Version, CheckAllowed)
113	var noVersionErr *NoMatchingVersionError
114	if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) {
115		// Ignore "not found" and "no matching version" errors.
116		// This means the proxy has no matching version or no versions at all.
117		//
118		// We should report other errors though. An attacker that controls the
119		// network shouldn't be able to hide versions by interfering with
120		// the HTTPS connection. An attacker that controls the proxy may still
121		// hide versions, since the "list" and "latest" endpoints are not
122		// authenticated.
123		return
124	} else if err != nil {
125		if m.Error == nil {
126			m.Error = &modinfo.ModuleError{Err: err.Error()}
127		}
128		return
129	}
130
131	if semver.Compare(info.Version, m.Version) > 0 {
132		m.Update = &modinfo.ModulePublic{
133			Path:    m.Path,
134			Version: info.Version,
135			Time:    &info.Time,
136		}
137	}
138}
139
140// addVersions fills in m.Versions with the list of known versions.
141// Excluded versions will be omitted. If listRetracted is false, retracted
142// versions will also be omitted.
143func addVersions(ctx context.Context, m *modinfo.ModulePublic, listRetracted bool) {
144	allowed := CheckAllowed
145	if listRetracted {
146		allowed = CheckExclusions
147	}
148	var err error
149	m.Versions, err = versions(ctx, m.Path, allowed)
150	if err != nil && m.Error == nil {
151		m.Error = &modinfo.ModuleError{Err: err.Error()}
152	}
153}
154
155// addRetraction fills in m.Retracted if the module was retracted by its author.
156// m.Error is set if there's an error loading retraction information.
157func addRetraction(ctx context.Context, m *modinfo.ModulePublic) {
158	if m.Version == "" {
159		return
160	}
161
162	err := CheckRetractions(ctx, module.Version{Path: m.Path, Version: m.Version})
163	var noVersionErr *NoMatchingVersionError
164	var retractErr *ModuleRetractedError
165	if err == nil || errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) {
166		// Ignore "not found" and "no matching version" errors.
167		// This means the proxy has no matching version or no versions at all.
168		//
169		// We should report other errors though. An attacker that controls the
170		// network shouldn't be able to hide versions by interfering with
171		// the HTTPS connection. An attacker that controls the proxy may still
172		// hide versions, since the "list" and "latest" endpoints are not
173		// authenticated.
174		return
175	} else if errors.As(err, &retractErr) {
176		if len(retractErr.Rationale) == 0 {
177			m.Retracted = []string{"retracted by module author"}
178		} else {
179			m.Retracted = retractErr.Rationale
180		}
181	} else if m.Error == nil {
182		m.Error = &modinfo.ModuleError{Err: err.Error()}
183	}
184}
185
186// addDeprecation fills in m.Deprecated if the module was deprecated by its
187// author. m.Error is set if there's an error loading deprecation information.
188func addDeprecation(ctx context.Context, m *modinfo.ModulePublic) {
189	deprecation, err := CheckDeprecation(ctx, module.Version{Path: m.Path, Version: m.Version})
190	var noVersionErr *NoMatchingVersionError
191	if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) {
192		// Ignore "not found" and "no matching version" errors.
193		// This means the proxy has no matching version or no versions at all.
194		//
195		// We should report other errors though. An attacker that controls the
196		// network shouldn't be able to hide versions by interfering with
197		// the HTTPS connection. An attacker that controls the proxy may still
198		// hide versions, since the "list" and "latest" endpoints are not
199		// authenticated.
200		return
201	}
202	if err != nil {
203		if m.Error == nil {
204			m.Error = &modinfo.ModuleError{Err: err.Error()}
205		}
206		return
207	}
208	m.Deprecated = deprecation
209}
210
211// moduleInfo returns information about module m, loaded from the requirements
212// in rs (which may be nil to indicate that m was not loaded from a requirement
213// graph).
214func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode ListMode) *modinfo.ModulePublic {
215	if m == Target {
216		info := &modinfo.ModulePublic{
217			Path:    m.Path,
218			Version: m.Version,
219			Main:    true,
220		}
221		if v, ok := rawGoVersion.Load(Target); ok {
222			info.GoVersion = v.(string)
223		} else {
224			panic("internal error: GoVersion not set for main module")
225		}
226		if HasModRoot() {
227			info.Dir = ModRoot()
228			info.GoMod = ModFilePath()
229		}
230		return info
231	}
232
233	info := &modinfo.ModulePublic{
234		Path:     m.Path,
235		Version:  m.Version,
236		Indirect: rs != nil && !rs.direct[m.Path],
237	}
238	if v, ok := rawGoVersion.Load(m); ok {
239		info.GoVersion = v.(string)
240	}
241
242	// completeFromModCache fills in the extra fields in m using the module cache.
243	completeFromModCache := func(m *modinfo.ModulePublic) {
244		checksumOk := func(suffix string) bool {
245			return rs == nil || m.Version == "" || cfg.BuildMod == "mod" ||
246				modfetch.HaveSum(module.Version{Path: m.Path, Version: m.Version + suffix})
247		}
248
249		if m.Version != "" {
250			if q, err := Query(ctx, m.Path, m.Version, "", nil); err != nil {
251				m.Error = &modinfo.ModuleError{Err: err.Error()}
252			} else {
253				m.Version = q.Version
254				m.Time = &q.Time
255			}
256		}
257		mod := module.Version{Path: m.Path, Version: m.Version}
258
259		if m.GoVersion == "" && checksumOk("/go.mod") {
260			// Load the go.mod file to determine the Go version, since it hasn't
261			// already been populated from rawGoVersion.
262			if summary, err := rawGoModSummary(mod); err == nil && summary.goVersion != "" {
263				m.GoVersion = summary.goVersion
264			}
265		}
266
267		if m.Version != "" {
268			if checksumOk("/go.mod") {
269				gomod, err := modfetch.CachePath(mod, "mod")
270				if err == nil {
271					if info, err := os.Stat(gomod); err == nil && info.Mode().IsRegular() {
272						m.GoMod = gomod
273					}
274				}
275			}
276			if checksumOk("") {
277				dir, err := modfetch.DownloadDir(mod)
278				if err == nil {
279					m.Dir = dir
280				}
281			}
282
283			if mode&ListRetracted != 0 {
284				addRetraction(ctx, m)
285			}
286		}
287	}
288
289	if rs == nil {
290		// If this was an explicitly-versioned argument to 'go mod download' or
291		// 'go list -m', report the actual requested version, not its replacement.
292		completeFromModCache(info) // Will set m.Error in vendor mode.
293		return info
294	}
295
296	r := Replacement(m)
297	if r.Path == "" {
298		if cfg.BuildMod == "vendor" {
299			// It's tempting to fill in the "Dir" field to point within the vendor
300			// directory, but that would be misleading: the vendor directory contains
301			// a flattened package tree, not complete modules, and it can even
302			// interleave packages from different modules if one module path is a
303			// prefix of the other.
304		} else {
305			completeFromModCache(info)
306		}
307		return info
308	}
309
310	// Don't hit the network to fill in extra data for replaced modules.
311	// The original resolved Version and Time don't matter enough to be
312	// worth the cost, and we're going to overwrite the GoMod and Dir from the
313	// replacement anyway. See https://golang.org/issue/27859.
314	info.Replace = &modinfo.ModulePublic{
315		Path:    r.Path,
316		Version: r.Version,
317	}
318	if v, ok := rawGoVersion.Load(m); ok {
319		info.Replace.GoVersion = v.(string)
320	}
321	if r.Version == "" {
322		if filepath.IsAbs(r.Path) {
323			info.Replace.Dir = r.Path
324		} else {
325			info.Replace.Dir = filepath.Join(ModRoot(), r.Path)
326		}
327		info.Replace.GoMod = filepath.Join(info.Replace.Dir, "go.mod")
328	}
329	if cfg.BuildMod != "vendor" {
330		completeFromModCache(info.Replace)
331		info.Dir = info.Replace.Dir
332		info.GoMod = info.Replace.GoMod
333		info.Retracted = info.Replace.Retracted
334	}
335	info.GoVersion = info.Replace.GoVersion
336	return info
337}
338
339// PackageBuildInfo returns a string containing module version information
340// for modules providing packages named by path and deps. path and deps must
341// name packages that were resolved successfully with LoadPackages.
342func PackageBuildInfo(path string, deps []string) string {
343	if isStandardImportPath(path) || !Enabled() {
344		return ""
345	}
346
347	target := mustFindModule(loaded, path, path)
348	mdeps := make(map[module.Version]bool)
349	for _, dep := range deps {
350		if !isStandardImportPath(dep) {
351			mdeps[mustFindModule(loaded, path, dep)] = true
352		}
353	}
354	var mods []module.Version
355	delete(mdeps, target)
356	for mod := range mdeps {
357		mods = append(mods, mod)
358	}
359	module.Sort(mods)
360
361	var buf bytes.Buffer
362	fmt.Fprintf(&buf, "path\t%s\n", path)
363
364	writeEntry := func(token string, m module.Version) {
365		mv := m.Version
366		if mv == "" {
367			mv = "(devel)"
368		}
369		fmt.Fprintf(&buf, "%s\t%s\t%s", token, m.Path, mv)
370		if r := Replacement(m); r.Path == "" {
371			fmt.Fprintf(&buf, "\t%s\n", modfetch.Sum(m))
372		} else {
373			fmt.Fprintf(&buf, "\n=>\t%s\t%s\t%s\n", r.Path, r.Version, modfetch.Sum(r))
374		}
375	}
376
377	writeEntry("mod", target)
378	for _, mod := range mods {
379		writeEntry("dep", mod)
380	}
381
382	return buf.String()
383}
384
385// mustFindModule is like findModule, but it calls base.Fatalf if the
386// module can't be found.
387//
388// TODO(jayconrod): remove this. Callers should use findModule and return
389// errors instead of relying on base.Fatalf.
390func mustFindModule(ld *loader, target, path string) module.Version {
391	pkg, ok := ld.pkgCache.Get(path).(*loadPkg)
392	if ok {
393		if pkg.err != nil {
394			base.Fatalf("build %v: cannot load %v: %v", target, path, pkg.err)
395		}
396		return pkg.mod
397	}
398
399	if path == "command-line-arguments" {
400		return Target
401	}
402
403	base.Fatalf("build %v: cannot find module for path %v", target, path)
404	panic("unreachable")
405}
406
407// findModule searches for the module that contains the package at path.
408// If the package was loaded, its containing module and true are returned.
409// Otherwise, module.Version{} and false are returend.
410func findModule(ld *loader, path string) (module.Version, bool) {
411	if pkg, ok := ld.pkgCache.Get(path).(*loadPkg); ok {
412		return pkg.mod, pkg.mod != module.Version{}
413	}
414	if path == "command-line-arguments" {
415		return Target, true
416	}
417	return module.Version{}, false
418}
419
420func ModInfoProg(info string, isgccgo bool) []byte {
421	// Inject a variable with the debug information as runtime.modinfo,
422	// but compile it in package main so that it is specific to the binary.
423	// The variable must be a literal so that it will have the correct value
424	// before the initializer for package main runs.
425	//
426	// The runtime startup code refers to the variable, which keeps it live
427	// in all binaries.
428	//
429	// Note: we use an alternate recipe below for gccgo (based on an
430	// init function) due to the fact that gccgo does not support
431	// applying a "//go:linkname" directive to a variable. This has
432	// drawbacks in that other packages may want to look at the module
433	// info in their init functions (see issue 29628), which won't
434	// work for gccgo. See also issue 30344.
435
436	if !isgccgo {
437		return []byte(fmt.Sprintf(`package main
438import _ "unsafe"
439//go:linkname __set_modinfo__ runtime.setmodinfo
440func __set_modinfo__(string)
441func init() { __set_modinfo__(%q) }
442	`, string(infoStart)+info+string(infoEnd)))
443	} else {
444		return []byte(fmt.Sprintf(`package main
445import _ "unsafe"
446//go:linkname __set_debug_modinfo__ runtime.setmodinfo
447func __set_debug_modinfo__(string)
448func init() { __set_debug_modinfo__(%q) }
449`, string(infoStart)+info+string(infoEnd)))
450	}
451}
452