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 "strings"
21 "sync"
22 "testing"
23 "time"
24
25 . "cmd/compile/internal/types2"
26 )
27
28 var stdLibImporter = defaultImporter()
29
30 func TestStdlib(t *testing.T) {
31 if testing.Short() {
32 t.Skip("skipping in short mode")
33 }
34
35 testenv.MustHaveGoBuild(t)
36
37
38 dirFiles := make(map[string][]string)
39 root := filepath.Join(testenv.GOROOT(t), "src")
40 walkPkgDirs(root, func(dir string, filenames []string) {
41 dirFiles[dir] = filenames
42 }, t.Error)
43
44 c := &stdlibChecker{
45 dirFiles: dirFiles,
46 pkgs: make(map[string]*futurePackage),
47 }
48
49 start := time.Now()
50
51
52
53
54
55
56
57 cpulimit := make(chan struct{}, runtime.GOMAXPROCS(0))
58 var wg sync.WaitGroup
59
60 for dir := range dirFiles {
61 dir := dir
62
63 cpulimit <- struct{}{}
64 wg.Add(1)
65 go func() {
66 defer func() {
67 wg.Done()
68 <-cpulimit
69 }()
70
71 _, err := c.getDirPackage(dir)
72 if err != nil {
73 t.Errorf("error checking %s: %v", dir, err)
74 }
75 }()
76 }
77
78 wg.Wait()
79
80 if testing.Verbose() {
81 fmt.Println(len(dirFiles), "packages typechecked in", time.Since(start))
82 }
83 }
84
85
86
87 type stdlibChecker struct {
88 dirFiles map[string][]string
89
90 mu sync.Mutex
91 pkgs map[string]*futurePackage
92 }
93
94
95 type futurePackage struct {
96 done chan struct{}
97 pkg *Package
98 err error
99 }
100
101 func (c *stdlibChecker) Import(path string) (*Package, error) {
102 panic("unimplemented: use ImportFrom")
103 }
104
105 func (c *stdlibChecker) ImportFrom(path, dir string, _ ImportMode) (*Package, error) {
106 if path == "unsafe" {
107
108 return Unsafe, nil
109 }
110
111 p, err := build.Default.Import(path, dir, build.FindOnly)
112 if err != nil {
113 return nil, err
114 }
115
116 pkg, err := c.getDirPackage(p.Dir)
117 if pkg != nil {
118
119
120 return pkg, nil
121 }
122 return nil, err
123 }
124
125
126
127
128
129 func (c *stdlibChecker) getDirPackage(dir string) (*Package, error) {
130 c.mu.Lock()
131 fut, ok := c.pkgs[dir]
132 if !ok {
133
134 fut = &futurePackage{
135 done: make(chan struct{}),
136 }
137 c.pkgs[dir] = fut
138 files, ok := c.dirFiles[dir]
139 c.mu.Unlock()
140 if !ok {
141 fut.err = fmt.Errorf("no files for %s", dir)
142 } else {
143
144
145
146 fut.pkg, fut.err = typecheckFiles(dir, files, c)
147 }
148 close(fut.done)
149 } else {
150
151 c.mu.Unlock()
152 <-fut.done
153 }
154 return fut.pkg, fut.err
155 }
156
157
158
159
160
161
162 func firstComment(filename string) (first string) {
163 f, err := os.Open(filename)
164 if err != nil {
165 return ""
166 }
167 defer f.Close()
168
169
170 var buf [4 << 10]byte
171 n, _ := f.Read(buf[:])
172 src := bytes.NewBuffer(buf[:n])
173
174
175 defer func() {
176 if p := recover(); p != nil {
177 if s, ok := p.(string); ok {
178 first = s
179 }
180 }
181 }()
182
183 syntax.CommentsDo(src, func(_, _ uint, text string) {
184 if text[0] != '/' {
185 return
186 }
187
188
189 if text[1] == '*' {
190 text = text[:len(text)-2]
191 }
192 text = strings.TrimSpace(text[2:])
193
194 if strings.HasPrefix(text, "go:build ") {
195 panic("skip")
196 }
197 if first == "" {
198 first = text
199 }
200
201 })
202
203 return
204 }
205
206 func testTestDir(t *testing.T, path string, ignore ...string) {
207 files, err := os.ReadDir(path)
208 if err != nil {
209
210
211
212 if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "test")); os.IsNotExist(err) {
213 if _, err := os.Stat(filepath.Join(testenv.GOROOT(t), "VERSION")); err == nil {
214 t.Skipf("skipping: GOROOT/test not present")
215 }
216 }
217 t.Fatal(err)
218 }
219
220 excluded := make(map[string]bool)
221 for _, filename := range ignore {
222 excluded[filename] = true
223 }
224
225 for _, f := range files {
226
227 if f.IsDir() || !strings.HasSuffix(f.Name(), ".go") || excluded[f.Name()] {
228 continue
229 }
230
231
232 expectErrors := false
233 filename := filepath.Join(path, f.Name())
234 goVersion := ""
235 if comment := firstComment(filename); comment != "" {
236 if strings.Contains(comment, "-goexperiment") {
237 continue
238 }
239 fields := strings.Fields(comment)
240 switch fields[0] {
241 case "skip", "compiledir":
242 continue
243 case "errorcheck":
244 expectErrors = true
245 for _, arg := range fields[1:] {
246 if arg == "-0" || arg == "-+" || arg == "-std" {
247
248
249
250
251 expectErrors = false
252 break
253 }
254 const prefix = "-lang="
255 if strings.HasPrefix(arg, prefix) {
256 goVersion = arg[len(prefix):]
257 }
258 }
259 }
260 }
261
262
263 if testing.Verbose() {
264 fmt.Println("\t", filename)
265 }
266 file, err := syntax.ParseFile(filename, nil, nil, 0)
267 if err == nil {
268 conf := Config{
269 GoVersion: goVersion,
270 Importer: stdLibImporter,
271 }
272 _, err = conf.Check(filename, []*syntax.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 "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 "issue56103.go",
332 "issue52697.go",
333
334
335
336 "bug514.go",
337 "issue40954.go",
338 "issue42032.go",
339 "issue42076.go",
340 "issue46903.go",
341 "issue51733.go",
342 "notinheap2.go",
343 "notinheap3.go",
344 )
345 }
346
347 func TestStdKen(t *testing.T) {
348 testenv.MustHaveGoBuild(t)
349
350 testTestDir(t, filepath.Join(testenv.GOROOT(t), "test", "ken"))
351 }
352
353
354 var excluded = map[string]bool{
355 "builtin": true,
356
357
358 "crypto/internal/edwards25519/field/_asm": true,
359 "crypto/internal/bigmod/_asm": true,
360 }
361
362
363
364
365
366
367 var printPackageMu sync.Mutex
368
369
370 func typecheckFiles(path string, filenames []string, importer Importer) (*Package, error) {
371
372 var files []*syntax.File
373 for _, filename := range filenames {
374 var errs []error
375 errh := func(err error) { errs = append(errs, err) }
376 file, err := syntax.ParseFile(filename, errh, nil, 0)
377 if err != nil {
378 return nil, errors.Join(errs...)
379 }
380
381 files = append(files, file)
382 }
383
384 if testing.Verbose() {
385 printPackageMu.Lock()
386 fmt.Println("package", files[0].PkgName.Value)
387 for _, filename := range filenames {
388 fmt.Println("\t", filename)
389 }
390 printPackageMu.Unlock()
391 }
392
393
394 var errs []error
395 conf := Config{
396 Error: func(err error) {
397 errs = append(errs, err)
398 },
399 Importer: importer,
400 EnableAlias: true,
401 }
402 info := Info{Uses: make(map[*syntax.Name]Object)}
403 pkg, _ := conf.Check(path, files, &info)
404 err := errors.Join(errs...)
405 if err != nil {
406 return pkg, err
407 }
408
409
410
411
412 errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0)
413 for id, obj := range info.Uses {
414 predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError
415 if predeclared == (obj.Pkg() != nil) {
416 posn := 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
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
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 ...interface{})) {
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
472
473
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
484 for _, f := range files {
485 if f.IsDir() && f.Name() != "testdata" {
486 w.walk(filepath.Join(dir, f.Name()))
487 }
488 }
489 }
490
View as plain text