// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package modload import ( "bytes" "context" "encoding/hex" "errors" "fmt" "internal/goroot" "io/fs" "os" "path/filepath" "strings" "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/modfetch" "cmd/go/internal/modinfo" "cmd/go/internal/search" "golang.org/x/mod/module" "golang.org/x/mod/semver" ) var ( infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6") infoEnd, _ = hex.DecodeString("f932433186182072008242104116d8f2") ) func isStandardImportPath(path string) bool { return findStandardImportPath(path) != "" } func findStandardImportPath(path string) string { if path == "" { panic("findStandardImportPath called with empty path") } if search.IsStandardImportPath(path) { if goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) { return filepath.Join(cfg.GOROOT, "src", path) } } return "" } // PackageModuleInfo returns information about the module that provides // a given package. If modules are not enabled or if the package is in the // standard library or if the package was not successfully loaded with // LoadPackages or ImportFromFiles, nil is returned. func PackageModuleInfo(ctx context.Context, pkgpath string) *modinfo.ModulePublic { if isStandardImportPath(pkgpath) || !Enabled() { return nil } m, ok := findModule(loaded, pkgpath) if !ok { return nil } rs := LoadModFile(ctx) return moduleInfo(ctx, rs, m, 0) } func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic { if !Enabled() { return nil } if i := strings.Index(path, "@"); i >= 0 { m := module.Version{Path: path[:i], Version: path[i+1:]} return moduleInfo(ctx, nil, m, 0) } rs := LoadModFile(ctx) var ( v string ok bool ) if rs.depth == lazy { v, ok = rs.rootSelected(path) } if !ok { mg, err := rs.Graph(ctx) if err != nil { base.Fatalf("go: %v", err) } v = mg.Selected(path) } if v == "none" { return &modinfo.ModulePublic{ Path: path, Error: &modinfo.ModuleError{ Err: "module not in current build", }, } } return moduleInfo(ctx, rs, module.Version{Path: path, Version: v}, 0) } // addUpdate fills in m.Update if an updated version is available. func addUpdate(ctx context.Context, m *modinfo.ModulePublic) { if m.Version == "" { return } info, err := Query(ctx, m.Path, "upgrade", m.Version, CheckAllowed) var noVersionErr *NoMatchingVersionError if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) { // Ignore "not found" and "no matching version" errors. // This means the proxy has no matching version or no versions at all. // // We should report other errors though. An attacker that controls the // network shouldn't be able to hide versions by interfering with // the HTTPS connection. An attacker that controls the proxy may still // hide versions, since the "list" and "latest" endpoints are not // authenticated. return } else if err != nil { if m.Error == nil { m.Error = &modinfo.ModuleError{Err: err.Error()} } return } if semver.Compare(info.Version, m.Version) > 0 { m.Update = &modinfo.ModulePublic{ Path: m.Path, Version: info.Version, Time: &info.Time, } } } // addVersions fills in m.Versions with the list of known versions. // Excluded versions will be omitted. If listRetracted is false, retracted // versions will also be omitted. func addVersions(ctx context.Context, m *modinfo.ModulePublic, listRetracted bool) { allowed := CheckAllowed if listRetracted { allowed = CheckExclusions } var err error m.Versions, err = versions(ctx, m.Path, allowed) if err != nil && m.Error == nil { m.Error = &modinfo.ModuleError{Err: err.Error()} } } // addRetraction fills in m.Retracted if the module was retracted by its author. // m.Error is set if there's an error loading retraction information. func addRetraction(ctx context.Context, m *modinfo.ModulePublic) { if m.Version == "" { return } err := CheckRetractions(ctx, module.Version{Path: m.Path, Version: m.Version}) var noVersionErr *NoMatchingVersionError var retractErr *ModuleRetractedError if err == nil || errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) { // Ignore "not found" and "no matching version" errors. // This means the proxy has no matching version or no versions at all. // // We should report other errors though. An attacker that controls the // network shouldn't be able to hide versions by interfering with // the HTTPS connection. An attacker that controls the proxy may still // hide versions, since the "list" and "latest" endpoints are not // authenticated. return } else if errors.As(err, &retractErr) { if len(retractErr.Rationale) == 0 { m.Retracted = []string{"retracted by module author"} } else { m.Retracted = retractErr.Rationale } } else if m.Error == nil { m.Error = &modinfo.ModuleError{Err: err.Error()} } } // addDeprecation fills in m.Deprecated if the module was deprecated by its // author. m.Error is set if there's an error loading deprecation information. func addDeprecation(ctx context.Context, m *modinfo.ModulePublic) { deprecation, err := CheckDeprecation(ctx, module.Version{Path: m.Path, Version: m.Version}) var noVersionErr *NoMatchingVersionError if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) { // Ignore "not found" and "no matching version" errors. // This means the proxy has no matching version or no versions at all. // // We should report other errors though. An attacker that controls the // network shouldn't be able to hide versions by interfering with // the HTTPS connection. An attacker that controls the proxy may still // hide versions, since the "list" and "latest" endpoints are not // authenticated. return } if err != nil { if m.Error == nil { m.Error = &modinfo.ModuleError{Err: err.Error()} } return } m.Deprecated = deprecation } // moduleInfo returns information about module m, loaded from the requirements // in rs (which may be nil to indicate that m was not loaded from a requirement // graph). func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode ListMode) *modinfo.ModulePublic { if m == Target { info := &modinfo.ModulePublic{ Path: m.Path, Version: m.Version, Main: true, } if v, ok := rawGoVersion.Load(Target); ok { info.GoVersion = v.(string) } else { panic("internal error: GoVersion not set for main module") } if HasModRoot() { info.Dir = ModRoot() info.GoMod = ModFilePath() } return info } info := &modinfo.ModulePublic{ Path: m.Path, Version: m.Version, Indirect: rs != nil && !rs.direct[m.Path], } if v, ok := rawGoVersion.Load(m); ok { info.GoVersion = v.(string) } // completeFromModCache fills in the extra fields in m using the module cache. completeFromModCache := func(m *modinfo.ModulePublic) { checksumOk := func(suffix string) bool { return rs == nil || m.Version == "" || cfg.BuildMod == "mod" || modfetch.HaveSum(module.Version{Path: m.Path, Version: m.Version + suffix}) } if m.Version != "" { if q, err := Query(ctx, m.Path, m.Version, "", nil); err != nil { m.Error = &modinfo.ModuleError{Err: err.Error()} } else { m.Version = q.Version m.Time = &q.Time } } mod := module.Version{Path: m.Path, Version: m.Version} if m.GoVersion == "" && checksumOk("/go.mod") { // Load the go.mod file to determine the Go version, since it hasn't // already been populated from rawGoVersion. if summary, err := rawGoModSummary(mod); err == nil && summary.goVersion != "" { m.GoVersion = summary.goVersion } } if m.Version != "" { if checksumOk("/go.mod") { gomod, err := modfetch.CachePath(mod, "mod") if err == nil { if info, err := os.Stat(gomod); err == nil && info.Mode().IsRegular() { m.GoMod = gomod } } } if checksumOk("") { dir, err := modfetch.DownloadDir(mod) if err == nil { m.Dir = dir } } if mode&ListRetracted != 0 { addRetraction(ctx, m) } } } if rs == nil { // If this was an explicitly-versioned argument to 'go mod download' or // 'go list -m', report the actual requested version, not its replacement. completeFromModCache(info) // Will set m.Error in vendor mode. return info } r := Replacement(m) if r.Path == "" { if cfg.BuildMod == "vendor" { // It's tempting to fill in the "Dir" field to point within the vendor // directory, but that would be misleading: the vendor directory contains // a flattened package tree, not complete modules, and it can even // interleave packages from different modules if one module path is a // prefix of the other. } else { completeFromModCache(info) } return info } // Don't hit the network to fill in extra data for replaced modules. // The original resolved Version and Time don't matter enough to be // worth the cost, and we're going to overwrite the GoMod and Dir from the // replacement anyway. See https://golang.org/issue/27859. info.Replace = &modinfo.ModulePublic{ Path: r.Path, Version: r.Version, } if v, ok := rawGoVersion.Load(m); ok { info.Replace.GoVersion = v.(string) } if r.Version == "" { if filepath.IsAbs(r.Path) { info.Replace.Dir = r.Path } else { info.Replace.Dir = filepath.Join(ModRoot(), r.Path) } info.Replace.GoMod = filepath.Join(info.Replace.Dir, "go.mod") } if cfg.BuildMod != "vendor" { completeFromModCache(info.Replace) info.Dir = info.Replace.Dir info.GoMod = info.Replace.GoMod info.Retracted = info.Replace.Retracted } info.GoVersion = info.Replace.GoVersion return info } // PackageBuildInfo returns a string containing module version information // for modules providing packages named by path and deps. path and deps must // name packages that were resolved successfully with LoadPackages. func PackageBuildInfo(path string, deps []string) string { if isStandardImportPath(path) || !Enabled() { return "" } target := mustFindModule(loaded, path, path) mdeps := make(map[module.Version]bool) for _, dep := range deps { if !isStandardImportPath(dep) { mdeps[mustFindModule(loaded, path, dep)] = true } } var mods []module.Version delete(mdeps, target) for mod := range mdeps { mods = append(mods, mod) } module.Sort(mods) var buf bytes.Buffer fmt.Fprintf(&buf, "path\t%s\n", path) writeEntry := func(token string, m module.Version) { mv := m.Version if mv == "" { mv = "(devel)" } fmt.Fprintf(&buf, "%s\t%s\t%s", token, m.Path, mv) if r := Replacement(m); r.Path == "" { fmt.Fprintf(&buf, "\t%s\n", modfetch.Sum(m)) } else { fmt.Fprintf(&buf, "\n=>\t%s\t%s\t%s\n", r.Path, r.Version, modfetch.Sum(r)) } } writeEntry("mod", target) for _, mod := range mods { writeEntry("dep", mod) } return buf.String() } // mustFindModule is like findModule, but it calls base.Fatalf if the // module can't be found. // // TODO(jayconrod): remove this. Callers should use findModule and return // errors instead of relying on base.Fatalf. func mustFindModule(ld *loader, target, path string) module.Version { pkg, ok := ld.pkgCache.Get(path).(*loadPkg) if ok { if pkg.err != nil { base.Fatalf("build %v: cannot load %v: %v", target, path, pkg.err) } return pkg.mod } if path == "command-line-arguments" { return Target } base.Fatalf("build %v: cannot find module for path %v", target, path) panic("unreachable") } // findModule searches for the module that contains the package at path. // If the package was loaded, its containing module and true are returned. // Otherwise, module.Version{} and false are returend. func findModule(ld *loader, path string) (module.Version, bool) { if pkg, ok := ld.pkgCache.Get(path).(*loadPkg); ok { return pkg.mod, pkg.mod != module.Version{} } if path == "command-line-arguments" { return Target, true } return module.Version{}, false } func ModInfoProg(info string, isgccgo bool) []byte { // Inject a variable with the debug information as runtime.modinfo, // but compile it in package main so that it is specific to the binary. // The variable must be a literal so that it will have the correct value // before the initializer for package main runs. // // The runtime startup code refers to the variable, which keeps it live // in all binaries. // // Note: we use an alternate recipe below for gccgo (based on an // init function) due to the fact that gccgo does not support // applying a "//go:linkname" directive to a variable. This has // drawbacks in that other packages may want to look at the module // info in their init functions (see issue 29628), which won't // work for gccgo. See also issue 30344. if !isgccgo { return []byte(fmt.Sprintf(`package main import _ "unsafe" //go:linkname __debug_modinfo__ runtime.modinfo var __debug_modinfo__ = %q `, string(infoStart)+info+string(infoEnd))) } else { return []byte(fmt.Sprintf(`package main import _ "unsafe" //go:linkname __set_debug_modinfo__ runtime.setmodinfo func __set_debug_modinfo__(string) func init() { __set_debug_modinfo__(%q) } `, string(infoStart)+info+string(infoEnd))) } }