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