1
2
3
4
5 package modload
6
7 import (
8 "context"
9 "errors"
10 "fmt"
11 "os"
12 "path/filepath"
13 "strings"
14 "sync"
15 "unicode"
16
17 "cmd/go/internal/base"
18 "cmd/go/internal/cfg"
19 "cmd/go/internal/fsys"
20 "cmd/go/internal/gover"
21 "cmd/go/internal/lockedfile"
22 "cmd/go/internal/modfetch"
23 "cmd/go/internal/trace"
24 "cmd/internal/par"
25
26 "golang.org/x/mod/modfile"
27 "golang.org/x/mod/module"
28 )
29
30
31
32 func ReadModFile(gomod string, fix modfile.VersionFixer) (data []byte, f *modfile.File, err error) {
33 if fsys.Replaced(gomod) {
34
35
36
37 data, err = os.ReadFile(fsys.Actual(gomod))
38 } else {
39 data, err = lockedfile.Read(gomod)
40 }
41 if err != nil {
42 return nil, nil, err
43 }
44
45 f, err = modfile.Parse(gomod, data, fix)
46 if err != nil {
47 f, laxErr := modfile.ParseLax(gomod, data, fix)
48 if laxErr == nil {
49 if f.Go != nil && gover.Compare(f.Go.Version, gover.Local()) > 0 {
50 toolchain := ""
51 if f.Toolchain != nil {
52 toolchain = f.Toolchain.Name
53 }
54 return nil, nil, &gover.TooNewError{What: base.ShortPath(gomod), GoVersion: f.Go.Version, Toolchain: toolchain}
55 }
56 }
57
58
59 return nil, nil, fmt.Errorf("errors parsing %s:\n%w", base.ShortPath(gomod), shortPathErrorList(err))
60 }
61 if f.Go != nil && gover.Compare(f.Go.Version, gover.Local()) > 0 {
62 toolchain := ""
63 if f.Toolchain != nil {
64 toolchain = f.Toolchain.Name
65 }
66 return nil, nil, &gover.TooNewError{What: base.ShortPath(gomod), GoVersion: f.Go.Version, Toolchain: toolchain}
67 }
68 if f.Module == nil {
69
70 return nil, nil, fmt.Errorf("error reading %s: missing module declaration. To specify the module path:\n\tgo mod edit -module=example.com/mod", base.ShortPath(gomod))
71 } else if err := CheckReservedModulePath(f.Module.Mod.Path); err != nil {
72 return nil, nil, fmt.Errorf("error reading %s: invalid module path: %q", base.ShortPath(gomod), f.Module.Mod.Path)
73 }
74
75 return data, f, err
76 }
77
78 func shortPathErrorList(err error) error {
79 if el, ok := errors.AsType[modfile.ErrorList](err); ok {
80 for i := range el {
81 el[i].Filename = base.ShortPath(el[i].Filename)
82 }
83 }
84 return err
85 }
86
87
88
89 type modFileIndex struct {
90 data []byte
91 dataNeedsFix bool
92 module module.Version
93 goVersion string
94 toolchain string
95 require map[module.Version]requireMeta
96 replace map[module.Version]module.Version
97 exclude map[module.Version]bool
98 ignore []string
99 }
100
101 type requireMeta struct {
102 indirect bool
103 }
104
105
106
107
108 type modPruning uint8
109
110 const (
111 pruned modPruning = iota
112 unpruned
113 workspace
114 )
115
116 func (p modPruning) String() string {
117 switch p {
118 case pruned:
119 return "pruned"
120 case unpruned:
121 return "unpruned"
122 case workspace:
123 return "workspace"
124 default:
125 return fmt.Sprintf("%T(%d)", p, p)
126 }
127 }
128
129 func pruningForGoVersion(goVersion string) modPruning {
130 if gover.Compare(goVersion, gover.ExplicitIndirectVersion) < 0 {
131
132
133 return unpruned
134 }
135 return pruned
136 }
137
138
139
140
141 func (s *State) CheckAllowed(ctx context.Context, m module.Version) error {
142 if err := s.CheckExclusions(ctx, m); err != nil {
143 return err
144 }
145 if err := s.CheckRetractions(ctx, m); err != nil {
146 return err
147 }
148 return nil
149 }
150
151
152
153 var ErrDisallowed = errors.New("disallowed module version")
154
155
156
157 func (s *State) CheckExclusions(ctx context.Context, m module.Version) error {
158 for _, mainModule := range s.MainModules.Versions() {
159 if index := s.MainModules.Index(mainModule); index != nil && index.exclude[m] {
160 return module.VersionError(m, errExcluded)
161 }
162 }
163 return nil
164 }
165
166 var errExcluded = &excludedError{}
167
168 type excludedError struct{}
169
170 func (e *excludedError) Error() string { return "excluded by go.mod" }
171 func (e *excludedError) Is(err error) bool { return err == ErrDisallowed }
172
173
174
175 func (s *State) CheckRetractions(ctx context.Context, m module.Version) (err error) {
176 defer func() {
177 if err == nil {
178 return
179 }
180 if _, ok := errors.AsType[*ModuleRetractedError](err); ok {
181 return
182 }
183
184
185 if mErr, ok := errors.AsType[*module.ModuleError](err); ok {
186 err = mErr.Err
187 }
188 err = &retractionLoadingError{m: m, err: err}
189 }()
190
191 if m.Version == "" {
192
193
194 return nil
195 }
196 if repl := Replacement(s, module.Version{Path: m.Path}); repl.Path != "" {
197
198
199 return nil
200 }
201
202
203
204
205
206
207
208
209
210
211
212
213 rm, err := queryLatestVersionIgnoringRetractions(s, ctx, m.Path)
214 if err != nil {
215 return err
216 }
217 summary, err := rawGoModSummary(s, rm)
218 if err != nil && !errors.Is(err, gover.ErrTooNew) {
219 return err
220 }
221
222 var rationale []string
223 isRetracted := false
224 for _, r := range summary.retract {
225 if gover.ModCompare(m.Path, r.Low, m.Version) <= 0 && gover.ModCompare(m.Path, m.Version, r.High) <= 0 {
226 isRetracted = true
227 if r.Rationale != "" {
228 rationale = append(rationale, r.Rationale)
229 }
230 }
231 }
232 if isRetracted {
233 return module.VersionError(m, &ModuleRetractedError{Rationale: rationale})
234 }
235 return nil
236 }
237
238 type ModuleRetractedError struct {
239 Rationale []string
240 }
241
242 func (e *ModuleRetractedError) Error() string {
243 msg := "retracted by module author"
244 if len(e.Rationale) > 0 {
245
246
247 msg += ": " + ShortMessage(e.Rationale[0], "retracted by module author")
248 }
249 return msg
250 }
251
252 func (e *ModuleRetractedError) Is(err error) bool {
253 return err == ErrDisallowed
254 }
255
256 type retractionLoadingError struct {
257 m module.Version
258 err error
259 }
260
261 func (e *retractionLoadingError) Error() string {
262 return fmt.Sprintf("loading module retractions for %v: %v", e.m, e.err)
263 }
264
265 func (e *retractionLoadingError) Unwrap() error {
266 return e.err
267 }
268
269
270
271
272
273
274
275 func ShortMessage(message, emptyDefault string) string {
276 const maxLen = 500
277 if i := strings.Index(message, "\n"); i >= 0 {
278 message = message[:i]
279 }
280 message = strings.TrimSpace(message)
281 if message == "" {
282 return emptyDefault
283 }
284 if len(message) > maxLen {
285 return "(message omitted: too long)"
286 }
287 for _, r := range message {
288 if !unicode.IsGraphic(r) && !unicode.IsSpace(r) {
289 return "(message omitted: contains non-printable characters)"
290 }
291 }
292
293 return message
294 }
295
296
297
298
299
300
301
302
303 func CheckDeprecation(loaderstate *State, ctx context.Context, m module.Version) (deprecation string, err error) {
304 defer func() {
305 if err != nil {
306 err = fmt.Errorf("loading deprecation for %s: %w", m.Path, err)
307 }
308 }()
309
310 if m.Version == "" {
311
312
313 return "", nil
314 }
315 if repl := Replacement(loaderstate, module.Version{Path: m.Path}); repl.Path != "" {
316
317
318 return "", nil
319 }
320
321 latest, err := queryLatestVersionIgnoringRetractions(loaderstate, ctx, m.Path)
322 if err != nil {
323 return "", err
324 }
325 summary, err := rawGoModSummary(loaderstate, latest)
326 if err != nil && !errors.Is(err, gover.ErrTooNew) {
327 return "", err
328 }
329 return summary.deprecated, nil
330 }
331
332 func replacement(mod module.Version, replace map[module.Version]module.Version) (fromVersion string, to module.Version, ok bool) {
333 if r, ok := replace[mod]; ok {
334 return mod.Version, r, true
335 }
336 if r, ok := replace[module.Version{Path: mod.Path}]; ok {
337 return "", r, true
338 }
339 return "", module.Version{}, false
340 }
341
342
343
344
345 func Replacement(loaderstate *State, mod module.Version) module.Version {
346 r, foundModRoot, _ := replacementFrom(loaderstate, mod)
347 return canonicalizeReplacePath(loaderstate, r, foundModRoot)
348 }
349
350
351
352 func replacementFrom(loaderstate *State, mod module.Version) (r module.Version, modroot string, fromFile string) {
353 foundFrom, found, foundModRoot := "", module.Version{}, ""
354 if loaderstate.MainModules == nil {
355 return module.Version{}, "", ""
356 } else if loaderstate.MainModules.Contains(mod.Path) && mod.Version == "" {
357
358 return module.Version{}, "", ""
359 }
360 if _, r, ok := replacement(mod, loaderstate.MainModules.WorkFileReplaceMap()); ok {
361 return r, "", loaderstate.workFilePath
362 }
363 for _, v := range loaderstate.MainModules.Versions() {
364 if index := loaderstate.MainModules.Index(v); index != nil {
365 if from, r, ok := replacement(mod, index.replace); ok {
366 modRoot := loaderstate.MainModules.ModRoot(v)
367 if foundModRoot != "" && foundFrom != from && found != r {
368 base.Errorf("conflicting replacements found for %v in workspace modules defined by %v and %v",
369 mod, modFilePath(foundModRoot), modFilePath(modRoot))
370 return found, foundModRoot, modFilePath(foundModRoot)
371 }
372 found, foundModRoot = r, modRoot
373 }
374 }
375 }
376 return found, foundModRoot, modFilePath(foundModRoot)
377 }
378
379 func replaceRelativeTo(loaderstate *State) string {
380 if workFilePath := WorkFilePath(loaderstate); workFilePath != "" {
381 return filepath.Dir(workFilePath)
382 }
383 return loaderstate.MainModules.ModRoot(loaderstate.MainModules.mustGetSingleMainModule(loaderstate))
384 }
385
386
387
388
389 func canonicalizeReplacePath(loaderstate *State, r module.Version, modRoot string) module.Version {
390 if filepath.IsAbs(r.Path) || r.Version != "" || modRoot == "" {
391 return r
392 }
393 workFilePath := WorkFilePath(loaderstate)
394 if workFilePath == "" {
395 return r
396 }
397 abs := filepath.Join(modRoot, r.Path)
398 if rel, err := filepath.Rel(filepath.Dir(workFilePath), abs); err == nil {
399 return module.Version{Path: ToDirectoryPath(rel), Version: r.Version}
400 }
401
402
403 return module.Version{Path: ToDirectoryPath(abs), Version: r.Version}
404 }
405
406
407
408
409
410 func resolveReplacement(loaderstate *State, m module.Version) module.Version {
411 if r := Replacement(loaderstate, m); r.Path != "" {
412 return r
413 }
414 return m
415 }
416
417 func toReplaceMap(replacements []*modfile.Replace) map[module.Version]module.Version {
418 replaceMap := make(map[module.Version]module.Version, len(replacements))
419 for _, r := range replacements {
420 if prev, dup := replaceMap[r.Old]; dup && prev != r.New {
421 base.Fatalf("go: conflicting replacements for %v:\n\t%v\n\t%v", r.Old, prev, r.New)
422 }
423 replaceMap[r.Old] = r.New
424 }
425 return replaceMap
426 }
427
428
429
430
431 func indexModFile(data []byte, modFile *modfile.File, mod module.Version, needsFix bool) *modFileIndex {
432 i := new(modFileIndex)
433 i.data = data
434 i.dataNeedsFix = needsFix
435
436 i.module = module.Version{}
437 if modFile.Module != nil {
438 i.module = modFile.Module.Mod
439 }
440
441 i.goVersion = ""
442 if modFile.Go == nil {
443 rawGoVersion.Store(mod, "")
444 } else {
445 i.goVersion = modFile.Go.Version
446 rawGoVersion.Store(mod, modFile.Go.Version)
447 }
448 if modFile.Toolchain != nil {
449 i.toolchain = modFile.Toolchain.Name
450 }
451
452 i.require = make(map[module.Version]requireMeta, len(modFile.Require))
453 for _, r := range modFile.Require {
454 i.require[r.Mod] = requireMeta{indirect: r.Indirect}
455 }
456
457 i.replace = toReplaceMap(modFile.Replace)
458
459 i.exclude = make(map[module.Version]bool, len(modFile.Exclude))
460 for _, x := range modFile.Exclude {
461 i.exclude[x.Mod] = true
462 }
463 if modFile.Ignore != nil {
464 for _, x := range modFile.Ignore {
465 i.ignore = append(i.ignore, x.Path)
466 }
467 }
468 return i
469 }
470
471
472
473
474
475 func (i *modFileIndex) modFileIsDirty(modFile *modfile.File) bool {
476 if i == nil {
477 return modFile != nil
478 }
479
480 if i.dataNeedsFix {
481 return true
482 }
483
484 if modFile.Module == nil {
485 if i.module != (module.Version{}) {
486 return true
487 }
488 } else if modFile.Module.Mod != i.module {
489 return true
490 }
491
492 var goV, toolchain string
493 if modFile.Go != nil {
494 goV = modFile.Go.Version
495 }
496 if modFile.Toolchain != nil {
497 toolchain = modFile.Toolchain.Name
498 }
499
500 if goV != i.goVersion ||
501 toolchain != i.toolchain ||
502 len(modFile.Require) != len(i.require) ||
503 len(modFile.Replace) != len(i.replace) ||
504 len(modFile.Exclude) != len(i.exclude) {
505 return true
506 }
507
508 for _, r := range modFile.Require {
509 if meta, ok := i.require[r.Mod]; !ok {
510 return true
511 } else if r.Indirect != meta.indirect {
512 if cfg.BuildMod == "readonly" {
513
514
515
516
517 } else {
518 return true
519 }
520 }
521 }
522
523 for _, r := range modFile.Replace {
524 if r.New != i.replace[r.Old] {
525 return true
526 }
527 }
528
529 for _, x := range modFile.Exclude {
530 if !i.exclude[x.Mod] {
531 return true
532 }
533 }
534
535 return false
536 }
537
538
539
540
541
542 var rawGoVersion sync.Map
543
544
545
546
547 type modFileSummary struct {
548 module module.Version
549 goVersion string
550 toolchain string
551 ignore []string
552 pruning modPruning
553 require []module.Version
554 retract []retraction
555 deprecated string
556 }
557
558
559
560 type retraction struct {
561 modfile.VersionInterval
562 Rationale string
563 }
564
565
566
567
568
569
570
571
572
573
574
575
576 func goModSummary(loaderstate *State, m module.Version) (*modFileSummary, error) {
577 if m.Version == "" && !loaderstate.inWorkspaceMode() && loaderstate.MainModules.Contains(m.Path) {
578 panic("internal error: goModSummary called on a main module")
579 }
580 if gover.IsToolchain(m.Path) {
581 return rawGoModSummary(loaderstate, m)
582 }
583
584 if cfg.BuildMod == "vendor" {
585 summary := &modFileSummary{
586 module: module.Version{Path: m.Path},
587 }
588
589 readVendorList(VendorDir(loaderstate))
590 if vendorVersion[m.Path] != m.Version {
591
592
593 return summary, nil
594 }
595
596
597
598
599
600 summary.require = vendorList
601 return summary, nil
602 }
603
604 actual := resolveReplacement(loaderstate, m)
605 if mustHaveSums(loaderstate) && actual.Version != "" {
606 key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"}
607 if !modfetch.HaveSum(loaderstate.Fetcher(), key) {
608 suggestion := fmt.Sprintf(" for go.mod file; to add it:\n\tgo mod download %s", m.Path)
609 return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion})
610 }
611 }
612 summary, err := rawGoModSummary(loaderstate, actual)
613 if err != nil {
614 return nil, err
615 }
616
617 if actual.Version == "" {
618
619
620
621
622
623
624 } else {
625 if summary.module.Path == "" {
626 return nil, module.VersionError(actual, errors.New("parsing go.mod: missing module line"))
627 }
628
629
630
631
632
633
634
635
636 if mpath := summary.module.Path; mpath != m.Path && mpath != actual.Path {
637 return nil, module.VersionError(actual,
638 fmt.Errorf("parsing go.mod:\n"+
639 "\tmodule declares its path as: %s\n"+
640 "\t but was required as: %s", mpath, m.Path))
641 }
642 }
643
644 for _, mainModule := range loaderstate.MainModules.Versions() {
645 if index := loaderstate.MainModules.Index(mainModule); index != nil && len(index.exclude) > 0 {
646
647
648
649 haveExcludedReqs := false
650 for _, r := range summary.require {
651 if index.exclude[r] {
652 haveExcludedReqs = true
653 break
654 }
655 }
656 if haveExcludedReqs {
657 s := new(modFileSummary)
658 *s = *summary
659 s.require = make([]module.Version, 0, len(summary.require))
660 for _, r := range summary.require {
661 if !index.exclude[r] {
662 s.require = append(s.require, r)
663 }
664 }
665 summary = s
666 }
667 }
668 }
669 return summary, nil
670 }
671
672
673
674
675
676
677
678
679 func rawGoModSummary(loaderstate *State, m module.Version) (*modFileSummary, error) {
680 if gover.IsToolchain(m.Path) {
681 if m.Path == "go" && gover.Compare(m.Version, gover.GoStrictVersion) >= 0 {
682
683
684
685 return &modFileSummary{module: m, require: []module.Version{{Path: "toolchain", Version: "go" + m.Version}}}, nil
686 }
687 return &modFileSummary{module: m}, nil
688 }
689 if m.Version == "" && !loaderstate.inWorkspaceMode() && loaderstate.MainModules.Contains(m.Path) {
690
691
692
693
694
695 panic("internal error: rawGoModSummary called on a main module")
696 }
697 if m.Version == "" && loaderstate.inWorkspaceMode() && m.Path == "command-line-arguments" {
698
699
700
701 return &modFileSummary{module: m}, nil
702 } else if m.Version == "" && loaderstate.inWorkspaceMode() && loaderstate.MainModules.Contains(m.Path) {
703
704
705
706
707 if mf := loaderstate.MainModules.ModFile(m); mf != nil {
708 return summaryFromModFile(m, loaderstate.MainModules.modFiles[m])
709 }
710 }
711 return rawGoModSummaryCache.Do(m, func() (*modFileSummary, error) {
712 name, data, err := rawGoModData(loaderstate, m)
713 if err != nil {
714 return nil, err
715 }
716 f, err := modfile.ParseLax(name, data, nil)
717 if err != nil {
718 return nil, module.VersionError(m, fmt.Errorf("parsing %s: %v", base.ShortPath(name), err))
719 }
720 return summaryFromModFile(m, f)
721 })
722 }
723
724 func summaryFromModFile(m module.Version, f *modfile.File) (*modFileSummary, error) {
725 summary := new(modFileSummary)
726 if f.Module != nil {
727 summary.module = f.Module.Mod
728 summary.deprecated = f.Module.Deprecated
729 }
730 if f.Go != nil {
731 rawGoVersion.LoadOrStore(m, f.Go.Version)
732 summary.goVersion = f.Go.Version
733 summary.pruning = pruningForGoVersion(f.Go.Version)
734 } else {
735 summary.pruning = unpruned
736 }
737 if f.Toolchain != nil {
738 summary.toolchain = f.Toolchain.Name
739 }
740 if f.Ignore != nil {
741 for _, i := range f.Ignore {
742 summary.ignore = append(summary.ignore, i.Path)
743 }
744 }
745 if len(f.Require) > 0 {
746 summary.require = make([]module.Version, 0, len(f.Require)+1)
747 for _, req := range f.Require {
748 summary.require = append(summary.require, req.Mod)
749 }
750 }
751
752 if len(f.Retract) > 0 {
753 summary.retract = make([]retraction, 0, len(f.Retract))
754 for _, ret := range f.Retract {
755 summary.retract = append(summary.retract, retraction{
756 VersionInterval: ret.VersionInterval,
757 Rationale: ret.Rationale,
758 })
759 }
760 }
761
762
763
764
765 if summary.goVersion != "" && gover.Compare(summary.goVersion, gover.GoStrictVersion) >= 0 {
766 summary.require = append(summary.require, module.Version{Path: "go", Version: summary.goVersion})
767 if gover.Compare(summary.goVersion, gover.Local()) > 0 {
768 return summary, &gover.TooNewError{What: "module " + m.String(), GoVersion: summary.goVersion}
769 }
770 }
771
772 return summary, nil
773 }
774
775 var rawGoModSummaryCache par.ErrCache[module.Version, *modFileSummary]
776
777
778
779
780
781
782
783
784 func rawGoModData(loaderstate *State, m module.Version) (name string, data []byte, err error) {
785 if m.Version == "" {
786 dir := m.Path
787 if !filepath.IsAbs(dir) {
788 if loaderstate.inWorkspaceMode() && loaderstate.MainModules.Contains(m.Path) {
789 dir = loaderstate.MainModules.ModRoot(m)
790 } else {
791
792 dir = filepath.Join(replaceRelativeTo(loaderstate), dir)
793 }
794 }
795 name = filepath.Join(dir, "go.mod")
796 if fsys.Replaced(name) {
797
798
799
800 data, err = os.ReadFile(fsys.Actual(name))
801 } else {
802 data, err = lockedfile.Read(name)
803 }
804 if err != nil {
805 return "", nil, module.VersionError(m, fmt.Errorf("reading %s: %v", base.ShortPath(name), err))
806 }
807 } else {
808 if !gover.ModIsValid(m.Path, m.Version) {
809
810 base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version)
811 }
812 name = "go.mod"
813 data, err = loaderstate.Fetcher().GoMod(context.TODO(), m.Path, m.Version)
814 }
815 return name, data, err
816 }
817
818
819
820
821
822
823
824
825
826
827
828 func queryLatestVersionIgnoringRetractions(loaderstate *State, ctx context.Context, path string) (latest module.Version, err error) {
829 return latestVersionIgnoringRetractionsCache.Do(path, func() (module.Version, error) {
830 ctx, span := trace.StartSpan(ctx, "queryLatestVersionIgnoringRetractions "+path)
831 defer span.Done()
832
833 if repl := Replacement(loaderstate, module.Version{Path: path}); repl.Path != "" {
834
835
836 return repl, nil
837 }
838
839
840
841 const ignoreSelected = ""
842 var allowAll AllowedFunc
843 rev, err := Query(loaderstate, ctx, path, "latest", ignoreSelected, allowAll)
844 if err != nil {
845 return module.Version{}, err
846 }
847 latest := module.Version{Path: path, Version: rev.Version}
848 if repl := resolveReplacement(loaderstate, latest); repl.Path != "" {
849 latest = repl
850 }
851 return latest, nil
852 })
853 }
854
855 var latestVersionIgnoringRetractionsCache par.ErrCache[string, module.Version]
856
857
858
859
860 func ToDirectoryPath(path string) string {
861 if modfile.IsDirectoryPath(path) {
862 return path
863 }
864
865
866 return "./" + filepath.ToSlash(filepath.Clean(path))
867 }
868
View as plain text