1
2
3
4
5 package loader
6
7
8
9 import (
10 "errors"
11 "fmt"
12 "go/ast"
13 "go/build"
14 "go/parser"
15 "go/token"
16 "go/types"
17 "os"
18 "path/filepath"
19 "sort"
20 "strings"
21 "sync"
22 "time"
23
24 "golang.org/x/tools/go/ast/astutil"
25 "golang.org/x/tools/go/internal/cgo"
26 "golang.org/x/tools/internal/versions"
27 )
28
29 var ignoreVendor build.ImportMode
30
31 const trace = false
32
33
34
35
36 type Config struct {
37
38
39
40 Fset *token.FileSet
41
42
43
44 ParserMode parser.Mode
45
46
47
48
49
50
51 TypeChecker types.Config
52
53
54
55
56
57
58
59 TypeCheckFuncBodies func(path string) bool
60
61
62
63
64
65
66
67
68 Build *build.Context
69
70
71
72
73 Cwd string
74
75
76
77
78
79 DisplayPath func(path string) string
80
81
82
83
84
85 AllowErrors bool
86
87
88
89
90 CreatePkgs []PkgSpec
91
92
93
94
95
96
97
98
99
100 ImportPkgs map[string]bool
101
102
103
104
105
106
107
108
109
110 FindPackage func(ctxt *build.Context, importPath, fromDir string, mode build.ImportMode) (*build.Package, error)
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129 AfterTypeCheck func(info *PackageInfo, files []*ast.File)
130 }
131
132
133
134
135
136
137
138 type PkgSpec struct {
139 Path string
140 Files []*ast.File
141 Filenames []string
142 }
143
144
145 type Program struct {
146 Fset *token.FileSet
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161 Created []*PackageInfo
162
163
164
165 Imported map[string]*PackageInfo
166
167
168
169
170 AllPackages map[*types.Package]*PackageInfo
171
172
173
174
175 importMap map[string]*types.Package
176 }
177
178
179
180
181
182 type PackageInfo struct {
183 Pkg *types.Package
184 Importable bool
185 TransitivelyErrorFree bool
186 Files []*ast.File
187 Errors []error
188 types.Info
189 dir string
190
191 checker *types.Checker
192 errorFunc func(error)
193 }
194
195 func (info *PackageInfo) String() string { return info.Pkg.Path() }
196
197 func (info *PackageInfo) appendError(err error) {
198 if info.errorFunc != nil {
199 info.errorFunc(err)
200 } else {
201 fmt.Fprintln(os.Stderr, err)
202 }
203 info.Errors = append(info.Errors, err)
204 }
205
206 func (conf *Config) fset() *token.FileSet {
207 if conf.Fset == nil {
208 conf.Fset = token.NewFileSet()
209 }
210 return conf.Fset
211 }
212
213
214
215
216
217
218
219 func (conf *Config) ParseFile(filename string, src interface{}) (*ast.File, error) {
220
221 return parser.ParseFile(conf.fset(), filename, src, conf.ParserMode)
222 }
223
224
225
226 const FromArgsUsage = `
227 <args> is a list of arguments denoting a set of initial packages.
228 It may take one of two forms:
229
230 1. A list of *.go source files.
231
232 All of the specified files are loaded, parsed and type-checked
233 as a single package. All the files must belong to the same directory.
234
235 2. A list of import paths, each denoting a package.
236
237 The package's directory is found relative to the $GOROOT and
238 $GOPATH using similar logic to 'go build', and the *.go files in
239 that directory are loaded, parsed and type-checked as a single
240 package.
241
242 In addition, all *_test.go files in the directory are then loaded
243 and parsed. Those files whose package declaration equals that of
244 the non-*_test.go files are included in the primary package. Test
245 files whose package declaration ends with "_test" are type-checked
246 as another package, the 'external' test package, so that a single
247 import path may denote two packages. (Whether this behaviour is
248 enabled is tool-specific, and may depend on additional flags.)
249
250 A '--' argument terminates the list of packages.
251 `
252
253
254
255
256
257
258
259
260
261
262
263 func (conf *Config) FromArgs(args []string, xtest bool) ([]string, error) {
264 var rest []string
265 for i, arg := range args {
266 if arg == "--" {
267 rest = args[i+1:]
268 args = args[:i]
269 break
270 }
271 }
272
273 if len(args) > 0 && strings.HasSuffix(args[0], ".go") {
274
275
276 for _, arg := range args {
277 if !strings.HasSuffix(arg, ".go") {
278 return nil, fmt.Errorf("named files must be .go files: %s", arg)
279 }
280 }
281 conf.CreateFromFilenames("", args...)
282 } else {
283
284
285 for _, arg := range args {
286 if xtest {
287 conf.ImportWithTests(arg)
288 } else {
289 conf.Import(arg)
290 }
291 }
292 }
293
294 return rest, nil
295 }
296
297
298
299
300 func (conf *Config) CreateFromFilenames(path string, filenames ...string) {
301 conf.CreatePkgs = append(conf.CreatePkgs, PkgSpec{Path: path, Filenames: filenames})
302 }
303
304
305
306 func (conf *Config) CreateFromFiles(path string, files ...*ast.File) {
307 conf.CreatePkgs = append(conf.CreatePkgs, PkgSpec{Path: path, Files: files})
308 }
309
310
311
312
313
314
315
316
317
318
319 func (conf *Config) ImportWithTests(path string) { conf.addImport(path, true) }
320
321
322
323 func (conf *Config) Import(path string) { conf.addImport(path, false) }
324
325 func (conf *Config) addImport(path string, tests bool) {
326 if path == "C" {
327 return
328 }
329 if conf.ImportPkgs == nil {
330 conf.ImportPkgs = make(map[string]bool)
331 }
332 conf.ImportPkgs[path] = conf.ImportPkgs[path] || tests
333 }
334
335
336
337
338
339
340
341 func (prog *Program) PathEnclosingInterval(start, end token.Pos) (pkg *PackageInfo, path []ast.Node, exact bool) {
342 for _, info := range prog.AllPackages {
343 for _, f := range info.Files {
344 if f.Pos() == token.NoPos {
345
346
347
348 continue
349 }
350 if !tokenFileContainsPos(prog.Fset.File(f.Pos()), start) {
351 continue
352 }
353 if path, exact := astutil.PathEnclosingInterval(f, start, end); path != nil {
354 return info, path, exact
355 }
356 }
357 }
358 return nil, nil, false
359 }
360
361
362
363 func (prog *Program) InitialPackages() []*PackageInfo {
364 infos := make([]*PackageInfo, 0, len(prog.Created)+len(prog.Imported))
365 infos = append(infos, prog.Created...)
366 for _, info := range prog.Imported {
367 infos = append(infos, info)
368 }
369 return infos
370 }
371
372
373
374 func (prog *Program) Package(path string) *PackageInfo {
375 if info, ok := prog.AllPackages[prog.importMap[path]]; ok {
376 return info
377 }
378 for _, info := range prog.Created {
379 if path == info.Pkg.Path() {
380 return info
381 }
382 }
383 return nil
384 }
385
386
387
388
389 type importer struct {
390 conf *Config
391 start time.Time
392
393 progMu sync.Mutex
394 prog *Program
395
396
397 findpkgMu sync.Mutex
398 findpkg map[findpkgKey]*findpkgValue
399
400 importedMu sync.Mutex
401 imported map[string]*importInfo
402
403
404
405
406
407
408 graphMu sync.Mutex
409 graph map[string]map[string]bool
410 }
411
412 type findpkgKey struct {
413 importPath string
414 fromDir string
415 mode build.ImportMode
416 }
417
418 type findpkgValue struct {
419 ready chan struct{}
420 bp *build.Package
421 err error
422 }
423
424
425
426
427
428
429 type importInfo struct {
430 path string
431 info *PackageInfo
432 complete chan struct{}
433 }
434
435
436
437 func (ii *importInfo) awaitCompletion() {
438 <-ii.complete
439 }
440
441
442
443 func (ii *importInfo) Complete(info *PackageInfo) {
444 if info == nil {
445 panic("info == nil")
446 }
447 ii.info = info
448 close(ii.complete)
449 }
450
451 type importError struct {
452 path string
453 err error
454 }
455
456
457
458
459
460
461
462
463
464
465
466
467
468 func (conf *Config) Load() (*Program, error) {
469
470 if conf.TypeChecker.Error == nil {
471 conf.TypeChecker.Error = func(e error) { fmt.Fprintln(os.Stderr, e) }
472 }
473
474
475 if conf.Cwd == "" {
476 var err error
477 conf.Cwd, err = os.Getwd()
478 if err != nil {
479 return nil, err
480 }
481 }
482
483
484 if conf.FindPackage == nil {
485 conf.FindPackage = (*build.Context).Import
486 }
487
488 prog := &Program{
489 Fset: conf.fset(),
490 Imported: make(map[string]*PackageInfo),
491 importMap: make(map[string]*types.Package),
492 AllPackages: make(map[*types.Package]*PackageInfo),
493 }
494
495 imp := importer{
496 conf: conf,
497 prog: prog,
498 findpkg: make(map[findpkgKey]*findpkgValue),
499 imported: make(map[string]*importInfo),
500 start: time.Now(),
501 graph: make(map[string]map[string]bool),
502 }
503
504
505
506 var errpkgs []string
507
508
509
510
511 infos, importErrors := imp.importAll("", conf.Cwd, conf.ImportPkgs, ignoreVendor)
512 for _, ie := range importErrors {
513 conf.TypeChecker.Error(ie.err)
514 errpkgs = append(errpkgs, ie.path)
515 }
516 for _, info := range infos {
517 prog.Imported[info.Pkg.Path()] = info
518 }
519
520
521
522 var xtestPkgs []*build.Package
523 for importPath, augment := range conf.ImportPkgs {
524 if !augment {
525 continue
526 }
527
528
529 bp, err := imp.findPackage(importPath, conf.Cwd, ignoreVendor)
530 if err != nil {
531
532
533 continue
534 }
535
536
537 if len(bp.XTestGoFiles) > 0 {
538 xtestPkgs = append(xtestPkgs, bp)
539 }
540
541
542 path := bp.ImportPath
543 imp.importedMu.Lock()
544 ii, ok := imp.imported[path]
545
546 if !ok {
547
548
549
550
551 panic(fmt.Sprintf("imported[%q] not found", path))
552 }
553 if ii == nil {
554
555
556
557
558 panic(fmt.Sprintf("imported[%q] == nil", path))
559 }
560 if ii.info == nil {
561
562
563
564 panic(fmt.Sprintf("imported[%q].info = nil", path))
565 }
566 info := ii.info
567 imp.importedMu.Unlock()
568
569
570 files, errs := imp.conf.parsePackageFiles(bp, 't')
571 for _, err := range errs {
572 info.appendError(err)
573 }
574
575
576
577
578 imp.addFiles(info, files, false)
579 }
580
581 createPkg := func(path, dir string, files []*ast.File, errs []error) {
582 info := imp.newPackageInfo(path, dir)
583 for _, err := range errs {
584 info.appendError(err)
585 }
586
587
588
589
590 imp.addFiles(info, files, false)
591 prog.Created = append(prog.Created, info)
592 }
593
594
595 for _, cp := range conf.CreatePkgs {
596 files, errs := parseFiles(conf.fset(), conf.build(), nil, conf.Cwd, cp.Filenames, conf.ParserMode)
597 files = append(files, cp.Files...)
598
599 path := cp.Path
600 if path == "" {
601 if len(files) > 0 {
602 path = files[0].Name.Name
603 } else {
604 path = "(unnamed)"
605 }
606 }
607
608 dir := conf.Cwd
609 if len(files) > 0 && files[0].Pos().IsValid() {
610 dir = filepath.Dir(conf.fset().File(files[0].Pos()).Name())
611 }
612 createPkg(path, dir, files, errs)
613 }
614
615
616 sort.Sort(byImportPath(xtestPkgs))
617 for _, bp := range xtestPkgs {
618 files, errs := imp.conf.parsePackageFiles(bp, 'x')
619 createPkg(bp.ImportPath+"_test", bp.Dir, files, errs)
620 }
621
622
623
624 if len(prog.Imported)+len(prog.Created) == 0 {
625 return nil, errors.New("no initial packages were loaded")
626 }
627
628
629
630 for _, obj := range prog.importMap {
631 info := prog.AllPackages[obj]
632 if info == nil {
633 prog.AllPackages[obj] = &PackageInfo{Pkg: obj, Importable: true}
634 } else {
635
636 info.checker = nil
637 info.errorFunc = nil
638 }
639 }
640
641 if !conf.AllowErrors {
642
643 for _, info := range prog.AllPackages {
644 if len(info.Errors) > 0 {
645 errpkgs = append(errpkgs, info.Pkg.Path())
646 }
647 }
648 if errpkgs != nil {
649 var more string
650 if len(errpkgs) > 3 {
651 more = fmt.Sprintf(" and %d more", len(errpkgs)-3)
652 errpkgs = errpkgs[:3]
653 }
654 return nil, fmt.Errorf("couldn't load packages due to errors: %s%s",
655 strings.Join(errpkgs, ", "), more)
656 }
657 }
658
659 markErrorFreePackages(prog.AllPackages)
660
661 return prog, nil
662 }
663
664 type byImportPath []*build.Package
665
666 func (b byImportPath) Len() int { return len(b) }
667 func (b byImportPath) Less(i, j int) bool { return b[i].ImportPath < b[j].ImportPath }
668 func (b byImportPath) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
669
670
671
672 func markErrorFreePackages(allPackages map[*types.Package]*PackageInfo) {
673
674 importedBy := make(map[*types.Package]map[*types.Package]bool)
675 for P := range allPackages {
676 for _, Q := range P.Imports() {
677 clients, ok := importedBy[Q]
678 if !ok {
679 clients = make(map[*types.Package]bool)
680 importedBy[Q] = clients
681 }
682 clients[P] = true
683 }
684 }
685
686
687 reachable := make(map[*types.Package]bool)
688 var visit func(*types.Package)
689 visit = func(p *types.Package) {
690 if !reachable[p] {
691 reachable[p] = true
692 for q := range importedBy[p] {
693 visit(q)
694 }
695 }
696 }
697 for _, info := range allPackages {
698 if len(info.Errors) > 0 {
699 visit(info.Pkg)
700 }
701 }
702
703
704 for _, info := range allPackages {
705 if !reachable[info.Pkg] {
706 info.TransitivelyErrorFree = true
707 }
708 }
709 }
710
711
712 func (conf *Config) build() *build.Context {
713 if conf.Build != nil {
714 return conf.Build
715 }
716 return &build.Default
717 }
718
719
720
721
722
723
724
725
726
727
728 func (conf *Config) parsePackageFiles(bp *build.Package, which rune) ([]*ast.File, []error) {
729 if bp.ImportPath == "unsafe" {
730 return nil, nil
731 }
732 var filenames []string
733 switch which {
734 case 'g':
735 filenames = bp.GoFiles
736 case 't':
737 filenames = bp.TestGoFiles
738 case 'x':
739 filenames = bp.XTestGoFiles
740 default:
741 panic(which)
742 }
743
744 files, errs := parseFiles(conf.fset(), conf.build(), conf.DisplayPath, bp.Dir, filenames, conf.ParserMode)
745
746
747 if which == 'g' && bp.CgoFiles != nil {
748 cgofiles, err := cgo.ProcessFiles(bp, conf.fset(), conf.DisplayPath, conf.ParserMode)
749 if err != nil {
750 errs = append(errs, err)
751 } else {
752 files = append(files, cgofiles...)
753 }
754 }
755
756 return files, errs
757 }
758
759
760
761
762
763
764
765
766
767
768 func (imp *importer) doImport(from *PackageInfo, to string) (*types.Package, error) {
769 if to == "C" {
770
771
772
773 return nil, fmt.Errorf(`the loader doesn't cgo-process ad hoc packages like %q; see Go issue 11627`,
774 from.Pkg.Path())
775 }
776
777 bp, err := imp.findPackage(to, from.dir, 0)
778 if err != nil {
779 return nil, err
780 }
781
782
783
784 if bp.ImportPath == "unsafe" {
785 return types.Unsafe, nil
786 }
787
788
789 path := bp.ImportPath
790 imp.importedMu.Lock()
791 ii := imp.imported[path]
792 imp.importedMu.Unlock()
793 if ii == nil {
794 panic("internal error: unexpected import: " + path)
795 }
796 if ii.info != nil {
797 return ii.info.Pkg, nil
798 }
799
800
801 fromPath := from.Pkg.Path()
802 if cycle := imp.findPath(path, fromPath); cycle != nil {
803
804 pos, start := -1, ""
805 for i, s := range cycle {
806 if pos < 0 || s > start {
807 pos, start = i, s
808 }
809 }
810 cycle = append(cycle, cycle[:pos]...)[pos:]
811 cycle = append(cycle, cycle[0])
812 return nil, fmt.Errorf("import cycle: %s", strings.Join(cycle, " -> "))
813 }
814
815 panic("internal error: import of incomplete (yet acyclic) package: " + fromPath)
816 }
817
818
819
820 func (imp *importer) findPackage(importPath, fromDir string, mode build.ImportMode) (*build.Package, error) {
821
822
823 key := findpkgKey{importPath, fromDir, mode}
824 imp.findpkgMu.Lock()
825 v, ok := imp.findpkg[key]
826 if ok {
827
828 imp.findpkgMu.Unlock()
829
830 <-v.ready
831 } else {
832
833
834 v = &findpkgValue{ready: make(chan struct{})}
835 imp.findpkg[key] = v
836 imp.findpkgMu.Unlock()
837
838 ioLimit <- true
839 v.bp, v.err = imp.conf.FindPackage(imp.conf.build(), importPath, fromDir, mode)
840 <-ioLimit
841
842 if _, ok := v.err.(*build.NoGoError); ok {
843 v.err = nil
844 }
845
846 close(v.ready)
847 }
848 return v.bp, v.err
849 }
850
851
852
853
854
855
856
857
858
859 func (imp *importer) importAll(fromPath, fromDir string, imports map[string]bool, mode build.ImportMode) (infos []*PackageInfo, errors []importError) {
860 if fromPath != "" {
861
862
863
864
865 imp.graphMu.Lock()
866 deps, ok := imp.graph[fromPath]
867 if !ok {
868 deps = make(map[string]bool)
869 imp.graph[fromPath] = deps
870 }
871 for importPath := range imports {
872 deps[importPath] = true
873 }
874 imp.graphMu.Unlock()
875 }
876
877 var pending []*importInfo
878 for importPath := range imports {
879 if fromPath != "" {
880 if cycle := imp.findPath(importPath, fromPath); cycle != nil {
881
882
883 if trace {
884 fmt.Fprintf(os.Stderr, "import cycle: %q\n", cycle)
885 }
886 continue
887 }
888 }
889 bp, err := imp.findPackage(importPath, fromDir, mode)
890 if err != nil {
891 errors = append(errors, importError{
892 path: importPath,
893 err: err,
894 })
895 continue
896 }
897 pending = append(pending, imp.startLoad(bp))
898 }
899
900 for _, ii := range pending {
901 ii.awaitCompletion()
902 infos = append(infos, ii.info)
903 }
904
905 return infos, errors
906 }
907
908
909
910 func (imp *importer) findPath(from, to string) []string {
911 imp.graphMu.Lock()
912 defer imp.graphMu.Unlock()
913
914 seen := make(map[string]bool)
915 var search func(stack []string, importPath string) []string
916 search = func(stack []string, importPath string) []string {
917 if !seen[importPath] {
918 seen[importPath] = true
919 stack = append(stack, importPath)
920 if importPath == to {
921 return stack
922 }
923 for x := range imp.graph[importPath] {
924 if p := search(stack, x); p != nil {
925 return p
926 }
927 }
928 }
929 return nil
930 }
931 return search(make([]string, 0, 20), from)
932 }
933
934
935
936
937
938
939
940
941 func (imp *importer) startLoad(bp *build.Package) *importInfo {
942 path := bp.ImportPath
943 imp.importedMu.Lock()
944 ii, ok := imp.imported[path]
945 if !ok {
946 ii = &importInfo{path: path, complete: make(chan struct{})}
947 imp.imported[path] = ii
948 go func() {
949 info := imp.load(bp)
950 ii.Complete(info)
951 }()
952 }
953 imp.importedMu.Unlock()
954
955 return ii
956 }
957
958
959
960 func (imp *importer) load(bp *build.Package) *PackageInfo {
961 info := imp.newPackageInfo(bp.ImportPath, bp.Dir)
962 info.Importable = true
963 files, errs := imp.conf.parsePackageFiles(bp, 'g')
964 for _, err := range errs {
965 info.appendError(err)
966 }
967
968 imp.addFiles(info, files, true)
969
970 imp.progMu.Lock()
971 imp.prog.importMap[bp.ImportPath] = info.Pkg
972 imp.progMu.Unlock()
973
974 return info
975 }
976
977
978
979
980
981
982
983
984 func (imp *importer) addFiles(info *PackageInfo, files []*ast.File, cycleCheck bool) {
985
986 var fromPath string
987 if cycleCheck {
988 fromPath = info.Pkg.Path()
989 }
990
991
992 imp.importAll(fromPath, info.dir, scanImports(files), 0)
993
994 if trace {
995 fmt.Fprintf(os.Stderr, "%s: start %q (%d)\n",
996 time.Since(imp.start), info.Pkg.Path(), len(files))
997 }
998
999
1000
1001 if info.Pkg == types.Unsafe {
1002 if len(files) > 0 {
1003 panic(`"unsafe" package contains unexpected files`)
1004 }
1005 } else {
1006
1007
1008 info.checker.Files(files)
1009 info.Files = append(info.Files, files...)
1010 }
1011
1012 if imp.conf.AfterTypeCheck != nil {
1013 imp.conf.AfterTypeCheck(info, files)
1014 }
1015
1016 if trace {
1017 fmt.Fprintf(os.Stderr, "%s: stop %q\n",
1018 time.Since(imp.start), info.Pkg.Path())
1019 }
1020 }
1021
1022 func (imp *importer) newPackageInfo(path, dir string) *PackageInfo {
1023 var pkg *types.Package
1024 if path == "unsafe" {
1025 pkg = types.Unsafe
1026 } else {
1027 pkg = types.NewPackage(path, "")
1028 }
1029 info := &PackageInfo{
1030 Pkg: pkg,
1031 Info: types.Info{
1032 Types: make(map[ast.Expr]types.TypeAndValue),
1033 Defs: make(map[*ast.Ident]types.Object),
1034 Uses: make(map[*ast.Ident]types.Object),
1035 Implicits: make(map[ast.Node]types.Object),
1036 Instances: make(map[*ast.Ident]types.Instance),
1037 Scopes: make(map[ast.Node]*types.Scope),
1038 Selections: make(map[*ast.SelectorExpr]*types.Selection),
1039 },
1040 errorFunc: imp.conf.TypeChecker.Error,
1041 dir: dir,
1042 }
1043 versions.InitFileVersions(&info.Info)
1044
1045
1046 tc := imp.conf.TypeChecker
1047 tc.IgnoreFuncBodies = false
1048 if f := imp.conf.TypeCheckFuncBodies; f != nil {
1049 tc.IgnoreFuncBodies = !f(path)
1050 }
1051 tc.Importer = closure{imp, info}
1052 tc.Error = info.appendError
1053
1054 info.checker = types.NewChecker(&tc, imp.conf.fset(), pkg, &info.Info)
1055 imp.progMu.Lock()
1056 imp.prog.AllPackages[pkg] = info
1057 imp.progMu.Unlock()
1058 return info
1059 }
1060
1061 type closure struct {
1062 imp *importer
1063 info *PackageInfo
1064 }
1065
1066 func (c closure) Import(to string) (*types.Package, error) { return c.imp.doImport(c.info, to) }
1067
View as plain text