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 "internal/goroot" 13 "io/fs" 14 "os" 15 "path/filepath" 16 "strings" 17 18 "cmd/go/internal/base" 19 "cmd/go/internal/cfg" 20 "cmd/go/internal/modfetch" 21 "cmd/go/internal/modinfo" 22 "cmd/go/internal/search" 23 24 "golang.org/x/mod/module" 25 "golang.org/x/mod/semver" 26) 27 28var ( 29 infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6") 30 infoEnd, _ = hex.DecodeString("f932433186182072008242104116d8f2") 31) 32 33func isStandardImportPath(path string) bool { 34 return findStandardImportPath(path) != "" 35} 36 37func findStandardImportPath(path string) string { 38 if path == "" { 39 panic("findStandardImportPath called with empty path") 40 } 41 if search.IsStandardImportPath(path) { 42 if goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) { 43 return filepath.Join(cfg.GOROOT, "src", path) 44 } 45 } 46 return "" 47} 48 49// PackageModuleInfo returns information about the module that provides 50// a given package. If modules are not enabled or if the package is in the 51// standard library or if the package was not successfully loaded with 52// LoadPackages or ImportFromFiles, nil is returned. 53func PackageModuleInfo(ctx context.Context, pkgpath string) *modinfo.ModulePublic { 54 if isStandardImportPath(pkgpath) || !Enabled() { 55 return nil 56 } 57 m, ok := findModule(loaded, pkgpath) 58 if !ok { 59 return nil 60 } 61 62 rs := LoadModFile(ctx) 63 return moduleInfo(ctx, rs, m, 0) 64} 65 66func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic { 67 if !Enabled() { 68 return nil 69 } 70 71 if i := strings.Index(path, "@"); i >= 0 { 72 m := module.Version{Path: path[:i], Version: path[i+1:]} 73 return moduleInfo(ctx, nil, m, 0) 74 } 75 76 rs := LoadModFile(ctx) 77 78 var ( 79 v string 80 ok bool 81 ) 82 if rs.pruning == pruned { 83 v, ok = rs.rootSelected(path) 84 } 85 if !ok { 86 mg, err := rs.Graph(ctx) 87 if err != nil { 88 base.Fatalf("go: %v", err) 89 } 90 v = mg.Selected(path) 91 } 92 93 if v == "none" { 94 return &modinfo.ModulePublic{ 95 Path: path, 96 Error: &modinfo.ModuleError{ 97 Err: "module not in current build", 98 }, 99 } 100 } 101 102 return moduleInfo(ctx, rs, module.Version{Path: path, Version: v}, 0) 103} 104 105// addUpdate fills in m.Update if an updated version is available. 106func addUpdate(ctx context.Context, m *modinfo.ModulePublic) { 107 if m.Version == "" { 108 return 109 } 110 111 info, err := Query(ctx, m.Path, "upgrade", m.Version, CheckAllowed) 112 var noVersionErr *NoMatchingVersionError 113 if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) { 114 // Ignore "not found" and "no matching version" errors. 115 // This means the proxy has no matching version or no versions at all. 116 // 117 // We should report other errors though. An attacker that controls the 118 // network shouldn't be able to hide versions by interfering with 119 // the HTTPS connection. An attacker that controls the proxy may still 120 // hide versions, since the "list" and "latest" endpoints are not 121 // authenticated. 122 return 123 } else if err != nil { 124 if m.Error == nil { 125 m.Error = &modinfo.ModuleError{Err: err.Error()} 126 } 127 return 128 } 129 130 if semver.Compare(info.Version, m.Version) > 0 { 131 m.Update = &modinfo.ModulePublic{ 132 Path: m.Path, 133 Version: info.Version, 134 Time: &info.Time, 135 } 136 } 137} 138 139// addVersions fills in m.Versions with the list of known versions. 140// Excluded versions will be omitted. If listRetracted is false, retracted 141// versions will also be omitted. 142func addVersions(ctx context.Context, m *modinfo.ModulePublic, listRetracted bool) { 143 allowed := CheckAllowed 144 if listRetracted { 145 allowed = CheckExclusions 146 } 147 var err error 148 m.Versions, err = versions(ctx, m.Path, allowed) 149 if err != nil && m.Error == nil { 150 m.Error = &modinfo.ModuleError{Err: err.Error()} 151 } 152} 153 154// addRetraction fills in m.Retracted if the module was retracted by its author. 155// m.Error is set if there's an error loading retraction information. 156func addRetraction(ctx context.Context, m *modinfo.ModulePublic) { 157 if m.Version == "" { 158 return 159 } 160 161 err := CheckRetractions(ctx, module.Version{Path: m.Path, Version: m.Version}) 162 var noVersionErr *NoMatchingVersionError 163 var retractErr *ModuleRetractedError 164 if err == nil || errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) { 165 // Ignore "not found" and "no matching version" errors. 166 // This means the proxy has no matching version or no versions at all. 167 // 168 // We should report other errors though. An attacker that controls the 169 // network shouldn't be able to hide versions by interfering with 170 // the HTTPS connection. An attacker that controls the proxy may still 171 // hide versions, since the "list" and "latest" endpoints are not 172 // authenticated. 173 return 174 } else if errors.As(err, &retractErr) { 175 if len(retractErr.Rationale) == 0 { 176 m.Retracted = []string{"retracted by module author"} 177 } else { 178 m.Retracted = retractErr.Rationale 179 } 180 } else if m.Error == nil { 181 m.Error = &modinfo.ModuleError{Err: err.Error()} 182 } 183} 184 185// addDeprecation fills in m.Deprecated if the module was deprecated by its 186// author. m.Error is set if there's an error loading deprecation information. 187func addDeprecation(ctx context.Context, m *modinfo.ModulePublic) { 188 deprecation, err := CheckDeprecation(ctx, module.Version{Path: m.Path, Version: m.Version}) 189 var noVersionErr *NoMatchingVersionError 190 if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) { 191 // Ignore "not found" and "no matching version" errors. 192 // This means the proxy has no matching version or no versions at all. 193 // 194 // We should report other errors though. An attacker that controls the 195 // network shouldn't be able to hide versions by interfering with 196 // the HTTPS connection. An attacker that controls the proxy may still 197 // hide versions, since the "list" and "latest" endpoints are not 198 // authenticated. 199 return 200 } 201 if err != nil { 202 if m.Error == nil { 203 m.Error = &modinfo.ModuleError{Err: err.Error()} 204 } 205 return 206 } 207 m.Deprecated = deprecation 208} 209 210// moduleInfo returns information about module m, loaded from the requirements 211// in rs (which may be nil to indicate that m was not loaded from a requirement 212// graph). 213func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode ListMode) *modinfo.ModulePublic { 214 if m.Version == "" && MainModules.Contains(m.Path) { 215 info := &modinfo.ModulePublic{ 216 Path: m.Path, 217 Version: m.Version, 218 Main: true, 219 } 220 if v, ok := rawGoVersion.Load(m); ok { 221 info.GoVersion = v.(string) 222 } else { 223 panic("internal error: GoVersion not set for main module") 224 } 225 if modRoot := MainModules.ModRoot(m); modRoot != "" { 226 info.Dir = modRoot 227 info.GoMod = modFilePath(modRoot) 228 } 229 return info 230 } 231 232 info := &modinfo.ModulePublic{ 233 Path: m.Path, 234 Version: m.Version, 235 Indirect: rs != nil && !rs.direct[m.Path], 236 } 237 if v, ok := rawGoVersion.Load(m); ok { 238 info.GoVersion = v.(string) 239 } 240 241 // completeFromModCache fills in the extra fields in m using the module cache. 242 completeFromModCache := func(m *modinfo.ModulePublic) { 243 checksumOk := func(suffix string) bool { 244 return rs == nil || m.Version == "" || cfg.BuildMod == "mod" || 245 modfetch.HaveSum(module.Version{Path: m.Path, Version: m.Version + suffix}) 246 } 247 248 if m.Version != "" { 249 if q, err := Query(ctx, m.Path, m.Version, "", nil); err != nil { 250 m.Error = &modinfo.ModuleError{Err: err.Error()} 251 } else { 252 m.Version = q.Version 253 m.Time = &q.Time 254 } 255 } 256 mod := module.Version{Path: m.Path, Version: m.Version} 257 258 if m.GoVersion == "" && checksumOk("/go.mod") { 259 // Load the go.mod file to determine the Go version, since it hasn't 260 // already been populated from rawGoVersion. 261 if summary, err := rawGoModSummary(mod); err == nil && summary.goVersion != "" { 262 m.GoVersion = summary.goVersion 263 } 264 } 265 266 if m.Version != "" { 267 if checksumOk("/go.mod") { 268 gomod, err := modfetch.CachePath(mod, "mod") 269 if err == nil { 270 if info, err := os.Stat(gomod); err == nil && info.Mode().IsRegular() { 271 m.GoMod = gomod 272 } 273 } 274 } 275 if checksumOk("") { 276 dir, err := modfetch.DownloadDir(mod) 277 if err == nil { 278 m.Dir = dir 279 } 280 } 281 282 if mode&ListRetracted != 0 { 283 addRetraction(ctx, m) 284 } 285 } 286 } 287 288 if rs == nil { 289 // If this was an explicitly-versioned argument to 'go mod download' or 290 // 'go list -m', report the actual requested version, not its replacement. 291 completeFromModCache(info) // Will set m.Error in vendor mode. 292 return info 293 } 294 295 r := Replacement(m) 296 if r.Path == "" { 297 if cfg.BuildMod == "vendor" { 298 // It's tempting to fill in the "Dir" field to point within the vendor 299 // directory, but that would be misleading: the vendor directory contains 300 // a flattened package tree, not complete modules, and it can even 301 // interleave packages from different modules if one module path is a 302 // prefix of the other. 303 } else { 304 completeFromModCache(info) 305 } 306 return info 307 } 308 309 // Don't hit the network to fill in extra data for replaced modules. 310 // The original resolved Version and Time don't matter enough to be 311 // worth the cost, and we're going to overwrite the GoMod and Dir from the 312 // replacement anyway. See https://golang.org/issue/27859. 313 info.Replace = &modinfo.ModulePublic{ 314 Path: r.Path, 315 Version: r.Version, 316 } 317 if v, ok := rawGoVersion.Load(m); ok { 318 info.Replace.GoVersion = v.(string) 319 } 320 if r.Version == "" { 321 if filepath.IsAbs(r.Path) { 322 info.Replace.Dir = r.Path 323 } else { 324 info.Replace.Dir = filepath.Join(replaceRelativeTo(), r.Path) 325 } 326 info.Replace.GoMod = filepath.Join(info.Replace.Dir, "go.mod") 327 } 328 if cfg.BuildMod != "vendor" { 329 completeFromModCache(info.Replace) 330 info.Dir = info.Replace.Dir 331 info.GoMod = info.Replace.GoMod 332 info.Retracted = info.Replace.Retracted 333 } 334 info.GoVersion = info.Replace.GoVersion 335 return info 336} 337 338// findModule searches for the module that contains the package at path. 339// If the package was loaded, its containing module and true are returned. 340// Otherwise, module.Version{} and false are returned. 341func findModule(ld *loader, path string) (module.Version, bool) { 342 if pkg, ok := ld.pkgCache.Get(path).(*loadPkg); ok { 343 return pkg.mod, pkg.mod != module.Version{} 344 } 345 return module.Version{}, false 346} 347 348func ModInfoProg(info string, isgccgo bool) []byte { 349 // Inject an init function to set runtime.modinfo. 350 // This is only used for gccgo - with gc we hand the info directly to the linker. 351 // The init function has the drawback that packages may want to 352 // look at the module info in their init functions (see issue 29628), 353 // which won't work. See also issue 30344. 354 if isgccgo { 355 return []byte(fmt.Sprintf(`package main 356import _ "unsafe" 357//go:linkname __set_debug_modinfo__ runtime.setmodinfo 358func __set_debug_modinfo__(string) 359func init() { __set_debug_modinfo__(%q) } 360`, ModInfoData(info))) 361 } 362 return nil 363} 364 365func ModInfoData(info string) []byte { 366 return []byte(string(infoStart) + info + string(infoEnd)) 367} 368