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