]> Cypherpunks.ru repositories - gostls13.git/blob - src/go/types/stdlib_test.go
07c92225373ce63e37e3137262f7f9dd6b576085
[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                         fields := strings.Fields(comment)
241                         switch fields[0] {
242                         case "skip", "compiledir":
243                                 continue // ignore this file
244                         case "errorcheck":
245                                 expectErrors = true
246                                 for _, arg := range fields[1:] {
247                                         if arg == "-0" || arg == "-+" || arg == "-std" {
248                                                 // Marked explicitly as not expecting errors (-0),
249                                                 // or marked as compiling runtime/stdlib, which is only done
250                                                 // to trigger runtime/stdlib-only error output.
251                                                 // In both cases, the code should typecheck.
252                                                 expectErrors = false
253                                                 break
254                                         }
255                                         const prefix = "-lang="
256                                         if strings.HasPrefix(arg, prefix) {
257                                                 goVersion = arg[len(prefix):]
258                                         }
259                                 }
260                         }
261                 }
262
263                 // parse and type-check file
264                 file, err := parser.ParseFile(fset, filename, nil, 0)
265                 if err == nil {
266                         conf := Config{
267                                 GoVersion: goVersion,
268                                 Importer:  stdLibImporter,
269                         }
270                         _, err = conf.Check(filename, fset, []*ast.File{file}, nil)
271                 }
272
273                 if expectErrors {
274                         if err == nil {
275                                 t.Errorf("expected errors but found none in %s", filename)
276                         }
277                 } else {
278                         if err != nil {
279                                 t.Error(err)
280                         }
281                 }
282         }
283 }
284
285 func TestStdTest(t *testing.T) {
286         testenv.MustHaveGoBuild(t)
287
288         if testing.Short() && testenv.Builder() == "" {
289                 t.Skip("skipping in short mode")
290         }
291
292         testTestDir(t, filepath.Join(testenv.GOROOT(t), "test"),
293                 "cmplxdivide.go", // also needs file cmplxdivide1.go - ignore
294                 "directive.go",   // tests compiler rejection of bad directive placement - ignore
295                 "directive2.go",  // tests compiler rejection of bad directive placement - ignore
296                 "embedfunc.go",   // tests //go:embed
297                 "embedvers.go",   // tests //go:embed
298                 "linkname2.go",   // go/types doesn't check validity of //go:xxx directives
299                 "linkname3.go",   // go/types doesn't check validity of //go:xxx directives
300         )
301 }
302
303 func TestStdFixed(t *testing.T) {
304         testenv.MustHaveGoBuild(t)
305
306         if testing.Short() && testenv.Builder() == "" {
307                 t.Skip("skipping in short mode")
308         }
309
310         testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "fixedbugs"),
311                 "bug248.go", "bug302.go", "bug369.go", // complex test instructions - ignore
312                 "issue6889.go",   // gc-specific test
313                 "issue11362.go",  // canonical import path check
314                 "issue16369.go",  // go/types handles this correctly - not an issue
315                 "issue18459.go",  // go/types doesn't check validity of //go:xxx directives
316                 "issue18882.go",  // go/types doesn't check validity of //go:xxx directives
317                 "issue20529.go",  // go/types does not have constraints on stack size
318                 "issue22200.go",  // go/types does not have constraints on stack size
319                 "issue22200b.go", // go/types does not have constraints on stack size
320                 "issue25507.go",  // go/types does not have constraints on stack size
321                 "issue20780.go",  // go/types does not have constraints on stack size
322                 "bug251.go",      // go.dev/issue/34333 which was exposed with fix for go.dev/issue/34151
323                 "issue42058a.go", // go/types does not have constraints on channel element size
324                 "issue42058b.go", // go/types does not have constraints on channel element size
325                 "issue48097.go",  // go/types doesn't check validity of //go:xxx directives, and non-init bodyless function
326                 "issue48230.go",  // go/types doesn't check validity of //go:xxx directives
327                 "issue49767.go",  // go/types does not have constraints on channel element size
328                 "issue49814.go",  // go/types does not have constraints on array size
329                 "issue56103.go",  // anonymous interface cycles; will be a type checker error in 1.22
330
331                 // These tests requires runtime/cgo.Incomplete, which is only available on some platforms.
332                 // However, go/types does not know about build constraints.
333                 "bug514.go",
334                 "issue40954.go",
335                 "issue42032.go",
336                 "issue42076.go",
337                 "issue46903.go",
338                 "issue51733.go",
339                 "notinheap2.go",
340                 "notinheap3.go",
341         )
342 }
343
344 func TestStdKen(t *testing.T) {
345         testenv.MustHaveGoBuild(t)
346
347         testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "ken"))
348 }
349
350 // Package paths of excluded packages.
351 var excluded = map[string]bool{
352         "builtin": true,
353
354         // See go.dev/issue/46027: some imports are missing for this submodule.
355         "crypto/internal/edwards25519/field/_asm": true,
356         "crypto/internal/bigmod/_asm":             true,
357 }
358
359 // printPackageMu synchronizes the printing of type-checked package files in
360 // the typecheckFiles function.
361 //
362 // Without synchronization, package files may be interleaved during concurrent
363 // type-checking.
364 var printPackageMu sync.Mutex
365
366 // typecheckFiles typechecks the given package files.
367 func typecheckFiles(path string, filenames []string, importer Importer) (*Package, error) {
368         fset := token.NewFileSet()
369
370         // Parse package files.
371         var files []*ast.File
372         for _, filename := range filenames {
373                 file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
374                 if err != nil {
375                         return nil, err
376                 }
377
378                 files = append(files, file)
379         }
380
381         if testing.Verbose() {
382                 printPackageMu.Lock()
383                 fmt.Println("package", files[0].Name.Name)
384                 for _, filename := range filenames {
385                         fmt.Println("\t", filename)
386                 }
387                 printPackageMu.Unlock()
388         }
389
390         // Typecheck package files.
391         var errs []error
392         conf := Config{
393                 Error: func(err error) {
394                         errs = append(errs, err)
395                 },
396                 Importer: importer,
397         }
398         info := Info{Uses: make(map[*ast.Ident]Object)}
399         pkg, _ := conf.Check(path, fset, files, &info)
400         err := errors.Join(errs...)
401         if err != nil {
402                 return pkg, err
403         }
404
405         // Perform checks of API invariants.
406
407         // All Objects have a package, except predeclared ones.
408         errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0) // (error).Error
409         for id, obj := range info.Uses {
410                 predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError
411                 if predeclared == (obj.Pkg() != nil) {
412                         posn := fset.Position(id.Pos())
413                         if predeclared {
414                                 return nil, fmt.Errorf("%s: predeclared object with package: %s", posn, obj)
415                         } else {
416                                 return nil, fmt.Errorf("%s: user-defined object without package: %s", posn, obj)
417                         }
418                 }
419         }
420
421         return pkg, nil
422 }
423
424 // pkgFilenames returns the list of package filenames for the given directory.
425 func pkgFilenames(dir string, includeTest bool) ([]string, error) {
426         ctxt := build.Default
427         ctxt.CgoEnabled = false
428         pkg, err := ctxt.ImportDir(dir, 0)
429         if err != nil {
430                 if _, nogo := err.(*build.NoGoError); nogo {
431                         return nil, nil // no *.go files, not an error
432                 }
433                 return nil, err
434         }
435         if excluded[pkg.ImportPath] {
436                 return nil, nil
437         }
438         var filenames []string
439         for _, name := range pkg.GoFiles {
440                 filenames = append(filenames, filepath.Join(pkg.Dir, name))
441         }
442         if includeTest {
443                 for _, name := range pkg.TestGoFiles {
444                         filenames = append(filenames, filepath.Join(pkg.Dir, name))
445                 }
446         }
447         return filenames, nil
448 }
449
450 func walkPkgDirs(dir string, pkgh func(dir string, filenames []string), errh func(args ...any)) {
451         w := walker{pkgh, errh}
452         w.walk(dir)
453 }
454
455 type walker struct {
456         pkgh func(dir string, filenames []string)
457         errh func(args ...any)
458 }
459
460 func (w *walker) walk(dir string) {
461         files, err := os.ReadDir(dir)
462         if err != nil {
463                 w.errh(err)
464                 return
465         }
466
467         // apply pkgh to the files in directory dir
468
469         // Don't get test files as these packages are imported.
470         pkgFiles, err := pkgFilenames(dir, false)
471         if err != nil {
472                 w.errh(err)
473                 return
474         }
475         if pkgFiles != nil {
476                 w.pkgh(dir, pkgFiles)
477         }
478
479         // traverse subdirectories, but don't walk into testdata
480         for _, f := range files {
481                 if f.IsDir() && f.Name() != "testdata" {
482                         w.walk(filepath.Join(dir, f.Name()))
483                 }
484         }
485 }