Source file
src/go/types/stdlib_test.go
1
2
3
4
5
6
7
8 package types_test
9
10 import (
11 "errors"
12 "fmt"
13 "go/ast"
14 "go/build"
15 "go/importer"
16 "go/parser"
17 "go/scanner"
18 "go/token"
19 "internal/testenv"
20 "os"
21 "path/filepath"
22 "runtime"
23 "slices"
24 "strings"
25 "sync"
26 "testing"
27 "time"
28
29 . "go/types"
30 )
31
32
33
34
35
36
37
38
39
40 var stdLibImporter = importer.ForCompiler(token.NewFileSet(), "source", nil)
41
42 func TestStdlib(t *testing.T) {
43 if testing.Short() {
44 t.Skip("skipping in short mode")
45 }
46
47 testenv.MustHaveGoBuild(t)
48
49
50 dirFiles := make(map[string][]string)
51 root := filepath.Join(testenv.GOROOT(t), "src")
52 walkPkgDirs(root, func(dir string, filenames []string) {
53 dirFiles[dir] = filenames
54 }, t.Error)
55
56 c := &stdlibChecker{
57 dirFiles: dirFiles,
58 pkgs: make(map[string]*futurePackage),
59 }
60
61 start := time.Now()
62
63
64
65
66
67
68
69 cpulimit := make(chan struct{}, runtime.GOMAXPROCS(0))
70 var wg sync.WaitGroup
71
72 for dir := range dirFiles {
73 cpulimit <- struct{}{}
74 wg.Add(1)
75 go func() {
76 defer func() {
77 wg.Done()
78 <-cpulimit
79 }()
80
81 _, err := c.getDirPackage(dir)
82 if err != nil {
83 t.Errorf("error checking %s: %v", dir, err)
84 }
85 }()
86 }
87
88 wg.Wait()
89
90 if testing.Verbose() {
91 fmt.Println(len(dirFiles), "packages typechecked in", time.Since(start))
92 }
93 }
94
95
96
97 type stdlibChecker struct {
98 dirFiles map[string][]string
99
100 mu sync.Mutex
101 pkgs map[string]*futurePackage
102 }
103
104
105 type futurePackage struct {
106 done chan struct{}
107 pkg *Package
108 err error
109 }
110
111 func (c *stdlibChecker) Import(path string) (*Package, error) {
112 panic("unimplemented: use ImportFrom")
113 }
114
115 func (c *stdlibChecker) ImportFrom(path, dir string, _ ImportMode) (*Package, error) {
116 if path == "unsafe" {
117
118 return Unsafe, nil
119 }
120
121 p, err := build.Default.Import(path, dir, build.FindOnly)
122 if err != nil {
123 return nil, err
124 }
125
126 pkg, err := c.getDirPackage(p.Dir)
127 if pkg != nil {
128
129
130 return pkg, nil
131 }
132 return nil, err
133 }
134
135
136
137
138
139 func (c *stdlibChecker) getDirPackage(dir string) (*Package, error) {
140 c.mu.Lock()
141 fut, ok := c.pkgs[dir]
142 if !ok {
143
144 fut = &futurePackage{
145 done: make(chan struct{}),
146 }
147 c.pkgs[dir] = fut
148 files, ok := c.dirFiles[dir]
149 c.mu.Unlock()
150 if !ok {
151 fut.err = fmt.Errorf("no files for %s", dir)
152 } else {
153
154
155
156 fut.pkg, fut.err = typecheckFiles(dir, files, c)
157 }
158 close(fut.done)
159 } else {
160
161 c.mu.Unlock()
162 <-fut.done
163 }
164 return fut.pkg, fut.err
165 }
166
167
168
169
170
171
172 func firstComment(filename string) string {
173 f, err := os.Open(filename)
174 if err != nil {
175 return ""
176 }
177 defer f.Close()
178
179 var src [4 << 10]byte
180 n, _ := f.Read(src[:])
181
182 var first string
183 var s scanner.Scanner
184 s.Init(fset.AddFile("", fset.Base(), n), src[:n], nil , scanner.ScanComments)
185 for {
186 _, tok, lit := s.Scan()
187 switch tok {
188 case token.COMMENT:
189
190 if lit[1] == '*' {
191 lit = lit[:len(lit)-2]
192 }
193 contents := strings.TrimSpace(lit[2:])
194 if strings.HasPrefix(contents, "go:build ") {
195 return "skip"
196 }
197 if first == "" {
198 first = contents
199 }
200
201
202 case token.PACKAGE, token.EOF:
203 return first
204 }
205 }
206 }
207
208 func testTestDir(t *testing.T, path string, ignore ...string) {
209 files, err := os.ReadDir(path)
210 if err != nil {
211
212
213
214 if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "test")); os.IsNotExist(err) {
215 if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "VERSION")); err == nil {
216 t.Skipf("skipping: GOROOT/test not present")
217 }
218 }
219 t.Fatal(err)
220 }
221
222 excluded := make(map[string]bool)
223 for _, filename := range ignore {
224 excluded[filename] = true
225 }
226
227 fset := token.NewFileSet()
228 for _, f := range files {
229
230 if f.IsDir() || !strings.HasSuffix(f.Name(), ".go") || excluded[f.Name()] {
231 continue
232 }
233
234
235 expectErrors := false
236 filename := filepath.Join(path, f.Name())
237 goVersion := ""
238 if comment := firstComment(filename); comment != "" {
239 if strings.Contains(comment, "-goexperiment") {
240 continue
241 }
242 fields := strings.Fields(comment)
243 switch fields[0] {
244 case "skip", "compiledir":
245 continue
246 case "errorcheck":
247 expectErrors = true
248 for _, arg := range fields[1:] {
249 if arg == "-0" || arg == "-+" || arg == "-std" {
250
251
252
253
254 expectErrors = false
255 break
256 }
257 const prefix = "-lang="
258 if strings.HasPrefix(arg, prefix) {
259 goVersion = arg[len(prefix):]
260 }
261 }
262 }
263 }
264
265
266 file, err := parser.ParseFile(fset, filename, nil, parser.SkipObjectResolution)
267 if err == nil {
268 conf := Config{
269 GoVersion: goVersion,
270 Importer: stdLibImporter,
271 }
272 _, err = conf.Check(filename, fset, []*ast.File{file}, nil)
273 }
274
275 if expectErrors {
276 if err == nil {
277 t.Errorf("expected errors but found none in %s", filename)
278 }
279 } else {
280 if err != nil {
281 t.Error(err)
282 }
283 }
284 }
285 }
286
287 func TestStdTest(t *testing.T) {
288 testenv.MustHaveGoBuild(t)
289
290 if testing.Short() && testenv.Builder() == "" {
291 t.Skip("skipping in short mode")
292 }
293
294 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test"),
295 "cmplxdivide.go",
296 "directive.go",
297 "directive2.go",
298 "embedfunc.go",
299 "embedvers.go",
300 "linkname2.go",
301 "linkname3.go",
302 )
303 }
304
305 func TestStdFixed(t *testing.T) {
306 testenv.MustHaveGoBuild(t)
307
308 if testing.Short() && testenv.Builder() == "" {
309 t.Skip("skipping in short mode")
310 }
311
312 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "fixedbugs"),
313 "bug248.go", "bug302.go", "bug369.go",
314 "bug398.go",
315 "issue6889.go",
316 "issue11362.go",
317 "issue16369.go",
318 "issue18459.go",
319 "issue18882.go",
320 "issue20027.go",
321 "issue20529.go",
322 "issue22200.go",
323 "issue22200b.go",
324 "issue25507.go",
325 "issue20780.go",
326 "bug251.go",
327 "issue42058a.go",
328 "issue42058b.go",
329 "issue48097.go",
330 "issue48230.go",
331 "issue49767.go",
332 "issue49814.go",
333 "issue78355.go",
334 "issue56103.go",
335 "issue52697.go",
336
337
338
339 "bug514.go",
340 "issue40954.go",
341 "issue42032.go",
342 "issue42076.go",
343 "issue46903.go",
344 "issue51733.go",
345 "notinheap2.go",
346 "notinheap3.go",
347 )
348 }
349
350 func TestStdKen(t *testing.T) {
351 testenv.MustHaveGoBuild(t)
352
353 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "ken"))
354 }
355
356
357 var excluded = map[string]bool{
358 "builtin": true,
359 "cmd/compile/internal/ssa/_gen": true,
360 "crypto/internal/cryptotest/wycheproof/_schema": true,
361 "crypto/internal/cryptotest/x509limbo/_schema": true,
362 "runtime/_mkmalloc": true,
363 "simd/archsimd/_gen/midway": true,
364 "simd/archsimd/_gen/sgutil": true,
365 "simd/archsimd/_gen/simdgen": true,
366 "simd/archsimd/_gen/simdgen/arm64": true,
367 "simd/archsimd/_gen/tmplgen": true,
368 "simd/archsimd/_gen/unify": true,
369 "simd/archsimd/_gen/wasmgen": true,
370 }
371
372
373
374
375
376
377 var printPackageMu sync.Mutex
378
379
380 func typecheckFiles(path string, filenames []string, importer Importer) (*Package, error) {
381 fset := token.NewFileSet()
382
383
384 var files []*ast.File
385 for _, filename := range filenames {
386 file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors|parser.SkipObjectResolution)
387 if err != nil {
388 return nil, err
389 }
390
391 files = append(files, file)
392 }
393
394 if testing.Verbose() {
395 printPackageMu.Lock()
396 fmt.Println("package", files[0].Name.Name)
397 for _, filename := range filenames {
398 fmt.Println("\t", filename)
399 }
400 printPackageMu.Unlock()
401 }
402
403
404 var errs []error
405 conf := Config{
406 Error: func(err error) {
407 errs = append(errs, err)
408 },
409 Importer: importer,
410 }
411 info := Info{Uses: make(map[*ast.Ident]Object)}
412 pkg, _ := conf.Check(path, fset, files, &info)
413 err := errors.Join(errs...)
414 if err != nil {
415 return pkg, err
416 }
417
418
419
420
421 errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0)
422 for id, obj := range info.Uses {
423 predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError
424 if predeclared == (obj.Pkg() != nil) {
425 posn := fset.Position(id.Pos())
426 if predeclared {
427 return nil, fmt.Errorf("%s: predeclared object with package: %s", posn, obj)
428 } else {
429 return nil, fmt.Errorf("%s: user-defined object without package: %s", posn, obj)
430 }
431 }
432 }
433
434 return pkg, nil
435 }
436
437
438 func pkgFilenames(dir string, includeTest bool) ([]string, error) {
439 ctxt := build.Default
440 ctxt.CgoEnabled = false
441 pkg, err := ctxt.ImportDir(dir, 0)
442 if err != nil {
443 if _, nogo := err.(*build.NoGoError); nogo {
444 return nil, nil
445 }
446 return nil, err
447 }
448 if excluded[pkg.ImportPath] {
449 return nil, nil
450 }
451 if slices.Contains(strings.Split(pkg.ImportPath, "/"), "_asm") {
452
453
454 return nil, nil
455 }
456 var filenames []string
457 for _, name := range pkg.GoFiles {
458 filenames = append(filenames, filepath.Join(pkg.Dir, name))
459 }
460 if includeTest {
461 for _, name := range pkg.TestGoFiles {
462 filenames = append(filenames, filepath.Join(pkg.Dir, name))
463 }
464 }
465 return filenames, nil
466 }
467
468 func walkPkgDirs(dir string, pkgh func(dir string, filenames []string), errh func(args ...any)) {
469 w := walker{pkgh, errh}
470 w.walk(dir)
471 }
472
473 type walker struct {
474 pkgh func(dir string, filenames []string)
475 errh func(args ...any)
476 }
477
478 func (w *walker) walk(dir string) {
479 files, err := os.ReadDir(dir)
480 if err != nil {
481 w.errh(err)
482 return
483 }
484
485
486
487
488 pkgFiles, err := pkgFilenames(dir, false)
489 if err != nil {
490 w.errh(err)
491 return
492 }
493 if pkgFiles != nil {
494 w.pkgh(dir, pkgFiles)
495 }
496
497
498 for _, f := range files {
499 if f.IsDir() && f.Name() != "testdata" {
500 w.walk(filepath.Join(dir, f.Name()))
501 }
502 }
503 }
504
View as plain text