github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/modload/build.go (about) 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 5 package modload 6 7 import ( 8 "context" 9 "encoding/hex" 10 "errors" 11 "fmt" 12 "io/fs" 13 "os" 14 "path/filepath" 15 "strings" 16 17 "github.com/go-asm/go/cmd/go/base" 18 "github.com/go-asm/go/cmd/go/cfg" 19 "github.com/go-asm/go/cmd/go/gover" 20 "github.com/go-asm/go/cmd/go/modfetch" 21 "github.com/go-asm/go/cmd/go/modfetch/codehost" 22 "github.com/go-asm/go/cmd/go/modindex" 23 "github.com/go-asm/go/cmd/go/modinfo" 24 "github.com/go-asm/go/cmd/go/search" 25 26 "golang.org/x/mod/module" 27 ) 28 29 var ( 30 infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6") 31 infoEnd, _ = hex.DecodeString("f932433186182072008242104116d8f2") 32 ) 33 34 func isStandardImportPath(path string) bool { 35 return findStandardImportPath(path) != "" 36 } 37 38 func 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. 54 func 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. 71 func 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 86 func 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. 126 func 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. 168 func 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. 216 func 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. 235 func 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. 266 func 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). 292 func 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 } 368 if checksumOk("") { 369 dir, err := modfetch.DownloadDir(ctx, mod) 370 if err == nil { 371 m.Dir = dir 372 } 373 } 374 375 if mode&ListRetracted != 0 { 376 addRetraction(ctx, m) 377 } 378 } 379 } 380 381 if rs == nil { 382 // If this was an explicitly-versioned argument to 'go mod download' or 383 // 'go list -m', report the actual requested version, not its replacement. 384 completeFromModCache(info) // Will set m.Error in vendor mode. 385 return info 386 } 387 388 r := Replacement(m) 389 if r.Path == "" { 390 if cfg.BuildMod == "vendor" { 391 // It's tempting to fill in the "Dir" field to point within the vendor 392 // directory, but that would be misleading: the vendor directory contains 393 // a flattened package tree, not complete modules, and it can even 394 // interleave packages from different modules if one module path is a 395 // prefix of the other. 396 } else { 397 completeFromModCache(info) 398 } 399 return info 400 } 401 402 // Don't hit the network to fill in extra data for replaced modules. 403 // The original resolved Version and Time don't matter enough to be 404 // worth the cost, and we're going to overwrite the GoMod and Dir from the 405 // replacement anyway. See https://golang.org/issue/27859. 406 info.Replace = &modinfo.ModulePublic{ 407 Path: r.Path, 408 Version: r.Version, 409 } 410 if v, ok := rawGoVersion.Load(m); ok { 411 info.Replace.GoVersion = v.(string) 412 } 413 if r.Version == "" { 414 if filepath.IsAbs(r.Path) { 415 info.Replace.Dir = r.Path 416 } else { 417 info.Replace.Dir = filepath.Join(replaceRelativeTo(), r.Path) 418 } 419 info.Replace.GoMod = filepath.Join(info.Replace.Dir, "go.mod") 420 } 421 if cfg.BuildMod != "vendor" { 422 completeFromModCache(info.Replace) 423 info.Dir = info.Replace.Dir 424 info.GoMod = info.Replace.GoMod 425 info.Retracted = info.Replace.Retracted 426 } 427 info.GoVersion = info.Replace.GoVersion 428 return info 429 } 430 431 // findModule searches for the module that contains the package at path. 432 // If the package was loaded, its containing module and true are returned. 433 // Otherwise, module.Version{} and false are returned. 434 func findModule(ld *loader, path string) (module.Version, bool) { 435 if pkg, ok := ld.pkgCache.Get(path); ok { 436 return pkg.mod, pkg.mod != module.Version{} 437 } 438 return module.Version{}, false 439 } 440 441 func ModInfoProg(info string, isgccgo bool) []byte { 442 // Inject an init function to set runtime.modinfo. 443 // This is only used for gccgo - with gc we hand the info directly to the linker. 444 // The init function has the drawback that packages may want to 445 // look at the module info in their init functions (see issue 29628), 446 // which won't work. See also issue 30344. 447 if isgccgo { 448 return fmt.Appendf(nil, `package main 449 import _ "unsafe" 450 //go:linkname __set_debug_modinfo__ runtime.setmodinfo 451 func __set_debug_modinfo__(string) 452 func init() { __set_debug_modinfo__(%q) } 453 `, ModInfoData(info)) 454 } 455 return nil 456 } 457 458 func ModInfoData(info string) []byte { 459 return []byte(string(infoStart) + info + string(infoEnd)) 460 }