github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/ssa/builder_test.go (about)

     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  	"go/ast"
    10  	"go/build"
    11  	"go/importer"
    12  	"go/parser"
    13  	"go/token"
    14  	"go/types"
    15  	"os"
    16  	"path/filepath"
    17  	"reflect"
    18  	"sort"
    19  	"strings"
    20  	"testing"
    21  
    22  	"github.com/powerman/golang-tools/go/loader"
    23  	"github.com/powerman/golang-tools/go/ssa"
    24  	"github.com/powerman/golang-tools/go/ssa/ssautil"
    25  	"github.com/powerman/golang-tools/internal/typeparams"
    26  )
    27  
    28  func isEmpty(f *ssa.Function) bool { return f.Blocks == nil }
    29  
    30  // Tests that programs partially loaded from gc object files contain
    31  // functions with no code for the external portions, but are otherwise ok.
    32  func TestBuildPackage(t *testing.T) {
    33  	input := `
    34  package main
    35  
    36  import (
    37  	"bytes"
    38  	"io"
    39  	"testing"
    40  )
    41  
    42  func main() {
    43          var t testing.T
    44  	t.Parallel()    // static call to external declared method
    45          t.Fail()        // static call to promoted external declared method
    46          testing.Short() // static call to external package-level function
    47  
    48          var w io.Writer = new(bytes.Buffer)
    49          w.Write(nil)    // interface invoke of external declared method
    50  }
    51  `
    52  
    53  	// Parse the file.
    54  	fset := token.NewFileSet()
    55  	f, err := parser.ParseFile(fset, "input.go", input, 0)
    56  	if err != nil {
    57  		t.Error(err)
    58  		return
    59  	}
    60  
    61  	// Build an SSA program from the parsed file.
    62  	// Load its dependencies from gc binary export data.
    63  	mainPkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer.Default()}, fset,
    64  		types.NewPackage("main", ""), []*ast.File{f}, ssa.SanityCheckFunctions)
    65  	if err != nil {
    66  		t.Error(err)
    67  		return
    68  	}
    69  
    70  	// The main package, its direct and indirect dependencies are loaded.
    71  	deps := []string{
    72  		// directly imported dependencies:
    73  		"bytes", "io", "testing",
    74  		// indirect dependencies mentioned by
    75  		// the direct imports' export data
    76  		"sync", "unicode", "time",
    77  	}
    78  
    79  	prog := mainPkg.Prog
    80  	all := prog.AllPackages()
    81  	if len(all) <= len(deps) {
    82  		t.Errorf("unexpected set of loaded packages: %q", all)
    83  	}
    84  	for _, path := range deps {
    85  		pkg := prog.ImportedPackage(path)
    86  		if pkg == nil {
    87  			t.Errorf("package not loaded: %q", path)
    88  			continue
    89  		}
    90  
    91  		// External packages should have no function bodies (except for wrappers).
    92  		isExt := pkg != mainPkg
    93  
    94  		// init()
    95  		if isExt && !isEmpty(pkg.Func("init")) {
    96  			t.Errorf("external package %s has non-empty init", pkg)
    97  		} else if !isExt && isEmpty(pkg.Func("init")) {
    98  			t.Errorf("main package %s has empty init", pkg)
    99  		}
   100  
   101  		for _, mem := range pkg.Members {
   102  			switch mem := mem.(type) {
   103  			case *ssa.Function:
   104  				// Functions at package level.
   105  				if isExt && !isEmpty(mem) {
   106  					t.Errorf("external function %s is non-empty", mem)
   107  				} else if !isExt && isEmpty(mem) {
   108  					t.Errorf("function %s is empty", mem)
   109  				}
   110  
   111  			case *ssa.Type:
   112  				// Methods of named types T.
   113  				// (In this test, all exported methods belong to *T not T.)
   114  				if !isExt {
   115  					t.Fatalf("unexpected name type in main package: %s", mem)
   116  				}
   117  				mset := prog.MethodSets.MethodSet(types.NewPointer(mem.Type()))
   118  				for i, n := 0, mset.Len(); i < n; i++ {
   119  					m := prog.MethodValue(mset.At(i))
   120  					// For external types, only synthetic wrappers have code.
   121  					expExt := !strings.Contains(m.Synthetic, "wrapper")
   122  					if expExt && !isEmpty(m) {
   123  						t.Errorf("external method %s is non-empty: %s",
   124  							m, m.Synthetic)
   125  					} else if !expExt && isEmpty(m) {
   126  						t.Errorf("method function %s is empty: %s",
   127  							m, m.Synthetic)
   128  					}
   129  				}
   130  			}
   131  		}
   132  	}
   133  
   134  	expectedCallee := []string{
   135  		"(*testing.T).Parallel",
   136  		"(*testing.common).Fail",
   137  		"testing.Short",
   138  		"N/A",
   139  	}
   140  	callNum := 0
   141  	for _, b := range mainPkg.Func("main").Blocks {
   142  		for _, instr := range b.Instrs {
   143  			switch instr := instr.(type) {
   144  			case ssa.CallInstruction:
   145  				call := instr.Common()
   146  				if want := expectedCallee[callNum]; want != "N/A" {
   147  					got := call.StaticCallee().String()
   148  					if want != got {
   149  						t.Errorf("call #%d from main.main: got callee %s, want %s",
   150  							callNum, got, want)
   151  					}
   152  				}
   153  				callNum++
   154  			}
   155  		}
   156  	}
   157  	if callNum != 4 {
   158  		t.Errorf("in main.main: got %d calls, want %d", callNum, 4)
   159  	}
   160  }
   161  
   162  // TestRuntimeTypes tests that (*Program).RuntimeTypes() includes all necessary types.
   163  func TestRuntimeTypes(t *testing.T) {
   164  	tests := []struct {
   165  		input string
   166  		want  []string
   167  	}{
   168  		// An exported package-level type is needed.
   169  		{`package A; type T struct{}; func (T) f() {}`,
   170  			[]string{"*p.T", "p.T"},
   171  		},
   172  		// An unexported package-level type is not needed.
   173  		{`package B; type t struct{}; func (t) f() {}`,
   174  			nil,
   175  		},
   176  		// Subcomponents of type of exported package-level var are needed.
   177  		{`package C; import "bytes"; var V struct {*bytes.Buffer}`,
   178  			[]string{"*bytes.Buffer", "*struct{*bytes.Buffer}", "struct{*bytes.Buffer}"},
   179  		},
   180  		// Subcomponents of type of unexported package-level var are not needed.
   181  		{`package D; import "bytes"; var v struct {*bytes.Buffer}`,
   182  			nil,
   183  		},
   184  		// Subcomponents of type of exported package-level function are needed.
   185  		{`package E; import "bytes"; func F(struct {*bytes.Buffer}) {}`,
   186  			[]string{"*bytes.Buffer", "struct{*bytes.Buffer}"},
   187  		},
   188  		// Subcomponents of type of unexported package-level function are not needed.
   189  		{`package F; import "bytes"; func f(struct {*bytes.Buffer}) {}`,
   190  			nil,
   191  		},
   192  		// Subcomponents of type of exported method of uninstantiated unexported type are not needed.
   193  		{`package G; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v x`,
   194  			nil,
   195  		},
   196  		// ...unless used by MakeInterface.
   197  		{`package G2; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v interface{} = x{}`,
   198  			[]string{"*bytes.Buffer", "*p.x", "p.x", "struct{*bytes.Buffer}"},
   199  		},
   200  		// Subcomponents of type of unexported method are not needed.
   201  		{`package I; import "bytes"; type X struct{}; func (X) G(struct {*bytes.Buffer}) {}`,
   202  			[]string{"*bytes.Buffer", "*p.X", "p.X", "struct{*bytes.Buffer}"},
   203  		},
   204  		// Local types aren't needed.
   205  		{`package J; import "bytes"; func f() { type T struct {*bytes.Buffer}; var t T; _ = t }`,
   206  			nil,
   207  		},
   208  		// ...unless used by MakeInterface.
   209  		{`package K; import "bytes"; func f() { type T struct {*bytes.Buffer}; _ = interface{}(T{}) }`,
   210  			[]string{"*bytes.Buffer", "*p.T", "p.T"},
   211  		},
   212  		// Types used as operand of MakeInterface are needed.
   213  		{`package L; import "bytes"; func f() { _ = interface{}(struct{*bytes.Buffer}{}) }`,
   214  			[]string{"*bytes.Buffer", "struct{*bytes.Buffer}"},
   215  		},
   216  		// MakeInterface is optimized away when storing to a blank.
   217  		{`package M; import "bytes"; var _ interface{} = struct{*bytes.Buffer}{}`,
   218  			nil,
   219  		},
   220  	}
   221  	for _, test := range tests {
   222  		// Parse the file.
   223  		fset := token.NewFileSet()
   224  		f, err := parser.ParseFile(fset, "input.go", test.input, 0)
   225  		if err != nil {
   226  			t.Errorf("test %q: %s", test.input[:15], err)
   227  			continue
   228  		}
   229  
   230  		// Create a single-file main package.
   231  		// Load dependencies from gc binary export data.
   232  		ssapkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer.Default()}, fset,
   233  			types.NewPackage("p", ""), []*ast.File{f}, ssa.SanityCheckFunctions)
   234  		if err != nil {
   235  			t.Errorf("test %q: %s", test.input[:15], err)
   236  			continue
   237  		}
   238  
   239  		var typstrs []string
   240  		for _, T := range ssapkg.Prog.RuntimeTypes() {
   241  			typstrs = append(typstrs, T.String())
   242  		}
   243  		sort.Strings(typstrs)
   244  
   245  		if !reflect.DeepEqual(typstrs, test.want) {
   246  			t.Errorf("test 'package %s': got %q, want %q",
   247  				f.Name.Name, typstrs, test.want)
   248  		}
   249  	}
   250  }
   251  
   252  // TestInit tests that synthesized init functions are correctly formed.
   253  // Bare init functions omit calls to dependent init functions and the use of
   254  // an init guard. They are useful in cases where the client uses a different
   255  // calling convention for init functions, or cases where it is easier for a
   256  // client to analyze bare init functions. Both of these aspects are used by
   257  // the llgo compiler for simpler integration with gccgo's runtime library,
   258  // and to simplify the analysis whereby it deduces which stores to globals
   259  // can be lowered to global initializers.
   260  func TestInit(t *testing.T) {
   261  	tests := []struct {
   262  		mode        ssa.BuilderMode
   263  		input, want string
   264  	}{
   265  		{0, `package A; import _ "errors"; var i int = 42`,
   266  			`# Name: A.init
   267  # Package: A
   268  # Synthetic: package initializer
   269  func init():
   270  0:                                                                entry P:0 S:2
   271  	t0 = *init$guard                                                   bool
   272  	if t0 goto 2 else 1
   273  1:                                                           init.start P:1 S:1
   274  	*init$guard = true:bool
   275  	t1 = errors.init()                                                   ()
   276  	*i = 42:int
   277  	jump 2
   278  2:                                                            init.done P:2 S:0
   279  	return
   280  
   281  `},
   282  		{ssa.BareInits, `package B; import _ "errors"; var i int = 42`,
   283  			`# Name: B.init
   284  # Package: B
   285  # Synthetic: package initializer
   286  func init():
   287  0:                                                                entry P:0 S:0
   288  	*i = 42:int
   289  	return
   290  
   291  `},
   292  	}
   293  	for _, test := range tests {
   294  		// Create a single-file main package.
   295  		var conf loader.Config
   296  		f, err := conf.ParseFile("<input>", test.input)
   297  		if err != nil {
   298  			t.Errorf("test %q: %s", test.input[:15], err)
   299  			continue
   300  		}
   301  		conf.CreateFromFiles(f.Name.Name, f)
   302  
   303  		lprog, err := conf.Load()
   304  		if err != nil {
   305  			t.Errorf("test 'package %s': Load: %s", f.Name.Name, err)
   306  			continue
   307  		}
   308  		prog := ssautil.CreateProgram(lprog, test.mode)
   309  		mainPkg := prog.Package(lprog.Created[0].Pkg)
   310  		prog.Build()
   311  		initFunc := mainPkg.Func("init")
   312  		if initFunc == nil {
   313  			t.Errorf("test 'package %s': no init function", f.Name.Name)
   314  			continue
   315  		}
   316  
   317  		var initbuf bytes.Buffer
   318  		_, err = initFunc.WriteTo(&initbuf)
   319  		if err != nil {
   320  			t.Errorf("test 'package %s': WriteTo: %s", f.Name.Name, err)
   321  			continue
   322  		}
   323  
   324  		if initbuf.String() != test.want {
   325  			t.Errorf("test 'package %s': got %s, want %s", f.Name.Name, initbuf.String(), test.want)
   326  		}
   327  	}
   328  }
   329  
   330  // TestSyntheticFuncs checks that the expected synthetic functions are
   331  // created, reachable, and not duplicated.
   332  func TestSyntheticFuncs(t *testing.T) {
   333  	const input = `package P
   334  type T int
   335  func (T) f() int
   336  func (*T) g() int
   337  var (
   338  	// thunks
   339  	a = T.f
   340  	b = T.f
   341  	c = (struct{T}).f
   342  	d = (struct{T}).f
   343  	e = (*T).g
   344  	f = (*T).g
   345  	g = (struct{*T}).g
   346  	h = (struct{*T}).g
   347  
   348  	// bounds
   349  	i = T(0).f
   350  	j = T(0).f
   351  	k = new(T).g
   352  	l = new(T).g
   353  
   354  	// wrappers
   355  	m interface{} = struct{T}{}
   356  	n interface{} = struct{T}{}
   357  	o interface{} = struct{*T}{}
   358  	p interface{} = struct{*T}{}
   359  	q interface{} = new(struct{T})
   360  	r interface{} = new(struct{T})
   361  	s interface{} = new(struct{*T})
   362  	t interface{} = new(struct{*T})
   363  )
   364  `
   365  	// Parse
   366  	var conf loader.Config
   367  	f, err := conf.ParseFile("<input>", input)
   368  	if err != nil {
   369  		t.Fatalf("parse: %v", err)
   370  	}
   371  	conf.CreateFromFiles(f.Name.Name, f)
   372  
   373  	// Load
   374  	lprog, err := conf.Load()
   375  	if err != nil {
   376  		t.Fatalf("Load: %v", err)
   377  	}
   378  
   379  	// Create and build SSA
   380  	prog := ssautil.CreateProgram(lprog, 0)
   381  	prog.Build()
   382  
   383  	// Enumerate reachable synthetic functions
   384  	want := map[string]string{
   385  		"(*P.T).g$bound": "bound method wrapper for func (*P.T).g() int",
   386  		"(P.T).f$bound":  "bound method wrapper for func (P.T).f() int",
   387  
   388  		"(*P.T).g$thunk":         "thunk for func (*P.T).g() int",
   389  		"(P.T).f$thunk":          "thunk for func (P.T).f() int",
   390  		"(struct{*P.T}).g$thunk": "thunk for func (*P.T).g() int",
   391  		"(struct{P.T}).f$thunk":  "thunk for func (P.T).f() int",
   392  
   393  		"(*P.T).f":          "wrapper for func (P.T).f() int",
   394  		"(*struct{*P.T}).f": "wrapper for func (P.T).f() int",
   395  		"(*struct{*P.T}).g": "wrapper for func (*P.T).g() int",
   396  		"(*struct{P.T}).f":  "wrapper for func (P.T).f() int",
   397  		"(*struct{P.T}).g":  "wrapper for func (*P.T).g() int",
   398  		"(struct{*P.T}).f":  "wrapper for func (P.T).f() int",
   399  		"(struct{*P.T}).g":  "wrapper for func (*P.T).g() int",
   400  		"(struct{P.T}).f":   "wrapper for func (P.T).f() int",
   401  
   402  		"P.init": "package initializer",
   403  	}
   404  	for fn := range ssautil.AllFunctions(prog) {
   405  		if fn.Synthetic == "" {
   406  			continue
   407  		}
   408  		name := fn.String()
   409  		wantDescr, ok := want[name]
   410  		if !ok {
   411  			t.Errorf("got unexpected/duplicate func: %q: %q", name, fn.Synthetic)
   412  			continue
   413  		}
   414  		delete(want, name)
   415  
   416  		if wantDescr != fn.Synthetic {
   417  			t.Errorf("(%s).Synthetic = %q, want %q", name, fn.Synthetic, wantDescr)
   418  		}
   419  	}
   420  	for fn, descr := range want {
   421  		t.Errorf("want func: %q: %q", fn, descr)
   422  	}
   423  }
   424  
   425  // TestPhiElimination ensures that dead phis, including those that
   426  // participate in a cycle, are properly eliminated.
   427  func TestPhiElimination(t *testing.T) {
   428  	const input = `
   429  package p
   430  
   431  func f() error
   432  
   433  func g(slice []int) {
   434  	for {
   435  		for range slice {
   436  			// e should not be lifted to a dead φ-node.
   437  			e := f()
   438  			h(e)
   439  		}
   440  	}
   441  }
   442  
   443  func h(error)
   444  `
   445  	// The SSA code for this function should look something like this:
   446  	// 0:
   447  	//         jump 1
   448  	// 1:
   449  	//         t0 = len(slice)
   450  	//         jump 2
   451  	// 2:
   452  	//         t1 = phi [1: -1:int, 3: t2]
   453  	//         t2 = t1 + 1:int
   454  	//         t3 = t2 < t0
   455  	//         if t3 goto 3 else 1
   456  	// 3:
   457  	//         t4 = f()
   458  	//         t5 = h(t4)
   459  	//         jump 2
   460  	//
   461  	// But earlier versions of the SSA construction algorithm would
   462  	// additionally generate this cycle of dead phis:
   463  	//
   464  	// 1:
   465  	//         t7 = phi [0: nil:error, 2: t8] #e
   466  	//         ...
   467  	// 2:
   468  	//         t8 = phi [1: t7, 3: t4] #e
   469  	//         ...
   470  
   471  	// Parse
   472  	var conf loader.Config
   473  	f, err := conf.ParseFile("<input>", input)
   474  	if err != nil {
   475  		t.Fatalf("parse: %v", err)
   476  	}
   477  	conf.CreateFromFiles("p", f)
   478  
   479  	// Load
   480  	lprog, err := conf.Load()
   481  	if err != nil {
   482  		t.Fatalf("Load: %v", err)
   483  	}
   484  
   485  	// Create and build SSA
   486  	prog := ssautil.CreateProgram(lprog, 0)
   487  	p := prog.Package(lprog.Package("p").Pkg)
   488  	p.Build()
   489  	g := p.Func("g")
   490  
   491  	phis := 0
   492  	for _, b := range g.Blocks {
   493  		for _, instr := range b.Instrs {
   494  			if _, ok := instr.(*ssa.Phi); ok {
   495  				phis++
   496  			}
   497  		}
   498  	}
   499  	if phis != 1 {
   500  		g.WriteTo(os.Stderr)
   501  		t.Errorf("expected a single Phi (for the range index), got %d", phis)
   502  	}
   503  }
   504  
   505  // TestGenericDecls ensures that *unused* generic types, methods and functions
   506  // signatures can be built.
   507  //
   508  // TODO(taking): Add calls from non-generic functions to instantiations of generic functions.
   509  // TODO(taking): Add globals with types that are instantiations of generic functions.
   510  func TestGenericDecls(t *testing.T) {
   511  	if !typeparams.Enabled {
   512  		t.Skip("TestGenericDecls only works with type parameters enabled.")
   513  	}
   514  	const input = `
   515  package p
   516  
   517  import "unsafe"
   518  
   519  type Pointer[T any] struct {
   520  	v unsafe.Pointer
   521  }
   522  
   523  func (x *Pointer[T]) Load() *T {
   524  	return (*T)(LoadPointer(&x.v))
   525  }
   526  
   527  func Load[T any](x *Pointer[T]) *T {
   528  	return x.Load()
   529  }
   530  
   531  func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
   532  `
   533  	// The SSA members for this package should look something like this:
   534  	//          func  LoadPointer func(addr *unsafe.Pointer) (val unsafe.Pointer)
   535  	//      type  Pointer     struct{v unsafe.Pointer}
   536  	//        method (*Pointer[T any]) Load() *T
   537  	//      func  init        func()
   538  	//      var   init$guard  bool
   539  
   540  	// Parse
   541  	var conf loader.Config
   542  	f, err := conf.ParseFile("<input>", input)
   543  	if err != nil {
   544  		t.Fatalf("parse: %v", err)
   545  	}
   546  	conf.CreateFromFiles("p", f)
   547  
   548  	// Load
   549  	lprog, err := conf.Load()
   550  	if err != nil {
   551  		t.Fatalf("Load: %v", err)
   552  	}
   553  
   554  	// Create and build SSA
   555  	prog := ssautil.CreateProgram(lprog, 0)
   556  	p := prog.Package(lprog.Package("p").Pkg)
   557  	p.Build()
   558  
   559  	if load := p.Func("Load"); typeparams.ForSignature(load.Signature).Len() != 1 {
   560  		t.Errorf("expected a single type param T for Load got %q", load.Signature)
   561  	}
   562  	if ptr := p.Type("Pointer"); typeparams.ForNamed(ptr.Type().(*types.Named)).Len() != 1 {
   563  		t.Errorf("expected a single type param T for Pointer got %q", ptr.Type())
   564  	}
   565  }
   566  
   567  // TestTypeparamTest builds SSA over compilable examples in $GOROOT/test/typeparam/*.go.
   568  
   569  func TestTypeparamTest(t *testing.T) {
   570  	if !typeparams.Enabled {
   571  		return
   572  	}
   573  
   574  	// Tests use a fake goroot to stub out standard libraries with delcarations in
   575  	// testdata/src. Decreases runtime from ~80s to ~1s.
   576  
   577  	dir := filepath.Join(build.Default.GOROOT, "test", "typeparam")
   578  
   579  	// Collect all of the .go files in
   580  	list, err := os.ReadDir(dir)
   581  	if err != nil {
   582  		t.Fatal(err)
   583  	}
   584  
   585  	for _, entry := range list {
   586  		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") {
   587  			continue // Consider standalone go files.
   588  		}
   589  		input := filepath.Join(dir, entry.Name())
   590  		t.Run(entry.Name(), func(t *testing.T) {
   591  			src, err := os.ReadFile(input)
   592  			if err != nil {
   593  				t.Fatal(err)
   594  			}
   595  			// Only build test files that can be compiled, or compiled and run.
   596  			if !bytes.HasPrefix(src, []byte("// run")) && !bytes.HasPrefix(src, []byte("// compile")) {
   597  				t.Skipf("not detected as a run test")
   598  			}
   599  
   600  			t.Logf("Input: %s\n", input)
   601  
   602  			ctx := build.Default    // copy
   603  			ctx.GOROOT = "testdata" // fake goroot. Makes tests ~1s. tests take ~80s.
   604  
   605  			reportErr := func(err error) {
   606  				t.Error(err)
   607  			}
   608  			conf := loader.Config{Build: &ctx, TypeChecker: types.Config{Error: reportErr}}
   609  			if _, err := conf.FromArgs([]string{input}, true); err != nil {
   610  				t.Fatalf("FromArgs(%s) failed: %s", input, err)
   611  			}
   612  
   613  			iprog, err := conf.Load()
   614  			if iprog != nil {
   615  				for _, pkg := range iprog.Created {
   616  					for i, e := range pkg.Errors {
   617  						t.Errorf("Loading pkg %s error[%d]=%s", pkg, i, e)
   618  					}
   619  				}
   620  			}
   621  			if err != nil {
   622  				t.Fatalf("conf.Load(%s) failed: %s", input, err)
   623  			}
   624  
   625  			prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions)
   626  			prog.Build()
   627  		})
   628  	}
   629  }