| 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 | package ssa_test |
| 6 | |
| 7 | import ( |
| 8 | "bytes" |
| 9 | "fmt" |
| 10 | "go/ast" |
| 11 | "go/build" |
| 12 | "go/importer" |
| 13 | "go/parser" |
| 14 | "go/token" |
| 15 | "go/types" |
| 16 | "os" |
| 17 | "path/filepath" |
| 18 | "reflect" |
| 19 | "sort" |
| 20 | "strings" |
| 21 | "testing" |
| 22 | |
| 23 | "golang.org/x/tools/go/buildutil" |
| 24 | "golang.org/x/tools/go/loader" |
| 25 | "golang.org/x/tools/go/ssa" |
| 26 | "golang.org/x/tools/go/ssa/ssautil" |
| 27 | "golang.org/x/tools/internal/testenv" |
| 28 | "golang.org/x/tools/internal/typeparams" |
| 29 | ) |
| 30 | |
| 31 | func isEmpty(f *ssa.Function) bool { return f.Blocks == nil } |
| 32 | |
| 33 | // Tests that programs partially loaded from gc object files contain |
| 34 | // functions with no code for the external portions, but are otherwise ok. |
| 35 | func TestBuildPackage(t *testing.T) { |
| 36 | testenv.NeedsGoBuild(t) // for importer.Default() |
| 37 | |
| 38 | input := ` |
| 39 | package main |
| 40 | |
| 41 | import ( |
| 42 | "bytes" |
| 43 | "io" |
| 44 | "testing" |
| 45 | ) |
| 46 | |
| 47 | func main() { |
| 48 | var t testing.T |
| 49 | t.Parallel() // static call to external declared method |
| 50 | t.Fail() // static call to promoted external declared method |
| 51 | testing.Short() // static call to external package-level function |
| 52 | |
| 53 | var w io.Writer = new(bytes.Buffer) |
| 54 | w.Write(nil) // interface invoke of external declared method |
| 55 | } |
| 56 | ` |
| 57 | |
| 58 | // Parse the file. |
| 59 | fset := token.NewFileSet() |
| 60 | f, err := parser.ParseFile(fset, "input.go", input, 0) |
| 61 | if err != nil { |
| 62 | t.Error(err) |
| 63 | return |
| 64 | } |
| 65 | |
| 66 | // Build an SSA program from the parsed file. |
| 67 | // Load its dependencies from gc binary export data. |
| 68 | mode := ssa.SanityCheckFunctions |
| 69 | mainPkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer.Default()}, fset, |
| 70 | types.NewPackage("main", ""), []*ast.File{f}, mode) |
| 71 | if err != nil { |
| 72 | t.Error(err) |
| 73 | return |
| 74 | } |
| 75 | |
| 76 | // The main package, its direct and indirect dependencies are loaded. |
| 77 | deps := []string{ |
| 78 | // directly imported dependencies: |
| 79 | "bytes", "io", "testing", |
| 80 | // indirect dependencies mentioned by |
| 81 | // the direct imports' export data |
| 82 | "sync", "unicode", "time", |
| 83 | } |
| 84 | |
| 85 | prog := mainPkg.Prog |
| 86 | all := prog.AllPackages() |
| 87 | if len(all) <= len(deps) { |
| 88 | t.Errorf("unexpected set of loaded packages: %q", all) |
| 89 | } |
| 90 | for _, path := range deps { |
| 91 | pkg := prog.ImportedPackage(path) |
| 92 | if pkg == nil { |
| 93 | t.Errorf("package not loaded: %q", path) |
| 94 | continue |
| 95 | } |
| 96 | |
| 97 | // External packages should have no function bodies (except for wrappers). |
| 98 | isExt := pkg != mainPkg |
| 99 | |
| 100 | // init() |
| 101 | if isExt && !isEmpty(pkg.Func("init")) { |
| 102 | t.Errorf("external package %s has non-empty init", pkg) |
| 103 | } else if !isExt && isEmpty(pkg.Func("init")) { |
| 104 | t.Errorf("main package %s has empty init", pkg) |
| 105 | } |
| 106 | |
| 107 | for _, mem := range pkg.Members { |
| 108 | switch mem := mem.(type) { |
| 109 | case *ssa.Function: |
| 110 | // Functions at package level. |
| 111 | if isExt && !isEmpty(mem) { |
| 112 | t.Errorf("external function %s is non-empty", mem) |
| 113 | } else if !isExt && isEmpty(mem) { |
| 114 | t.Errorf("function %s is empty", mem) |
| 115 | } |
| 116 | |
| 117 | case *ssa.Type: |
| 118 | // Methods of named types T. |
| 119 | // (In this test, all exported methods belong to *T not T.) |
| 120 | if !isExt { |
| 121 | t.Fatalf("unexpected name type in main package: %s", mem) |
| 122 | } |
| 123 | mset := prog.MethodSets.MethodSet(types.NewPointer(mem.Type())) |
| 124 | for i, n := 0, mset.Len(); i < n; i++ { |
| 125 | m := prog.MethodValue(mset.At(i)) |
| 126 | // For external types, only synthetic wrappers have code. |
| 127 | expExt := !strings.Contains(m.Synthetic, "wrapper") |
| 128 | if expExt && !isEmpty(m) { |
| 129 | t.Errorf("external method %s is non-empty: %s", |
| 130 | m, m.Synthetic) |
| 131 | } else if !expExt && isEmpty(m) { |
| 132 | t.Errorf("method function %s is empty: %s", |
| 133 | m, m.Synthetic) |
| 134 | } |
| 135 | } |
| 136 | } |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | expectedCallee := []string{ |
| 141 | "(*testing.T).Parallel", |
| 142 | "(*testing.common).Fail", |
| 143 | "testing.Short", |
| 144 | "N/A", |
| 145 | } |
| 146 | callNum := 0 |
| 147 | for _, b := range mainPkg.Func("main").Blocks { |
| 148 | for _, instr := range b.Instrs { |
| 149 | switch instr := instr.(type) { |
| 150 | case ssa.CallInstruction: |
| 151 | call := instr.Common() |
| 152 | if want := expectedCallee[callNum]; want != "N/A" { |
| 153 | got := call.StaticCallee().String() |
| 154 | if want != got { |
| 155 | t.Errorf("call #%d from main.main: got callee %s, want %s", |
| 156 | callNum, got, want) |
| 157 | } |
| 158 | } |
| 159 | callNum++ |
| 160 | } |
| 161 | } |
| 162 | } |
| 163 | if callNum != 4 { |
| 164 | t.Errorf("in main.main: got %d calls, want %d", callNum, 4) |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | // TestRuntimeTypes tests that (*Program).RuntimeTypes() includes all necessary types. |
| 169 | func TestRuntimeTypes(t *testing.T) { |
| 170 | testenv.NeedsGoBuild(t) // for importer.Default() |
| 171 | |
| 172 | tests := []struct { |
| 173 | input string |
| 174 | want []string |
| 175 | }{ |
| 176 | // An exported package-level type is needed. |
| 177 | {`package A; type T struct{}; func (T) f() {}`, |
| 178 | []string{"*p.T", "p.T"}, |
| 179 | }, |
| 180 | // An unexported package-level type is not needed. |
| 181 | {`package B; type t struct{}; func (t) f() {}`, |
| 182 | nil, |
| 183 | }, |
| 184 | // Subcomponents of type of exported package-level var are needed. |
| 185 | {`package C; import "bytes"; var V struct {*bytes.Buffer}`, |
| 186 | []string{"*bytes.Buffer", "*struct{*bytes.Buffer}", "struct{*bytes.Buffer}"}, |
| 187 | }, |
| 188 | // Subcomponents of type of unexported package-level var are not needed. |
| 189 | {`package D; import "bytes"; var v struct {*bytes.Buffer}`, |
| 190 | nil, |
| 191 | }, |
| 192 | // Subcomponents of type of exported package-level function are needed. |
| 193 | {`package E; import "bytes"; func F(struct {*bytes.Buffer}) {}`, |
| 194 | []string{"*bytes.Buffer", "struct{*bytes.Buffer}"}, |
| 195 | }, |
| 196 | // Subcomponents of type of unexported package-level function are not needed. |
| 197 | {`package F; import "bytes"; func f(struct {*bytes.Buffer}) {}`, |
| 198 | nil, |
| 199 | }, |
| 200 | // Subcomponents of type of exported method of uninstantiated unexported type are not needed. |
| 201 | {`package G; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v x`, |
| 202 | nil, |
| 203 | }, |
| 204 | // ...unless used by MakeInterface. |
| 205 | {`package G2; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v interface{} = x{}`, |
| 206 | []string{"*bytes.Buffer", "*p.x", "p.x", "struct{*bytes.Buffer}"}, |
| 207 | }, |
| 208 | // Subcomponents of type of unexported method are not needed. |
| 209 | {`package I; import "bytes"; type X struct{}; func (X) G(struct {*bytes.Buffer}) {}`, |
| 210 | []string{"*bytes.Buffer", "*p.X", "p.X", "struct{*bytes.Buffer}"}, |
| 211 | }, |
| 212 | // Local types aren't needed. |
| 213 | {`package J; import "bytes"; func f() { type T struct {*bytes.Buffer}; var t T; _ = t }`, |
| 214 | nil, |
| 215 | }, |
| 216 | // ...unless used by MakeInterface. |
| 217 | {`package K; import "bytes"; func f() { type T struct {*bytes.Buffer}; _ = interface{}(T{}) }`, |
| 218 | []string{"*bytes.Buffer", "*p.T", "p.T"}, |
| 219 | }, |
| 220 | // Types used as operand of MakeInterface are needed. |
| 221 | {`package L; import "bytes"; func f() { _ = interface{}(struct{*bytes.Buffer}{}) }`, |
| 222 | []string{"*bytes.Buffer", "struct{*bytes.Buffer}"}, |
| 223 | }, |
| 224 | // MakeInterface is optimized away when storing to a blank. |
| 225 | {`package M; import "bytes"; var _ interface{} = struct{*bytes.Buffer}{}`, |
| 226 | nil, |
| 227 | }, |
| 228 | } |
| 229 | |
| 230 | if typeparams.Enabled { |
| 231 | tests = append(tests, []struct { |
| 232 | input string |
| 233 | want []string |
| 234 | }{ |
| 235 | // MakeInterface does not create runtime type for parameterized types. |
| 236 | {`package N; var g interface{}; func f[S any]() { var v []S; g = v }; `, |
| 237 | nil, |
| 238 | }, |
| 239 | }...) |
| 240 | } |
| 241 | for _, test := range tests { |
| 242 | // Parse the file. |
| 243 | fset := token.NewFileSet() |
| 244 | f, err := parser.ParseFile(fset, "input.go", test.input, 0) |
| 245 | if err != nil { |
| 246 | t.Errorf("test %q: %s", test.input[:15], err) |
| 247 | continue |
| 248 | } |
| 249 | |
| 250 | // Create a single-file main package. |
| 251 | // Load dependencies from gc binary export data. |
| 252 | mode := ssa.SanityCheckFunctions |
| 253 | ssapkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer.Default()}, fset, |
| 254 | types.NewPackage("p", ""), []*ast.File{f}, mode) |
| 255 | if err != nil { |
| 256 | t.Errorf("test %q: %s", test.input[:15], err) |
| 257 | continue |
| 258 | } |
| 259 | |
| 260 | var typstrs []string |
| 261 | for _, T := range ssapkg.Prog.RuntimeTypes() { |
| 262 | typstrs = append(typstrs, T.String()) |
| 263 | } |
| 264 | sort.Strings(typstrs) |
| 265 | |
| 266 | if !reflect.DeepEqual(typstrs, test.want) { |
| 267 | t.Errorf("test 'package %s': got %q, want %q", |
| 268 | f.Name.Name, typstrs, test.want) |
| 269 | } |
| 270 | } |
| 271 | } |
| 272 | |
| 273 | // TestInit tests that synthesized init functions are correctly formed. |
| 274 | // Bare init functions omit calls to dependent init functions and the use of |
| 275 | // an init guard. They are useful in cases where the client uses a different |
| 276 | // calling convention for init functions, or cases where it is easier for a |
| 277 | // client to analyze bare init functions. Both of these aspects are used by |
| 278 | // the llgo compiler for simpler integration with gccgo's runtime library, |
| 279 | // and to simplify the analysis whereby it deduces which stores to globals |
| 280 | // can be lowered to global initializers. |
| 281 | func TestInit(t *testing.T) { |
| 282 | tests := []struct { |
| 283 | mode ssa.BuilderMode |
| 284 | input, want string |
| 285 | }{ |
| 286 | {0, `package A; import _ "errors"; var i int = 42`, |
| 287 | `# Name: A.init |
| 288 | # Package: A |
| 289 | # Synthetic: package initializer |
| 290 | func init(): |
| 291 | 0: entry P:0 S:2 |
| 292 | t0 = *init$guard bool |
| 293 | if t0 goto 2 else 1 |
| 294 | 1: init.start P:1 S:1 |
| 295 | *init$guard = true:bool |
| 296 | t1 = errors.init() () |
| 297 | *i = 42:int |
| 298 | jump 2 |
| 299 | 2: init.done P:2 S:0 |
| 300 | return |
| 301 | |
| 302 | `}, |
| 303 | {ssa.BareInits, `package B; import _ "errors"; var i int = 42`, |
| 304 | `# Name: B.init |
| 305 | # Package: B |
| 306 | # Synthetic: package initializer |
| 307 | func init(): |
| 308 | 0: entry P:0 S:0 |
| 309 | *i = 42:int |
| 310 | return |
| 311 | |
| 312 | `}, |
| 313 | } |
| 314 | for _, test := range tests { |
| 315 | // Create a single-file main package. |
| 316 | var conf loader.Config |
| 317 | f, err := conf.ParseFile("<input>", test.input) |
| 318 | if err != nil { |
| 319 | t.Errorf("test %q: %s", test.input[:15], err) |
| 320 | continue |
| 321 | } |
| 322 | conf.CreateFromFiles(f.Name.Name, f) |
| 323 | |
| 324 | lprog, err := conf.Load() |
| 325 | if err != nil { |
| 326 | t.Errorf("test 'package %s': Load: %s", f.Name.Name, err) |
| 327 | continue |
| 328 | } |
| 329 | prog := ssautil.CreateProgram(lprog, test.mode) |
| 330 | mainPkg := prog.Package(lprog.Created[0].Pkg) |
| 331 | prog.Build() |
| 332 | initFunc := mainPkg.Func("init") |
| 333 | if initFunc == nil { |
| 334 | t.Errorf("test 'package %s': no init function", f.Name.Name) |
| 335 | continue |
| 336 | } |
| 337 | |
| 338 | var initbuf bytes.Buffer |
| 339 | _, err = initFunc.WriteTo(&initbuf) |
| 340 | if err != nil { |
| 341 | t.Errorf("test 'package %s': WriteTo: %s", f.Name.Name, err) |
| 342 | continue |
| 343 | } |
| 344 | |
| 345 | if initbuf.String() != test.want { |
| 346 | t.Errorf("test 'package %s': got %s, want %s", f.Name.Name, initbuf.String(), test.want) |
| 347 | } |
| 348 | } |
| 349 | } |
| 350 | |
| 351 | // TestSyntheticFuncs checks that the expected synthetic functions are |
| 352 | // created, reachable, and not duplicated. |
| 353 | func TestSyntheticFuncs(t *testing.T) { |
| 354 | const input = `package P |
| 355 | type T int |
| 356 | func (T) f() int |
| 357 | func (*T) g() int |
| 358 | var ( |
| 359 | // thunks |
| 360 | a = T.f |
| 361 | b = T.f |
| 362 | c = (struct{T}).f |
| 363 | d = (struct{T}).f |
| 364 | e = (*T).g |
| 365 | f = (*T).g |
| 366 | g = (struct{*T}).g |
| 367 | h = (struct{*T}).g |
| 368 | |
| 369 | // bounds |
| 370 | i = T(0).f |
| 371 | j = T(0).f |
| 372 | k = new(T).g |
| 373 | l = new(T).g |
| 374 | |
| 375 | // wrappers |
| 376 | m interface{} = struct{T}{} |
| 377 | n interface{} = struct{T}{} |
| 378 | o interface{} = struct{*T}{} |
| 379 | p interface{} = struct{*T}{} |
| 380 | q interface{} = new(struct{T}) |
| 381 | r interface{} = new(struct{T}) |
| 382 | s interface{} = new(struct{*T}) |
| 383 | t interface{} = new(struct{*T}) |
| 384 | ) |
| 385 | ` |
| 386 | // Parse |
| 387 | var conf loader.Config |
| 388 | f, err := conf.ParseFile("<input>", input) |
| 389 | if err != nil { |
| 390 | t.Fatalf("parse: %v", err) |
| 391 | } |
| 392 | conf.CreateFromFiles(f.Name.Name, f) |
| 393 | |
| 394 | // Load |
| 395 | lprog, err := conf.Load() |
| 396 | if err != nil { |
| 397 | t.Fatalf("Load: %v", err) |
| 398 | } |
| 399 | |
| 400 | // Create and build SSA |
| 401 | prog := ssautil.CreateProgram(lprog, ssa.BuilderMode(0)) |
| 402 | prog.Build() |
| 403 | |
| 404 | // Enumerate reachable synthetic functions |
| 405 | want := map[string]string{ |
| 406 | "(*P.T).g$bound": "bound method wrapper for func (*P.T).g() int", |
| 407 | "(P.T).f$bound": "bound method wrapper for func (P.T).f() int", |
| 408 | |
| 409 | "(*P.T).g$thunk": "thunk for func (*P.T).g() int", |
| 410 | "(P.T).f$thunk": "thunk for func (P.T).f() int", |
| 411 | "(struct{*P.T}).g$thunk": "thunk for func (*P.T).g() int", |
| 412 | "(struct{P.T}).f$thunk": "thunk for func (P.T).f() int", |
| 413 | |
| 414 | "(*P.T).f": "wrapper for func (P.T).f() int", |
| 415 | "(*struct{*P.T}).f": "wrapper for func (P.T).f() int", |
| 416 | "(*struct{*P.T}).g": "wrapper for func (*P.T).g() int", |
| 417 | "(*struct{P.T}).f": "wrapper for func (P.T).f() int", |
| 418 | "(*struct{P.T}).g": "wrapper for func (*P.T).g() int", |
| 419 | "(struct{*P.T}).f": "wrapper for func (P.T).f() int", |
| 420 | "(struct{*P.T}).g": "wrapper for func (*P.T).g() int", |
| 421 | "(struct{P.T}).f": "wrapper for func (P.T).f() int", |
| 422 | |
| 423 | "P.init": "package initializer", |
| 424 | } |
| 425 | for fn := range ssautil.AllFunctions(prog) { |
| 426 | if fn.Synthetic == "" { |
| 427 | continue |
| 428 | } |
| 429 | name := fn.String() |
| 430 | wantDescr, ok := want[name] |
| 431 | if !ok { |
| 432 | t.Errorf("got unexpected/duplicate func: %q: %q", name, fn.Synthetic) |
| 433 | continue |
| 434 | } |
| 435 | delete(want, name) |
| 436 | |
| 437 | if wantDescr != fn.Synthetic { |
| 438 | t.Errorf("(%s).Synthetic = %q, want %q", name, fn.Synthetic, wantDescr) |
| 439 | } |
| 440 | } |
| 441 | for fn, descr := range want { |
| 442 | t.Errorf("want func: %q: %q", fn, descr) |
| 443 | } |
| 444 | } |
| 445 | |
| 446 | // TestPhiElimination ensures that dead phis, including those that |
| 447 | // participate in a cycle, are properly eliminated. |
| 448 | func TestPhiElimination(t *testing.T) { |
| 449 | const input = ` |
| 450 | package p |
| 451 | |
| 452 | func f() error |
| 453 | |
| 454 | func g(slice []int) { |
| 455 | for { |
| 456 | for range slice { |
| 457 | // e should not be lifted to a dead φ-node. |
| 458 | e := f() |
| 459 | h(e) |
| 460 | } |
| 461 | } |
| 462 | } |
| 463 | |
| 464 | func h(error) |
| 465 | ` |
| 466 | // The SSA code for this function should look something like this: |
| 467 | // 0: |
| 468 | // jump 1 |
| 469 | // 1: |
| 470 | // t0 = len(slice) |
| 471 | // jump 2 |
| 472 | // 2: |
| 473 | // t1 = phi [1: -1:int, 3: t2] |
| 474 | // t2 = t1 + 1:int |
| 475 | // t3 = t2 < t0 |
| 476 | // if t3 goto 3 else 1 |
| 477 | // 3: |
| 478 | // t4 = f() |
| 479 | // t5 = h(t4) |
| 480 | // jump 2 |
| 481 | // |
| 482 | // But earlier versions of the SSA construction algorithm would |
| 483 | // additionally generate this cycle of dead phis: |
| 484 | // |
| 485 | // 1: |
| 486 | // t7 = phi [0: nil:error, 2: t8] #e |
| 487 | // ... |
| 488 | // 2: |
| 489 | // t8 = phi [1: t7, 3: t4] #e |
| 490 | // ... |
| 491 | |
| 492 | // Parse |
| 493 | var conf loader.Config |
| 494 | f, err := conf.ParseFile("<input>", input) |
| 495 | if err != nil { |
| 496 | t.Fatalf("parse: %v", err) |
| 497 | } |
| 498 | conf.CreateFromFiles("p", f) |
| 499 | |
| 500 | // Load |
| 501 | lprog, err := conf.Load() |
| 502 | if err != nil { |
| 503 | t.Fatalf("Load: %v", err) |
| 504 | } |
| 505 | |
| 506 | // Create and build SSA |
| 507 | prog := ssautil.CreateProgram(lprog, ssa.BuilderMode(0)) |
| 508 | p := prog.Package(lprog.Package("p").Pkg) |
| 509 | p.Build() |
| 510 | g := p.Func("g") |
| 511 | |
| 512 | phis := 0 |
| 513 | for _, b := range g.Blocks { |
| 514 | for _, instr := range b.Instrs { |
| 515 | if _, ok := instr.(*ssa.Phi); ok { |
| 516 | phis++ |
| 517 | } |
| 518 | } |
| 519 | } |
| 520 | if phis != 1 { |
| 521 | g.WriteTo(os.Stderr) |
| 522 | t.Errorf("expected a single Phi (for the range index), got %d", phis) |
| 523 | } |
| 524 | } |
| 525 | |
| 526 | // TestGenericDecls ensures that *unused* generic types, methods and functions |
| 527 | // signatures can be built. |
| 528 | // |
| 529 | // TODO(taking): Add calls from non-generic functions to instantiations of generic functions. |
| 530 | // TODO(taking): Add globals with types that are instantiations of generic functions. |
| 531 | func TestGenericDecls(t *testing.T) { |
| 532 | if !typeparams.Enabled { |
| 533 | t.Skip("TestGenericDecls only works with type parameters enabled.") |
| 534 | } |
| 535 | const input = ` |
| 536 | package p |
| 537 | |
| 538 | import "unsafe" |
| 539 | |
| 540 | type Pointer[T any] struct { |
| 541 | v unsafe.Pointer |
| 542 | } |
| 543 | |
| 544 | func (x *Pointer[T]) Load() *T { |
| 545 | return (*T)(LoadPointer(&x.v)) |
| 546 | } |
| 547 | |
| 548 | func Load[T any](x *Pointer[T]) *T { |
| 549 | return x.Load() |
| 550 | } |
| 551 | |
| 552 | func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer) |
| 553 | ` |
| 554 | // The SSA members for this package should look something like this: |
| 555 | // func LoadPointer func(addr *unsafe.Pointer) (val unsafe.Pointer) |
| 556 | // type Pointer struct{v unsafe.Pointer} |
| 557 | // method (*Pointer[T any]) Load() *T |
| 558 | // func init func() |
| 559 | // var init$guard bool |
| 560 | |
| 561 | // Parse |
| 562 | var conf loader.Config |
| 563 | f, err := conf.ParseFile("<input>", input) |
| 564 | if err != nil { |
| 565 | t.Fatalf("parse: %v", err) |
| 566 | } |
| 567 | conf.CreateFromFiles("p", f) |
| 568 | |
| 569 | // Load |
| 570 | lprog, err := conf.Load() |
| 571 | if err != nil { |
| 572 | t.Fatalf("Load: %v", err) |
| 573 | } |
| 574 | |
| 575 | // Create and build SSA |
| 576 | prog := ssautil.CreateProgram(lprog, ssa.BuilderMode(0)) |
| 577 | p := prog.Package(lprog.Package("p").Pkg) |
| 578 | p.Build() |
| 579 | |
| 580 | if load := p.Func("Load"); typeparams.ForSignature(load.Signature).Len() != 1 { |
| 581 | t.Errorf("expected a single type param T for Load got %q", load.Signature) |
| 582 | } |
| 583 | if ptr := p.Type("Pointer"); typeparams.ForNamed(ptr.Type().(*types.Named)).Len() != 1 { |
| 584 | t.Errorf("expected a single type param T for Pointer got %q", ptr.Type()) |
| 585 | } |
| 586 | } |
| 587 | |
| 588 | func TestGenericWrappers(t *testing.T) { |
| 589 | if !typeparams.Enabled { |
| 590 | t.Skip("TestGenericWrappers only works with type parameters enabled.") |
| 591 | } |
| 592 | const input = ` |
| 593 | package p |
| 594 | |
| 595 | type S[T any] struct { |
| 596 | t *T |
| 597 | } |
| 598 | |
| 599 | func (x S[T]) M() T { |
| 600 | return *(x.t) |
| 601 | } |
| 602 | |
| 603 | var thunk = S[int].M |
| 604 | |
| 605 | var g S[int] |
| 606 | var bound = g.M |
| 607 | |
| 608 | type R[T any] struct{ S[T] } |
| 609 | |
| 610 | var indirect = R[int].M |
| 611 | ` |
| 612 | // The relevant SSA members for this package should look something like this: |
| 613 | // var bound func() int |
| 614 | // var thunk func(S[int]) int |
| 615 | // var wrapper func(R[int]) int |
| 616 | |
| 617 | // Parse |
| 618 | var conf loader.Config |
| 619 | f, err := conf.ParseFile("<input>", input) |
| 620 | if err != nil { |
| 621 | t.Fatalf("parse: %v", err) |
| 622 | } |
| 623 | conf.CreateFromFiles("p", f) |
| 624 | |
| 625 | // Load |
| 626 | lprog, err := conf.Load() |
| 627 | if err != nil { |
| 628 | t.Fatalf("Load: %v", err) |
| 629 | } |
| 630 | |
| 631 | for _, mode := range []ssa.BuilderMode{ssa.BuilderMode(0), ssa.InstantiateGenerics} { |
| 632 | // Create and build SSA |
| 633 | prog := ssautil.CreateProgram(lprog, mode) |
| 634 | p := prog.Package(lprog.Package("p").Pkg) |
| 635 | p.Build() |
| 636 | |
| 637 | for _, entry := range []struct { |
| 638 | name string // name of the package variable |
| 639 | typ string // type of the package variable |
| 640 | wrapper string // wrapper function to which the package variable is set |
| 641 | callee string // callee within the wrapper function |
| 642 | }{ |
| 643 | { |
| 644 | "bound", |
| 645 | "*func() int", |
| 646 | "(p.S[int]).M$bound", |
| 647 | "(p.S[int]).M[int]", |
| 648 | }, |
| 649 | { |
| 650 | "thunk", |
| 651 | "*func(p.S[int]) int", |
| 652 | "(p.S[int]).M$thunk", |
| 653 | "(p.S[int]).M[int]", |
| 654 | }, |
| 655 | { |
| 656 | "indirect", |
| 657 | "*func(p.R[int]) int", |
| 658 | "(p.R[int]).M$thunk", |
| 659 | "(p.S[int]).M[int]", |
| 660 | }, |
| 661 | } { |
| 662 | entry := entry |
| 663 | t.Run(entry.name, func(t *testing.T) { |
| 664 | v := p.Var(entry.name) |
| 665 | if v == nil { |
| 666 | t.Fatalf("Did not find variable for %q in %s", entry.name, p.String()) |
| 667 | } |
| 668 | if v.Type().String() != entry.typ { |
| 669 | t.Errorf("Expected type for variable %s: %q. got %q", v, entry.typ, v.Type()) |
| 670 | } |
| 671 | |
| 672 | // Find the wrapper for v. This is stored exactly once in init. |
| 673 | var wrapper *ssa.Function |
| 674 | for _, bb := range p.Func("init").Blocks { |
| 675 | for _, i := range bb.Instrs { |
| 676 | if store, ok := i.(*ssa.Store); ok && v == store.Addr { |
| 677 | switch val := store.Val.(type) { |
| 678 | case *ssa.Function: |
| 679 | wrapper = val |
| 680 | case *ssa.MakeClosure: |
| 681 | wrapper = val.Fn.(*ssa.Function) |
| 682 | } |
| 683 | } |
| 684 | } |
| 685 | } |
| 686 | if wrapper == nil { |
| 687 | t.Fatalf("failed to find wrapper function for %s", entry.name) |
| 688 | } |
| 689 | if wrapper.String() != entry.wrapper { |
| 690 | t.Errorf("Expected wrapper function %q. got %q", wrapper, entry.wrapper) |
| 691 | } |
| 692 | |
| 693 | // Find the callee within the wrapper. There should be exactly one call. |
| 694 | var callee *ssa.Function |
| 695 | for _, bb := range wrapper.Blocks { |
| 696 | for _, i := range bb.Instrs { |
| 697 | if call, ok := i.(*ssa.Call); ok { |
| 698 | callee = call.Call.StaticCallee() |
| 699 | } |
| 700 | } |
| 701 | } |
| 702 | if callee == nil { |
| 703 | t.Fatalf("failed to find callee within wrapper %s", wrapper) |
| 704 | } |
| 705 | if callee.String() != entry.callee { |
| 706 | t.Errorf("Expected callee in wrapper %q is %q. got %q", v, entry.callee, callee) |
| 707 | } |
| 708 | }) |
| 709 | } |
| 710 | } |
| 711 | } |
| 712 | |
| 713 | // TestTypeparamTest builds SSA over compilable examples in $GOROOT/test/typeparam/*.go. |
| 714 | |
| 715 | func TestTypeparamTest(t *testing.T) { |
| 716 | if !typeparams.Enabled { |
| 717 | return |
| 718 | } |
| 719 | |
| 720 | // Tests use a fake goroot to stub out standard libraries with delcarations in |
| 721 | // testdata/src. Decreases runtime from ~80s to ~1s. |
| 722 | |
| 723 | dir := filepath.Join(build.Default.GOROOT, "test", "typeparam") |
| 724 | |
| 725 | // Collect all of the .go files in |
| 726 | list, err := os.ReadDir(dir) |
| 727 | if err != nil { |
| 728 | t.Fatal(err) |
| 729 | } |
| 730 | |
| 731 | for _, entry := range list { |
| 732 | if entry.Name() == "issue376214.go" { |
| 733 | continue // investigate variadic + New signature. |
| 734 | } |
| 735 | if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") { |
| 736 | continue // Consider standalone go files. |
| 737 | } |
| 738 | input := filepath.Join(dir, entry.Name()) |
| 739 | t.Run(entry.Name(), func(t *testing.T) { |
| 740 | src, err := os.ReadFile(input) |
| 741 | if err != nil { |
| 742 | t.Fatal(err) |
| 743 | } |
| 744 | // Only build test files that can be compiled, or compiled and run. |
| 745 | if !bytes.HasPrefix(src, []byte("// run")) && !bytes.HasPrefix(src, []byte("// compile")) { |
| 746 | t.Skipf("not detected as a run test") |
| 747 | } |
| 748 | |
| 749 | t.Logf("Input: %s\n", input) |
| 750 | |
| 751 | ctx := build.Default // copy |
| 752 | ctx.GOROOT = "testdata" // fake goroot. Makes tests ~1s. tests take ~80s. |
| 753 | |
| 754 | reportErr := func(err error) { |
| 755 | t.Error(err) |
| 756 | } |
| 757 | conf := loader.Config{Build: &ctx, TypeChecker: types.Config{Error: reportErr}} |
| 758 | if _, err := conf.FromArgs([]string{input}, true); err != nil { |
| 759 | t.Fatalf("FromArgs(%s) failed: %s", input, err) |
| 760 | } |
| 761 | |
| 762 | iprog, err := conf.Load() |
| 763 | if iprog != nil { |
| 764 | for _, pkg := range iprog.Created { |
| 765 | for i, e := range pkg.Errors { |
| 766 | t.Errorf("Loading pkg %s error[%d]=%s", pkg, i, e) |
| 767 | } |
| 768 | } |
| 769 | } |
| 770 | if err != nil { |
| 771 | t.Fatalf("conf.Load(%s) failed: %s", input, err) |
| 772 | } |
| 773 | |
| 774 | mode := ssa.SanityCheckFunctions | ssa.InstantiateGenerics |
| 775 | prog := ssautil.CreateProgram(iprog, mode) |
| 776 | prog.Build() |
| 777 | }) |
| 778 | } |
| 779 | } |
| 780 | |
| 781 | // TestOrderOfOperations ensures order of operations are as intended. |
| 782 | func TestOrderOfOperations(t *testing.T) { |
| 783 | // Testing for the order of operations within an expression is done |
| 784 | // by collecting the sequence of direct function calls within a *Function. |
| 785 | // Callees are all external functions so they cannot be safely re-ordered by ssa. |
| 786 | const input = ` |
| 787 | package p |
| 788 | |
| 789 | func a() int |
| 790 | func b() int |
| 791 | func c() int |
| 792 | |
| 793 | func slice(s []int) []int { return s[a():b()] } |
| 794 | func sliceMax(s []int) []int { return s[a():b():c()] } |
| 795 | |
| 796 | ` |
| 797 | |
| 798 | // Parse |
| 799 | var conf loader.Config |
| 800 | f, err := conf.ParseFile("<input>", input) |
| 801 | if err != nil { |
| 802 | t.Fatalf("parse: %v", err) |
| 803 | } |
| 804 | conf.CreateFromFiles("p", f) |
| 805 | |
| 806 | // Load |
| 807 | lprog, err := conf.Load() |
| 808 | if err != nil { |
| 809 | t.Fatalf("Load: %v", err) |
| 810 | } |
| 811 | |
| 812 | // Create and build SSA |
| 813 | prog := ssautil.CreateProgram(lprog, ssa.BuilderMode(0)) |
| 814 | p := prog.Package(lprog.Package("p").Pkg) |
| 815 | p.Build() |
| 816 | |
| 817 | for _, item := range []struct { |
| 818 | fn string |
| 819 | want string // sequence of calls within the function. |
| 820 | }{ |
| 821 | {"sliceMax", "[a() b() c()]"}, |
| 822 | {"slice", "[a() b()]"}, |
| 823 | } { |
| 824 | fn := p.Func(item.fn) |
| 825 | want := item.want |
| 826 | t.Run(item.fn, func(t *testing.T) { |
| 827 | t.Parallel() |
| 828 | |
| 829 | var calls []string |
| 830 | for _, b := range fn.Blocks { |
| 831 | for _, instr := range b.Instrs { |
| 832 | if call, ok := instr.(ssa.CallInstruction); ok { |
| 833 | calls = append(calls, call.String()) |
| 834 | } |
| 835 | } |
| 836 | } |
| 837 | if got := fmt.Sprint(calls); got != want { |
| 838 | fn.WriteTo(os.Stderr) |
| 839 | t.Errorf("Expected sequence of function calls in %s was %s. got %s", fn, want, got) |
| 840 | } |
| 841 | }) |
| 842 | } |
| 843 | } |
| 844 | |
| 845 | // TestGenericFunctionSelector ensures generic functions from other packages can be selected. |
| 846 | func TestGenericFunctionSelector(t *testing.T) { |
| 847 | if !typeparams.Enabled { |
| 848 | t.Skip("TestGenericFunctionSelector uses type parameters.") |
| 849 | } |
| 850 | |
| 851 | pkgs := map[string]map[string]string{ |
| 852 | "main": {"m.go": `package main; import "a"; func main() { a.F[int](); a.G[int,string](); a.H(0) }`}, |
| 853 | "a": {"a.go": `package a; func F[T any](){}; func G[S, T any](){}; func H[T any](a T){} `}, |
| 854 | } |
| 855 | |
| 856 | for _, mode := range []ssa.BuilderMode{ |
| 857 | ssa.SanityCheckFunctions, |
| 858 | ssa.SanityCheckFunctions | ssa.InstantiateGenerics, |
| 859 | } { |
| 860 | conf := loader.Config{ |
| 861 | Build: buildutil.FakeContext(pkgs), |
| 862 | } |
| 863 | conf.Import("main") |
| 864 | |
| 865 | lprog, err := conf.Load() |
| 866 | if err != nil { |
| 867 | t.Errorf("Load failed: %s", err) |
| 868 | } |
| 869 | if lprog == nil { |
| 870 | t.Fatalf("Load returned nil *Program") |
| 871 | } |
| 872 | // Create and build SSA |
| 873 | prog := ssautil.CreateProgram(lprog, mode) |
| 874 | p := prog.Package(lprog.Package("main").Pkg) |
| 875 | p.Build() |
| 876 | |
| 877 | var callees []string // callees of the CallInstruction.String() in main(). |
| 878 | for _, b := range p.Func("main").Blocks { |
| 879 | for _, i := range b.Instrs { |
| 880 | if call, ok := i.(ssa.CallInstruction); ok { |
| 881 | if callee := call.Common().StaticCallee(); call != nil { |
| 882 | callees = append(callees, callee.String()) |
| 883 | } else { |
| 884 | t.Errorf("CallInstruction without StaticCallee() %q", call) |
| 885 | } |
| 886 | } |
| 887 | } |
| 888 | } |
| 889 | sort.Strings(callees) // ignore the order in the code. |
| 890 | |
| 891 | want := "[a.F[int] a.G[int string] a.H[int]]" |
| 892 | if got := fmt.Sprint(callees); got != want { |
| 893 | t.Errorf("Expected main() to contain calls %v. got %v", want, got) |
| 894 | } |
| 895 | } |
| 896 | } |
| 897 |
Members
TestGenericWrappers.RangeStmt_17037.BlockStmt.RangeStmt_17253.BlockStmt.BlockStmt.RangeStmt_18248.bb