]> Cypherpunks.ru repositories - gostls13.git/blob - src/go/types/stdlib_test.go
e490dea848b8ad21f325ce1b42b1dd24a3a38b9c
[gostls13.git] / src / go / types / stdlib_test.go
1 // Copyright 2013 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // This file tests types.Check by using it to
6 // typecheck the standard library and tests.
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         "strings"
24         "sync"
25         "testing"
26         "time"
27
28         . "go/types"
29 )
30
31 // The cmd/*/internal packages may have been deleted as part of a binary
32 // release. Import from source instead.
33 //
34 // (See https://golang.org/issue/43232 and
35 // https://github.com/golang/build/blob/df58bbac082bc87c4a3cdfe336d1ffe60bbaa916/cmd/release/release.go#L533-L545.)
36 //
37 // Use the same importer for all std lib tests to
38 // avoid repeated importing of the same packages.
39 var stdLibImporter = importer.ForCompiler(token.NewFileSet(), "source", nil)
40
41 func TestStdlib(t *testing.T) {
42         if testing.Short() {
43                 t.Skip("skipping in short mode")
44         }
45
46         testenv.MustHaveGoBuild(t)
47
48         // Collect non-test files.
49         dirFiles := make(map[string][]string)
50         root := filepath.Join(testenv.GOROOT(t), "src")
51         walkPkgDirs(root, func(dir string, filenames []string) {
52                 dirFiles[dir] = filenames
53         }, t.Error)
54
55         c := &stdlibChecker{
56                 dirFiles: dirFiles,
57                 pkgs:     make(map[string]*futurePackage),
58         }
59
60         start := time.Now()
61
62         // Though we read files while parsing, type-checking is otherwise CPU bound.
63         //
64         // This doesn't achieve great CPU utilization as many packages may block
65         // waiting for a common import, but in combination with the non-deterministic
66         // map iteration below this should provide decent coverage of concurrent
67         // type-checking (see golang/go#47729).
68         cpulimit := make(chan struct{}, runtime.GOMAXPROCS(0))
69         var wg sync.WaitGroup
70
71         for dir := range dirFiles {
72                 dir := dir
73
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 // stdlibChecker implements concurrent type-checking of the packages defined by
97 // dirFiles, which must define a closed set of packages (such as GOROOT/src).
98 type stdlibChecker struct {
99         dirFiles map[string][]string // non-test files per directory; must be pre-populated
100
101         mu   sync.Mutex
102         pkgs map[string]*futurePackage // future cache of type-checking results
103 }
104
105 // A futurePackage is a future result of type-checking.
106 type futurePackage struct {
107         done chan struct{} // guards pkg and err
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                 // unsafe cannot be type checked normally.
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                 // As long as pkg is non-nil, avoid redundant errors related to failed
130                 // imports. TestStdlib will collect errors once for each package.
131                 return pkg, nil
132         }
133         return nil, err
134 }
135
136 // getDirPackage gets the package defined in dir from the future cache.
137 //
138 // If this is the first goroutine requesting the package, getDirPackage
139 // type-checks.
140 func (c *stdlibChecker) getDirPackage(dir string) (*Package, error) {
141         c.mu.Lock()
142         fut, ok := c.pkgs[dir]
143         if !ok {
144                 // First request for this package dir; type check.
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                         // Using dir as the package path here may be inconsistent with the behavior
155                         // of a normal importer, but is sufficient as dir is by construction unique
156                         // to this package.
157                         fut.pkg, fut.err = typecheckFiles(dir, files, c)
158                 }
159                 close(fut.done)
160         } else {
161                 // Otherwise, await the result.
162                 c.mu.Unlock()
163                 <-fut.done
164         }
165         return fut.pkg, fut.err
166 }
167
168 // firstComment returns the contents of the first non-empty comment in
169 // the given file, "skip", or the empty string. No matter the present
170 // comments, if any of them contains a build tag, the result is always
171 // "skip". Only comments before the "package" token and within the first
172 // 4K of the file are considered.
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 // read at most 4KB
181         n, _ := f.Read(src[:])
182
183         var first string
184         var s scanner.Scanner
185         s.Init(fset.AddFile("", fset.Base(), n), src[:n], nil /* ignore errors */, scanner.ScanComments)
186         for {
187                 _, tok, lit := s.Scan()
188                 switch tok {
189                 case token.COMMENT:
190                         // remove trailing */ of multi-line comment
191                         if lit[1] == '*' {
192                                 lit = lit[:len(lit)-2]
193                         }
194                         contents := strings.TrimSpace(lit[2:])
195                         if strings.HasPrefix(contents, "+build ") {
196                                 return "skip"
197                         }
198                         if first == "" {
199                                 first = contents // contents may be "" but that's ok
200                         }
201                         // continue as we may still see build tags
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                 // cmd/distpack deletes GOROOT/test, so skip the test if it isn't present.
213                 // cmd/distpack also requires GOROOT/VERSION to exist, so use that to
214                 // suppress false-positive skips.
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                 // filter directory contents
231                 if f.IsDir() || !strings.HasSuffix(f.Name(), ".go") || excluded[f.Name()] {
232                         continue
233                 }
234
235                 // get per-file instructions
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 // ignore this file
242                         }
243                         fields := strings.Fields(comment)
244                         switch fields[0] {
245                         case "skip", "compiledir":
246                                 continue // ignore this file
247                         case "errorcheck":
248                                 expectErrors = true
249                                 for _, arg := range fields[1:] {
250                                         if arg == "-0" || arg == "-+" || arg == "-std" {
251                                                 // Marked explicitly as not expecting errors (-0),
252                                                 // or marked as compiling runtime/stdlib, which is only done
253                                                 // to trigger runtime/stdlib-only error output.
254                                                 // In both cases, the code should typecheck.
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                 // parse and type-check file
267                 file, err := parser.ParseFile(fset, filename, nil, 0)
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", // also needs file cmplxdivide1.go - ignore
297                 "directive.go",   // tests compiler rejection of bad directive placement - ignore
298                 "directive2.go",  // tests compiler rejection of bad directive placement - ignore
299                 "embedfunc.go",   // tests //go:embed
300                 "embedvers.go",   // tests //go:embed
301                 "linkname2.go",   // go/types doesn't check validity of //go:xxx directives
302                 "linkname3.go",   // go/types doesn't check validity of //go:xxx directives
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", // complex test instructions - ignore
315                 "issue6889.go",   // gc-specific test
316                 "issue11362.go",  // canonical import path check
317                 "issue16369.go",  // go/types handles this correctly - not an issue
318                 "issue18459.go",  // go/types doesn't check validity of //go:xxx directives
319                 "issue18882.go",  // go/types doesn't check validity of //go:xxx directives
320                 "issue20529.go",  // go/types does not have constraints on stack size
321                 "issue22200.go",  // go/types does not have constraints on stack size
322                 "issue22200b.go", // go/types does not have constraints on stack size
323                 "issue25507.go",  // go/types does not have constraints on stack size
324                 "issue20780.go",  // go/types does not have constraints on stack size
325                 "bug251.go",      // go.dev/issue/34333 which was exposed with fix for go.dev/issue/34151
326                 "issue42058a.go", // go/types does not have constraints on channel element size
327                 "issue42058b.go", // go/types does not have constraints on channel element size
328                 "issue48097.go",  // go/types doesn't check validity of //go:xxx directives, and non-init bodyless function
329                 "issue48230.go",  // go/types doesn't check validity of //go:xxx directives
330                 "issue49767.go",  // go/types does not have constraints on channel element size
331                 "issue49814.go",  // go/types does not have constraints on array size
332                 "issue56103.go",  // anonymous interface cycles; will be a type checker error in 1.22
333                 "issue52697.go",  // go/types does not have constraints on stack size
334
335                 // These tests requires runtime/cgo.Incomplete, which is only available on some platforms.
336                 // However, go/types does not know about build constraints.
337                 "bug514.go",
338                 "issue40954.go",
339                 "issue42032.go",
340                 "issue42076.go",
341                 "issue46903.go",
342                 "issue51733.go",
343                 "notinheap2.go",
344                 "notinheap3.go",
345         )
346 }
347
348 func TestStdKen(t *testing.T) {
349         testenv.MustHaveGoBuild(t)
350
351         testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "ken"))
352 }
353
354 // Package paths of excluded packages.
355 var excluded = map[string]bool{
356         "builtin": true,
357
358         // See go.dev/issue/46027: some imports are missing for this submodule.
359         "crypto/internal/edwards25519/field/_asm": true,
360         "crypto/internal/bigmod/_asm":             true,
361 }
362
363 // printPackageMu synchronizes the printing of type-checked package files in
364 // the typecheckFiles function.
365 //
366 // Without synchronization, package files may be interleaved during concurrent
367 // type-checking.
368 var printPackageMu sync.Mutex
369
370 // typecheckFiles typechecks the given package files.
371 func typecheckFiles(path string, filenames []string, importer Importer) (*Package, error) {
372         fset := token.NewFileSet()
373
374         // Parse package files.
375         var files []*ast.File
376         for _, filename := range filenames {
377                 file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
378                 if err != nil {
379                         return nil, err
380                 }
381
382                 files = append(files, file)
383         }
384
385         if testing.Verbose() {
386                 printPackageMu.Lock()
387                 fmt.Println("package", files[0].Name.Name)
388                 for _, filename := range filenames {
389                         fmt.Println("\t", filename)
390                 }
391                 printPackageMu.Unlock()
392         }
393
394         // Typecheck package files.
395         var errs []error
396         conf := Config{
397                 Error: func(err error) {
398                         errs = append(errs, err)
399                 },
400                 Importer: importer,
401         }
402         info := Info{Uses: make(map[*ast.Ident]Object)}
403         pkg, _ := conf.Check(path, fset, files, &info)
404         err := errors.Join(errs...)
405         if err != nil {
406                 return pkg, err
407         }
408
409         // Perform checks of API invariants.
410
411         // All Objects have a package, except predeclared ones.
412         errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0) // (error).Error
413         for id, obj := range info.Uses {
414                 predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError
415                 if predeclared == (obj.Pkg() != nil) {
416                         posn := fset.Position(id.Pos())
417                         if predeclared {
418                                 return nil, fmt.Errorf("%s: predeclared object with package: %s", posn, obj)
419                         } else {
420                                 return nil, fmt.Errorf("%s: user-defined object without package: %s", posn, obj)
421                         }
422                 }
423         }
424
425         return pkg, nil
426 }
427
428 // pkgFilenames returns the list of package filenames for the given directory.
429 func pkgFilenames(dir string, includeTest bool) ([]string, error) {
430         ctxt := build.Default
431         ctxt.CgoEnabled = false
432         pkg, err := ctxt.ImportDir(dir, 0)
433         if err != nil {
434                 if _, nogo := err.(*build.NoGoError); nogo {
435                         return nil, nil // no *.go files, not an error
436                 }
437                 return nil, err
438         }
439         if excluded[pkg.ImportPath] {
440                 return nil, nil
441         }
442         var filenames []string
443         for _, name := range pkg.GoFiles {
444                 filenames = append(filenames, filepath.Join(pkg.Dir, name))
445         }
446         if includeTest {
447                 for _, name := range pkg.TestGoFiles {
448                         filenames = append(filenames, filepath.Join(pkg.Dir, name))
449                 }
450         }
451         return filenames, nil
452 }
453
454 func walkPkgDirs(dir string, pkgh func(dir string, filenames []string), errh func(args ...any)) {
455         w := walker{pkgh, errh}
456         w.walk(dir)
457 }
458
459 type walker struct {
460         pkgh func(dir string, filenames []string)
461         errh func(args ...any)
462 }
463
464 func (w *walker) walk(dir string) {
465         files, err := os.ReadDir(dir)
466         if err != nil {
467                 w.errh(err)
468                 return
469         }
470
471         // apply pkgh to the files in directory dir
472
473         // Don't get test files as these packages are imported.
474         pkgFiles, err := pkgFilenames(dir, false)
475         if err != nil {
476                 w.errh(err)
477                 return
478         }
479         if pkgFiles != nil {
480                 w.pkgh(dir, pkgFiles)
481         }
482
483         // traverse subdirectories, but don't walk into testdata
484         for _, f := range files {
485                 if f.IsDir() && f.Name() != "testdata" {
486                         w.walk(filepath.Join(dir, f.Name()))
487                 }
488         }
489 }