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