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