github.com/stingnevermore/go@v0.0.0-20180120041312-3810f5bfed72/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 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 }