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