github.com/sanprasirt/go@v0.0.0-20170607001320-a027466e4b6d/src/cmd/compile/internal/gc/scope_test.go (about)

     1  // Copyright 2016 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 gc_test
     6  
     7  import (
     8  	"cmd/internal/objfile"
     9  	"debug/dwarf"
    10  	"internal/testenv"
    11  	"io/ioutil"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"runtime"
    16  	"sort"
    17  	"strconv"
    18  	"strings"
    19  	"testing"
    20  )
    21  
    22  type testline struct {
    23  	// line is one line of go source
    24  	line string
    25  
    26  	// scopes is a list of scope IDs of all the lexical scopes that this line
    27  	// of code belongs to.
    28  	// Scope IDs are assigned by traversing the tree of lexical blocks of a
    29  	// function in pre-order
    30  	// Scope IDs are function specific, i.e. scope 0 is always the root scope
    31  	// of the function that this line belongs to. Empty scopes are not assigned
    32  	// an ID (because they are not saved in debug_info).
    33  	// Scope 0 is always omitted from this list since all lines always belong
    34  	// to it.
    35  	scopes []int
    36  
    37  	// vars is the list of variables that belong in scopes[len(scopes)-1].
    38  	// Local variables are prefixed with "var ", formal parameters with "arg ".
    39  	// Must be ordered alphabetically.
    40  	// Set to nil to skip the check.
    41  	vars []string
    42  }
    43  
    44  var testfile = []testline{
    45  	{line: "package main"},
    46  	{line: "func f1(x int) { }"},
    47  	{line: "func f2(x int) { }"},
    48  	{line: "func f3(x int) { }"},
    49  	{line: "func f4(x int) { }"},
    50  	{line: "func f5(x int) { }"},
    51  	{line: "func f6(x int) { }"},
    52  	{line: "func gret1() int { return 2 }"},
    53  	{line: "func gretbool() bool { return true }"},
    54  	{line: "func gret3() (int, int, int) { return 0, 1, 2 }"},
    55  	{line: "var v = []int{ 0, 1, 2 }"},
    56  	{line: "var ch = make(chan int)"},
    57  	{line: "var floatch = make(chan float64)"},
    58  	{line: "var iface interface{}"},
    59  	{line: "func TestNestedFor() {", vars: []string{"var a int"}},
    60  	{line: "	a := 0"},
    61  	{line: "	f1(a)"},
    62  	{line: "	for i := 0; i < 5; i++ {", scopes: []int{1}, vars: []string{"var i int"}},
    63  	{line: "		f2(i)", scopes: []int{1}},
    64  	{line: "		for i := 0; i < 5; i++ {", scopes: []int{1, 2}, vars: []string{"var i int"}},
    65  	{line: "			f3(i)", scopes: []int{1, 2}},
    66  	{line: "		}"},
    67  	{line: "		f4(i)", scopes: []int{1}},
    68  	{line: "	}"},
    69  	{line: "	f5(a)"},
    70  	{line: "}"},
    71  	{line: "func TestOas2() {", vars: []string{}},
    72  	{line: "	if a, b, c := gret3(); a != 1 {", scopes: []int{1}, vars: []string{"var a int", "var b int", "var c int"}},
    73  	{line: "		f1(a)", scopes: []int{1}},
    74  	{line: "		f1(b)", scopes: []int{1}},
    75  	{line: "		f1(c)", scopes: []int{1}},
    76  	{line: "	}"},
    77  	{line: "	for i, x := range v {", scopes: []int{2}, vars: []string{"var i int", "var x int"}},
    78  	{line: "		f1(i)", scopes: []int{2}},
    79  	{line: "		f1(x)", scopes: []int{2}},
    80  	{line: "	}"},
    81  	{line: "	if a, ok := <- ch; ok {", scopes: []int{3}, vars: []string{"var a int", "var ok bool"}},
    82  	{line: "		f1(a)", scopes: []int{3}},
    83  	{line: "	}"},
    84  	{line: "	if a, ok := iface.(int); ok {", scopes: []int{4}, vars: []string{"var a int", "var ok bool"}},
    85  	{line: "		f1(a)", scopes: []int{4}},
    86  	{line: "	}"},
    87  	{line: "}"},
    88  	{line: "func TestIfElse() {"},
    89  	{line: "	if x := gret1(); x != 0 {", scopes: []int{1}, vars: []string{"var x int"}},
    90  	{line: "		a := 0", scopes: []int{1, 2}, vars: []string{"var a int"}},
    91  	{line: "		f1(a); f1(x)", scopes: []int{1, 2}},
    92  	{line: "	} else {"},
    93  	{line: "		b := 1", scopes: []int{1, 3}, vars: []string{"var b int"}},
    94  	{line: "		f1(b); f1(x+1)", scopes: []int{1, 3}},
    95  	{line: "	}"},
    96  	{line: "}"},
    97  	{line: "func TestSwitch() {", vars: []string{}},
    98  	{line: "	switch x := gret1(); x {", scopes: []int{1}, vars: []string{"var x int"}},
    99  	{line: "	case 0:", scopes: []int{1, 2}},
   100  	{line: "		i := x + 5", scopes: []int{1, 2}, vars: []string{"var i int"}},
   101  	{line: "		f1(x); f1(i)", scopes: []int{1, 2}},
   102  	{line: "	case 1:", scopes: []int{1, 3}},
   103  	{line: "		j := x + 10", scopes: []int{1, 3}, vars: []string{"var j int"}},
   104  	{line: "		f1(x); f1(j)", scopes: []int{1, 3}},
   105  	{line: "	case 2:", scopes: []int{1, 4}},
   106  	{line: "		k := x + 2", scopes: []int{1, 4}, vars: []string{"var k int"}},
   107  	{line: "		f1(x); f1(k)", scopes: []int{1, 4}},
   108  	{line: "	}"},
   109  	{line: "}"},
   110  	{line: "func TestTypeSwitch() {", vars: []string{}},
   111  	{line: "	switch x := iface.(type) {"},
   112  	{line: "	case int:", scopes: []int{1}},
   113  	{line: "		f1(x)", scopes: []int{1}, vars: []string{"var x int"}},
   114  	{line: "	case uint8:", scopes: []int{2}},
   115  	{line: "		f1(int(x))", scopes: []int{2}, vars: []string{"var x uint8"}},
   116  	{line: "	case float64:", scopes: []int{3}},
   117  	{line: "		f1(int(x)+1)", scopes: []int{3}, vars: []string{"var x float64"}},
   118  	{line: "	}"},
   119  	{line: "}"},
   120  	{line: "func TestSelectScope() {"},
   121  	{line: "	select {"},
   122  	{line: "	case i := <- ch:", scopes: []int{1}},
   123  	{line: "		f1(i)", scopes: []int{1}, vars: []string{"var i int"}},
   124  	{line: "	case f := <- floatch:", scopes: []int{2}},
   125  	{line: "		f1(int(f))", scopes: []int{2}, vars: []string{"var f float64"}},
   126  	{line: "	}"},
   127  	{line: "}"},
   128  	{line: "func TestBlock() {", vars: []string{"var a int"}},
   129  	{line: "	a := 1"},
   130  	{line: "	{"},
   131  	{line: "		b := 2", scopes: []int{1}, vars: []string{"var b int"}},
   132  	{line: "		f1(b)", scopes: []int{1}},
   133  	{line: "		f1(a)", scopes: []int{1}},
   134  	{line: "	}"},
   135  	{line: "}"},
   136  	{line: "func TestDiscontiguousRanges() {", vars: []string{"var a int"}},
   137  	{line: "	a := 0"},
   138  	{line: "	f1(a)"},
   139  	{line: "	{"},
   140  	{line: "		b := 0", scopes: []int{1}, vars: []string{"var b int"}},
   141  	{line: "		f2(b)", scopes: []int{1}},
   142  	{line: "		if gretbool() {", scopes: []int{1}},
   143  	{line: "			c := 0", scopes: []int{1, 2}, vars: []string{"var c int"}},
   144  	{line: "			f3(c)", scopes: []int{1, 2}},
   145  	{line: "		} else {"},
   146  	{line: "			c := 1.1", scopes: []int{1, 3}, vars: []string{"var c float64"}},
   147  	{line: "			f4(int(c))", scopes: []int{1, 3}},
   148  	{line: "		}"},
   149  	{line: "		f5(b)", scopes: []int{1}},
   150  	{line: "	}"},
   151  	{line: "	f6(a)"},
   152  	{line: "}"},
   153  	{line: "func TestClosureScope() {", vars: []string{"var a int", "var b int", "var f func(int)"}},
   154  	{line: "	a := 1; b := 1"},
   155  	{line: "	f := func(c int) {", scopes: []int{0}, vars: []string{"arg c int", "var &b *int", "var a int", "var d int"}},
   156  	{line: "		d := 3"},
   157  	{line: "		f1(c); f1(d)"},
   158  	{line: "		if e := 3; e != 0 {", scopes: []int{1}, vars: []string{"var e int"}},
   159  	{line: "			f1(e)", scopes: []int{1}},
   160  	{line: "			f1(a)", scopes: []int{1}},
   161  	{line: "			b = 2", scopes: []int{1}},
   162  	{line: "		}"},
   163  	{line: "	}"},
   164  	{line: "	f(3); f1(b)"},
   165  	{line: "}"},
   166  	{line: "func main() {"},
   167  	{line: "	TestNestedFor()"},
   168  	{line: "	TestOas2()"},
   169  	{line: "	TestIfElse()"},
   170  	{line: "	TestSwitch()"},
   171  	{line: "	TestTypeSwitch()"},
   172  	{line: "	TestSelectScope()"},
   173  	{line: "	TestBlock()"},
   174  	{line: "	TestDiscontiguousRanges()"},
   175  	{line: "	TestClosureScope()"},
   176  	{line: "}"},
   177  }
   178  
   179  const detailOutput = false
   180  
   181  // Compiles testfile checks that the description of lexical blocks emitted
   182  // by the linker in debug_info, for each function in the main package,
   183  // corresponds to what we expect it to be.
   184  func TestScopeRanges(t *testing.T) {
   185  	testenv.MustHaveGoBuild(t)
   186  
   187  	if runtime.GOOS == "plan9" {
   188  		t.Skip("skipping on plan9; no DWARF symbol table in executables")
   189  	}
   190  
   191  	dir, err := ioutil.TempDir("", "TestScopeRanges")
   192  	if err != nil {
   193  		t.Fatalf("could not create directory: %v", err)
   194  	}
   195  	defer os.RemoveAll(dir)
   196  
   197  	src, f := gobuild(t, dir, testfile)
   198  	defer f.Close()
   199  
   200  	// the compiler uses forward slashes for paths even on windows
   201  	src = strings.Replace(src, "\\", "/", -1)
   202  
   203  	pcln, err := f.PCLineTable()
   204  	if err != nil {
   205  		t.Fatal(err)
   206  	}
   207  	dwarfData, err := f.DWARF()
   208  	if err != nil {
   209  		t.Fatal(err)
   210  	}
   211  	dwarfReader := dwarfData.Reader()
   212  
   213  	lines := make(map[line][]*lexblock)
   214  
   215  	for {
   216  		entry, err := dwarfReader.Next()
   217  		if err != nil {
   218  			t.Fatal(err)
   219  		}
   220  		if entry == nil {
   221  			break
   222  		}
   223  
   224  		if entry.Tag != dwarf.TagSubprogram {
   225  			continue
   226  		}
   227  
   228  		name, ok := entry.Val(dwarf.AttrName).(string)
   229  		if !ok || !strings.HasPrefix(name, "main.Test") {
   230  			continue
   231  		}
   232  
   233  		var scope lexblock
   234  		ctxt := scopexplainContext{
   235  			dwarfData:   dwarfData,
   236  			dwarfReader: dwarfReader,
   237  			scopegen:    1,
   238  		}
   239  
   240  		readScope(&ctxt, &scope, entry)
   241  
   242  		scope.markLines(pcln, lines)
   243  	}
   244  
   245  	anyerror := false
   246  	for i := range testfile {
   247  		tgt := testfile[i].scopes
   248  		out := lines[line{src, i + 1}]
   249  
   250  		if detailOutput {
   251  			t.Logf("%s // %v", testfile[i].line, out)
   252  		}
   253  
   254  		scopesok := checkScopes(tgt, out)
   255  		if !scopesok {
   256  			t.Logf("mismatch at line %d %q: expected: %v got: %v\n", i, testfile[i].line, tgt, scopesToString(out))
   257  		}
   258  
   259  		varsok := true
   260  		if testfile[i].vars != nil {
   261  			if len(out) > 0 {
   262  				varsok = checkVars(testfile[i].vars, out[len(out)-1].vars)
   263  				if !varsok {
   264  					t.Logf("variable mismatch at line %d %q for scope %d: expected: %v got: %v\n", i, testfile[i].line, out[len(out)-1].id, testfile[i].vars, out[len(out)-1].vars)
   265  				}
   266  			}
   267  		}
   268  
   269  		anyerror = anyerror || !scopesok || !varsok
   270  	}
   271  
   272  	if anyerror {
   273  		t.Fatalf("mismatched output")
   274  	}
   275  }
   276  
   277  func scopesToString(v []*lexblock) string {
   278  	r := make([]string, len(v))
   279  	for i, s := range v {
   280  		r[i] = strconv.Itoa(s.id)
   281  	}
   282  	return "[ " + strings.Join(r, ", ") + " ]"
   283  }
   284  
   285  func checkScopes(tgt []int, out []*lexblock) bool {
   286  	if len(out) > 0 {
   287  		// omit scope 0
   288  		out = out[1:]
   289  	}
   290  	if len(tgt) != len(out) {
   291  		return false
   292  	}
   293  	for i := range tgt {
   294  		if tgt[i] != out[i].id {
   295  			return false
   296  		}
   297  	}
   298  	return true
   299  }
   300  
   301  func checkVars(tgt, out []string) bool {
   302  	if len(tgt) != len(out) {
   303  		return false
   304  	}
   305  	for i := range tgt {
   306  		if tgt[i] != out[i] {
   307  			return false
   308  		}
   309  	}
   310  	return true
   311  }
   312  
   313  type lexblock struct {
   314  	id     int
   315  	ranges [][2]uint64
   316  	vars   []string
   317  	scopes []lexblock
   318  }
   319  
   320  type line struct {
   321  	file   string
   322  	lineno int
   323  }
   324  
   325  type scopexplainContext struct {
   326  	dwarfData   *dwarf.Data
   327  	dwarfReader *dwarf.Reader
   328  	scopegen    int
   329  	lines       map[line][]int
   330  }
   331  
   332  // readScope reads the DW_TAG_lexical_block or the DW_TAG_subprogram in
   333  // entry and writes a description in scope.
   334  // Nested DW_TAG_lexical_block entries are read recursively.
   335  func readScope(ctxt *scopexplainContext, scope *lexblock, entry *dwarf.Entry) {
   336  	var err error
   337  	scope.ranges, err = ctxt.dwarfData.Ranges(entry)
   338  	if err != nil {
   339  		panic(err)
   340  	}
   341  	for {
   342  		e, err := ctxt.dwarfReader.Next()
   343  		if err != nil {
   344  			panic(err)
   345  		}
   346  		switch e.Tag {
   347  		case 0:
   348  			sort.Strings(scope.vars)
   349  			return
   350  		case dwarf.TagFormalParameter:
   351  			typ, err := ctxt.dwarfData.Type(e.Val(dwarf.AttrType).(dwarf.Offset))
   352  			if err != nil {
   353  				panic(err)
   354  			}
   355  			scope.vars = append(scope.vars, "arg "+e.Val(dwarf.AttrName).(string)+" "+typ.String())
   356  		case dwarf.TagVariable:
   357  			typ, err := ctxt.dwarfData.Type(e.Val(dwarf.AttrType).(dwarf.Offset))
   358  			if err != nil {
   359  				panic(err)
   360  			}
   361  			scope.vars = append(scope.vars, "var "+e.Val(dwarf.AttrName).(string)+" "+typ.String())
   362  		case dwarf.TagLexDwarfBlock:
   363  			scope.scopes = append(scope.scopes, lexblock{id: ctxt.scopegen})
   364  			ctxt.scopegen++
   365  			readScope(ctxt, &scope.scopes[len(scope.scopes)-1], e)
   366  		}
   367  	}
   368  }
   369  
   370  // markLines marks all lines that belong to this scope with this scope
   371  // Recursively calls markLines for all children scopes.
   372  func (scope *lexblock) markLines(pcln objfile.Liner, lines map[line][]*lexblock) {
   373  	for _, r := range scope.ranges {
   374  		for pc := r[0]; pc < r[1]; pc++ {
   375  			file, lineno, _ := pcln.PCToLine(pc)
   376  			l := line{file, lineno}
   377  			if len(lines[l]) == 0 || lines[l][len(lines[l])-1] != scope {
   378  				lines[l] = append(lines[l], scope)
   379  			}
   380  		}
   381  	}
   382  
   383  	for i := range scope.scopes {
   384  		scope.scopes[i].markLines(pcln, lines)
   385  	}
   386  }
   387  
   388  func gobuild(t *testing.T, dir string, testfile []testline) (string, *objfile.File) {
   389  	src := filepath.Join(dir, "test.go")
   390  	dst := filepath.Join(dir, "out.o")
   391  
   392  	f, err := os.Create(src)
   393  	if err != nil {
   394  		t.Fatal(err)
   395  	}
   396  	for i := range testfile {
   397  		f.Write([]byte(testfile[i].line))
   398  		f.Write([]byte{'\n'})
   399  	}
   400  	f.Close()
   401  
   402  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=-N -l", "-o", dst, src)
   403  	if b, err := cmd.CombinedOutput(); err != nil {
   404  		t.Logf("build: %s\n", string(b))
   405  		t.Fatal(err)
   406  	}
   407  
   408  	pkg, err := objfile.Open(dst)
   409  	if err != nil {
   410  		t.Fatal(err)
   411  	}
   412  	return src, pkg
   413  }