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 }
401 info := Info{Uses: make(map[*syntax.Name]Object)}
402 pkg, _ := conf.Check(path, files, &info)
403 err := errors.Join(errs...)
404 if err != nil {
405 return pkg, err
406 }
407
408
409
410
411 errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0)
412 for id, obj := range info.Uses {
413 predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError
414 if predeclared == (obj.Pkg() != nil) {
415 posn := id.Pos()
416 if predeclared {
417 return nil, fmt.Errorf("%s: predeclared object with package: %s", posn, obj)
418 } else {
419 return nil, fmt.Errorf("%s: user-defined object without package: %s", posn, obj)
420 }
421 }
422 }
423
424 return pkg, nil
425 }
426
427
428 func pkgFilenames(dir string, includeTest bool) ([]string, error) {
429 ctxt := build.Default
430 ctxt.CgoEnabled = false
431 pkg, err := ctxt.ImportDir(dir, 0)
432 if err != nil {
433 if _, nogo := err.(*build.NoGoError); nogo {
434 return nil, nil
435 }
436 return nil, err
437 }
438 if excluded[pkg.ImportPath] {
439 return nil, nil
440 }
441 var filenames []string
442 for _, name := range pkg.GoFiles {
443 filenames = append(filenames, filepath.Join(pkg.Dir, name))
444 }
445 if includeTest {
446 for _, name := range pkg.TestGoFiles {
447 filenames = append(filenames, filepath.Join(pkg.Dir, name))
448 }
449 }
450 return filenames, nil
451 }
452
453 func walkPkgDirs(dir string, pkgh func(dir string, filenames []string), errh func(args ...interface{})) {
454 w := walker{pkgh, errh}
455 w.walk(dir)
456 }
457
458 type walker struct {
459 pkgh func(dir string, filenames []string)
460 errh func(args ...any)
461 }
462
463 func (w *walker) walk(dir string) {
464 files, err := os.ReadDir(dir)
465 if err != nil {
466 w.errh(err)
467 return
468 }
469
470
471
472
473 pkgFiles, err := pkgFilenames(dir, false)
474 if err != nil {
475 w.errh(err)
476 return
477 }
478 if pkgFiles != nil {
479 w.pkgh(dir, pkgFiles)
480 }
481
482
483 for _, f := range files {
484 if f.IsDir() && f.Name() != "testdata" {
485 w.walk(filepath.Join(dir, f.Name()))
486 }
487 }
488 }
489
View as plain text