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