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