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/lockedfile"
21 "cmd/go/internal/modfetch"
22 "cmd/go/internal/par"
23 "cmd/go/internal/trace"
24
25 "golang.org/x/mod/modfile"
26 "golang.org/x/mod/module"
27 "golang.org/x/mod/semver"
28 )
29
30 const (
31
32
33
34 narrowAllVersionV = "v1.16"
35
36
37
38
39 lazyLoadingVersionV = "v1.17"
40
41
42
43
44 separateIndirectVersionV = "v1.17"
45 )
46
47 const (
48
49
50
51 go117EnableLazyLoading = true
52
53
54
55
56 go117LazyTODO = false
57 )
58
59 var modFile *modfile.File
60
61
62
63 func modFileGoVersion() string {
64 if modFile == nil {
65 return LatestGoVersion()
66 }
67 if modFile.Go == nil || modFile.Go.Version == "" {
68
69
70
71
72
73
74
75
76
77 return "1.16"
78 }
79 return modFile.Go.Version
80 }
81
82
83
84 type modFileIndex struct {
85 data []byte
86 dataNeedsFix bool
87 module module.Version
88 goVersionV string
89 require map[module.Version]requireMeta
90 replace map[module.Version]module.Version
91 highestReplaced map[string]string
92 exclude map[module.Version]bool
93 }
94
95
96 var index *modFileIndex
97
98 type requireMeta struct {
99 indirect bool
100 }
101
102
103 type modDepth uint8
104
105 const (
106 lazy modDepth = iota
107 eager
108 )
109
110 func modDepthFromGoVersion(goVersion string) modDepth {
111 if !go117EnableLazyLoading {
112 return eager
113 }
114 if semver.Compare("v"+goVersion, lazyLoadingVersionV) < 0 {
115 return eager
116 }
117 return lazy
118 }
119
120
121
122
123 func CheckAllowed(ctx context.Context, m module.Version) error {
124 if err := CheckExclusions(ctx, m); err != nil {
125 return err
126 }
127 if err := CheckRetractions(ctx, m); err != nil {
128 return err
129 }
130 return nil
131 }
132
133
134
135 var ErrDisallowed = errors.New("disallowed module version")
136
137
138
139 func CheckExclusions(ctx context.Context, m module.Version) error {
140 if index != nil && index.exclude[m] {
141 return module.VersionError(m, errExcluded)
142 }
143 return nil
144 }
145
146 var errExcluded = &excludedError{}
147
148 type excludedError struct{}
149
150 func (e *excludedError) Error() string { return "excluded by go.mod" }
151 func (e *excludedError) Is(err error) bool { return err == ErrDisallowed }
152
153
154
155 func CheckRetractions(ctx context.Context, m module.Version) (err error) {
156 defer func() {
157 if retractErr := (*ModuleRetractedError)(nil); err == nil || errors.As(err, &retractErr) {
158 return
159 }
160
161
162 if mErr := (*module.ModuleError)(nil); errors.As(err, &mErr) {
163 err = mErr.Err
164 }
165 err = &retractionLoadingError{m: m, err: err}
166 }()
167
168 if m.Version == "" {
169
170
171 return nil
172 }
173 if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
174
175
176 return nil
177 }
178
179
180
181
182
183
184
185
186
187
188
189
190 rm, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
191 if err != nil {
192 return err
193 }
194 summary, err := rawGoModSummary(rm)
195 if err != nil {
196 return err
197 }
198
199 var rationale []string
200 isRetracted := false
201 for _, r := range summary.retract {
202 if semver.Compare(r.Low, m.Version) <= 0 && semver.Compare(m.Version, r.High) <= 0 {
203 isRetracted = true
204 if r.Rationale != "" {
205 rationale = append(rationale, r.Rationale)
206 }
207 }
208 }
209 if isRetracted {
210 return module.VersionError(m, &ModuleRetractedError{Rationale: rationale})
211 }
212 return nil
213 }
214
215 type ModuleRetractedError struct {
216 Rationale []string
217 }
218
219 func (e *ModuleRetractedError) Error() string {
220 msg := "retracted by module author"
221 if len(e.Rationale) > 0 {
222
223
224 msg += ": " + ShortMessage(e.Rationale[0], "retracted by module author")
225 }
226 return msg
227 }
228
229 func (e *ModuleRetractedError) Is(err error) bool {
230 return err == ErrDisallowed
231 }
232
233 type retractionLoadingError struct {
234 m module.Version
235 err error
236 }
237
238 func (e *retractionLoadingError) Error() string {
239 return fmt.Sprintf("loading module retractions for %v: %v", e.m, e.err)
240 }
241
242 func (e *retractionLoadingError) Unwrap() error {
243 return e.err
244 }
245
246
247
248
249
250
251
252 func ShortMessage(message, emptyDefault string) string {
253 const maxLen = 500
254 if i := strings.Index(message, "\n"); i >= 0 {
255 message = message[:i]
256 }
257 message = strings.TrimSpace(message)
258 if message == "" {
259 return emptyDefault
260 }
261 if len(message) > maxLen {
262 return "(message omitted: too long)"
263 }
264 for _, r := range message {
265 if !unicode.IsGraphic(r) && !unicode.IsSpace(r) {
266 return "(message omitted: contains non-printable characters)"
267 }
268 }
269
270 return message
271 }
272
273
274
275
276
277
278
279
280 func CheckDeprecation(ctx context.Context, m module.Version) (deprecation string, err error) {
281 defer func() {
282 if err != nil {
283 err = fmt.Errorf("loading deprecation for %s: %w", m.Path, err)
284 }
285 }()
286
287 if m.Version == "" {
288
289
290 return "", nil
291 }
292 if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
293
294
295 return "", nil
296 }
297
298 latest, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
299 if err != nil {
300 return "", err
301 }
302 summary, err := rawGoModSummary(latest)
303 if err != nil {
304 return "", err
305 }
306 return summary.deprecated, nil
307 }
308
309
310
311
312 func Replacement(mod module.Version) module.Version {
313 if index != nil {
314 if r, ok := index.replace[mod]; ok {
315 return r
316 }
317 if r, ok := index.replace[module.Version{Path: mod.Path}]; ok {
318 return r
319 }
320 }
321 return module.Version{}
322 }
323
324
325
326 func resolveReplacement(m module.Version) module.Version {
327 if r := Replacement(m); r.Path != "" {
328 return r
329 }
330 return m
331 }
332
333
334
335
336 func indexModFile(data []byte, modFile *modfile.File, needsFix bool) *modFileIndex {
337 i := new(modFileIndex)
338 i.data = data
339 i.dataNeedsFix = needsFix
340
341 i.module = module.Version{}
342 if modFile.Module != nil {
343 i.module = modFile.Module.Mod
344 }
345
346 i.goVersionV = ""
347 if modFile.Go == nil {
348 rawGoVersion.Store(Target, "")
349 } else {
350
351
352 i.goVersionV = "v" + modFile.Go.Version
353 rawGoVersion.Store(Target, modFile.Go.Version)
354 }
355
356 i.require = make(map[module.Version]requireMeta, len(modFile.Require))
357 for _, r := range modFile.Require {
358 i.require[r.Mod] = requireMeta{indirect: r.Indirect}
359 }
360
361 i.replace = make(map[module.Version]module.Version, len(modFile.Replace))
362 for _, r := range modFile.Replace {
363 if prev, dup := i.replace[r.Old]; dup && prev != r.New {
364 base.Fatalf("go: conflicting replacements for %v:\n\t%v\n\t%v", r.Old, prev, r.New)
365 }
366 i.replace[r.Old] = r.New
367 }
368
369 i.highestReplaced = make(map[string]string)
370 for _, r := range modFile.Replace {
371 v, ok := i.highestReplaced[r.Old.Path]
372 if !ok || semver.Compare(r.Old.Version, v) > 0 {
373 i.highestReplaced[r.Old.Path] = r.Old.Version
374 }
375 }
376
377 i.exclude = make(map[module.Version]bool, len(modFile.Exclude))
378 for _, x := range modFile.Exclude {
379 i.exclude[x.Mod] = true
380 }
381
382 return i
383 }
384
385
386
387
388
389 func (i *modFileIndex) modFileIsDirty(modFile *modfile.File) bool {
390 if i == nil {
391 return modFile != nil
392 }
393
394 if i.dataNeedsFix {
395 return true
396 }
397
398 if modFile.Module == nil {
399 if i.module != (module.Version{}) {
400 return true
401 }
402 } else if modFile.Module.Mod != i.module {
403 return true
404 }
405
406 if modFile.Go == nil {
407 if i.goVersionV != "" {
408 return true
409 }
410 } else if "v"+modFile.Go.Version != i.goVersionV {
411 if i.goVersionV == "" && cfg.BuildMod != "mod" {
412
413
414
415 } else {
416 return true
417 }
418 }
419
420 if len(modFile.Require) != len(i.require) ||
421 len(modFile.Replace) != len(i.replace) ||
422 len(modFile.Exclude) != len(i.exclude) {
423 return true
424 }
425
426 for _, r := range modFile.Require {
427 if meta, ok := i.require[r.Mod]; !ok {
428 return true
429 } else if r.Indirect != meta.indirect {
430 if cfg.BuildMod == "readonly" {
431
432
433
434
435 } else {
436 return true
437 }
438 }
439 }
440
441 for _, r := range modFile.Replace {
442 if r.New != i.replace[r.Old] {
443 return true
444 }
445 }
446
447 for _, x := range modFile.Exclude {
448 if !i.exclude[x.Mod] {
449 return true
450 }
451 }
452
453 return false
454 }
455
456
457
458
459
460 var rawGoVersion sync.Map
461
462
463
464
465 type modFileSummary struct {
466 module module.Version
467 goVersion string
468 depth modDepth
469 require []module.Version
470 retract []retraction
471 deprecated string
472 }
473
474
475
476 type retraction struct {
477 modfile.VersionInterval
478 Rationale string
479 }
480
481
482
483
484
485
486
487
488
489
490
491
492 func goModSummary(m module.Version) (*modFileSummary, error) {
493 if m == Target {
494 panic("internal error: goModSummary called on the Target module")
495 }
496
497 if cfg.BuildMod == "vendor" {
498 summary := &modFileSummary{
499 module: module.Version{Path: m.Path},
500 }
501 if vendorVersion[m.Path] != m.Version {
502
503
504 return summary, nil
505 }
506
507
508
509 readVendorList()
510
511
512
513 summary.require = vendorList
514 return summary, nil
515 }
516
517 actual := resolveReplacement(m)
518 if HasModRoot() && cfg.BuildMod == "readonly" && actual.Version != "" {
519 key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"}
520 if !modfetch.HaveSum(key) {
521 suggestion := fmt.Sprintf("; to add it:\n\tgo mod download %s", m.Path)
522 return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion})
523 }
524 }
525 summary, err := rawGoModSummary(actual)
526 if err != nil {
527 return nil, err
528 }
529
530 if actual.Version == "" {
531
532
533
534
535
536
537 } else {
538 if summary.module.Path == "" {
539 return nil, module.VersionError(actual, errors.New("parsing go.mod: missing module line"))
540 }
541
542
543
544
545
546
547
548
549 if mpath := summary.module.Path; mpath != m.Path && mpath != actual.Path {
550 return nil, module.VersionError(actual, fmt.Errorf(`parsing go.mod:
551 module declares its path as: %s
552 but was required as: %s`, mpath, m.Path))
553 }
554 }
555
556 if index != nil && len(index.exclude) > 0 {
557
558
559
560 haveExcludedReqs := false
561 for _, r := range summary.require {
562 if index.exclude[r] {
563 haveExcludedReqs = true
564 break
565 }
566 }
567 if haveExcludedReqs {
568 s := new(modFileSummary)
569 *s = *summary
570 s.require = make([]module.Version, 0, len(summary.require))
571 for _, r := range summary.require {
572 if !index.exclude[r] {
573 s.require = append(s.require, r)
574 }
575 }
576 summary = s
577 }
578 }
579 return summary, nil
580 }
581
582
583
584
585
586
587 func rawGoModSummary(m module.Version) (*modFileSummary, error) {
588 if m == Target {
589 panic("internal error: rawGoModSummary called on the Target module")
590 }
591
592 type cached struct {
593 summary *modFileSummary
594 err error
595 }
596 c := rawGoModSummaryCache.Do(m, func() interface{} {
597 summary := new(modFileSummary)
598 name, data, err := rawGoModData(m)
599 if err != nil {
600 return cached{nil, err}
601 }
602 f, err := modfile.ParseLax(name, data, nil)
603 if err != nil {
604 return cached{nil, module.VersionError(m, fmt.Errorf("parsing %s: %v", base.ShortPath(name), err))}
605 }
606 if f.Module != nil {
607 summary.module = f.Module.Mod
608 summary.deprecated = f.Module.Deprecated
609 }
610 if f.Go != nil && f.Go.Version != "" {
611 rawGoVersion.LoadOrStore(m, f.Go.Version)
612 summary.goVersion = f.Go.Version
613 summary.depth = modDepthFromGoVersion(f.Go.Version)
614 } else {
615 summary.depth = eager
616 }
617 if len(f.Require) > 0 {
618 summary.require = make([]module.Version, 0, len(f.Require))
619 for _, req := range f.Require {
620 summary.require = append(summary.require, req.Mod)
621 }
622 }
623 if len(f.Retract) > 0 {
624 summary.retract = make([]retraction, 0, len(f.Retract))
625 for _, ret := range f.Retract {
626 summary.retract = append(summary.retract, retraction{
627 VersionInterval: ret.VersionInterval,
628 Rationale: ret.Rationale,
629 })
630 }
631 }
632
633 return cached{summary, nil}
634 }).(cached)
635
636 return c.summary, c.err
637 }
638
639 var rawGoModSummaryCache par.Cache
640
641
642
643
644
645
646
647
648 func rawGoModData(m module.Version) (name string, data []byte, err error) {
649 if m.Version == "" {
650
651 dir := m.Path
652 if !filepath.IsAbs(dir) {
653 dir = filepath.Join(ModRoot(), dir)
654 }
655 name = filepath.Join(dir, "go.mod")
656 if gomodActual, ok := fsys.OverlayPath(name); ok {
657
658
659
660 data, err = os.ReadFile(gomodActual)
661 } else {
662 data, err = lockedfile.Read(gomodActual)
663 }
664 if err != nil {
665 return "", nil, module.VersionError(m, fmt.Errorf("reading %s: %v", base.ShortPath(name), err))
666 }
667 } else {
668 if !semver.IsValid(m.Version) {
669
670 base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version)
671 }
672 name = "go.mod"
673 data, err = modfetch.GoMod(m.Path, m.Version)
674 }
675 return name, data, err
676 }
677
678
679
680
681
682
683
684
685
686
687
688 func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (latest module.Version, err error) {
689 type entry struct {
690 latest module.Version
691 err error
692 }
693 e := latestVersionIgnoringRetractionsCache.Do(path, func() interface{} {
694 ctx, span := trace.StartSpan(ctx, "queryLatestVersionIgnoringRetractions "+path)
695 defer span.Done()
696
697 if repl := Replacement(module.Version{Path: path}); repl.Path != "" {
698
699
700 return &entry{latest: repl}
701 }
702
703
704
705 const ignoreSelected = ""
706 var allowAll AllowedFunc
707 rev, err := Query(ctx, path, "latest", ignoreSelected, allowAll)
708 if err != nil {
709 return &entry{err: err}
710 }
711 latest := module.Version{Path: path, Version: rev.Version}
712 if repl := resolveReplacement(latest); repl.Path != "" {
713 latest = repl
714 }
715 return &entry{latest: latest}
716 }).(*entry)
717 return e.latest, e.err
718 }
719
720 var latestVersionIgnoringRetractionsCache par.Cache
721
View as plain text