github.com/zebozhuang/go@v0.0.0-20200207033046-f8a98f6f5c5d/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 main() {"}, 177 {line: " TestNestedFor()"}, 178 {line: " TestOas2()"}, 179 {line: " TestIfElse()"}, 180 {line: " TestSwitch()"}, 181 {line: " TestTypeSwitch()"}, 182 {line: " TestSelectScope()"}, 183 {line: " TestBlock()"}, 184 {line: " TestDiscontiguousRanges()"}, 185 {line: " TestClosureScope()"}, 186 {line: " TestEscape()"}, 187 {line: "}"}, 188 } 189 190 const detailOutput = false 191 192 // Compiles testfile checks that the description of lexical blocks emitted 193 // by the linker in debug_info, for each function in the main package, 194 // corresponds to what we expect it to be. 195 func TestScopeRanges(t *testing.T) { 196 testenv.MustHaveGoBuild(t) 197 198 if runtime.GOOS == "plan9" { 199 t.Skip("skipping on plan9; no DWARF symbol table in executables") 200 } 201 202 dir, err := ioutil.TempDir("", "TestScopeRanges") 203 if err != nil { 204 t.Fatalf("could not create directory: %v", err) 205 } 206 defer os.RemoveAll(dir) 207 208 src, f := gobuild(t, dir, testfile) 209 defer f.Close() 210 211 // the compiler uses forward slashes for paths even on windows 212 src = strings.Replace(src, "\\", "/", -1) 213 214 pcln, err := f.PCLineTable() 215 if err != nil { 216 t.Fatal(err) 217 } 218 dwarfData, err := f.DWARF() 219 if err != nil { 220 t.Fatal(err) 221 } 222 dwarfReader := dwarfData.Reader() 223 224 lines := make(map[line][]*lexblock) 225 226 for { 227 entry, err := dwarfReader.Next() 228 if err != nil { 229 t.Fatal(err) 230 } 231 if entry == nil { 232 break 233 } 234 235 if entry.Tag != dwarf.TagSubprogram { 236 continue 237 } 238 239 name, ok := entry.Val(dwarf.AttrName).(string) 240 if !ok || !strings.HasPrefix(name, "main.Test") { 241 continue 242 } 243 244 var scope lexblock 245 ctxt := scopexplainContext{ 246 dwarfData: dwarfData, 247 dwarfReader: dwarfReader, 248 scopegen: 1, 249 } 250 251 readScope(&ctxt, &scope, entry) 252 253 scope.markLines(pcln, lines) 254 } 255 256 anyerror := false 257 for i := range testfile { 258 tgt := testfile[i].scopes 259 out := lines[line{src, i + 1}] 260 261 if detailOutput { 262 t.Logf("%s // %v", testfile[i].line, out) 263 } 264 265 scopesok := checkScopes(tgt, out) 266 if !scopesok { 267 t.Logf("mismatch at line %d %q: expected: %v got: %v\n", i, testfile[i].line, tgt, scopesToString(out)) 268 } 269 270 varsok := true 271 if testfile[i].vars != nil { 272 if len(out) > 0 { 273 varsok = checkVars(testfile[i].vars, out[len(out)-1].vars) 274 if !varsok { 275 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) 276 } 277 } 278 } 279 280 anyerror = anyerror || !scopesok || !varsok 281 } 282 283 if anyerror { 284 t.Fatalf("mismatched output") 285 } 286 } 287 288 func scopesToString(v []*lexblock) string { 289 r := make([]string, len(v)) 290 for i, s := range v { 291 r[i] = strconv.Itoa(s.id) 292 } 293 return "[ " + strings.Join(r, ", ") + " ]" 294 } 295 296 func checkScopes(tgt []int, out []*lexblock) bool { 297 if len(out) > 0 { 298 // omit scope 0 299 out = out[1:] 300 } 301 if len(tgt) != len(out) { 302 return false 303 } 304 for i := range tgt { 305 if tgt[i] != out[i].id { 306 return false 307 } 308 } 309 return true 310 } 311 312 func checkVars(tgt, out []string) bool { 313 if len(tgt) != len(out) { 314 return false 315 } 316 for i := range tgt { 317 if tgt[i] != out[i] { 318 return false 319 } 320 } 321 return true 322 } 323 324 type lexblock struct { 325 id int 326 ranges [][2]uint64 327 vars []string 328 scopes []lexblock 329 } 330 331 type line struct { 332 file string 333 lineno int 334 } 335 336 type scopexplainContext struct { 337 dwarfData *dwarf.Data 338 dwarfReader *dwarf.Reader 339 scopegen int 340 lines map[line][]int 341 } 342 343 // readScope reads the DW_TAG_lexical_block or the DW_TAG_subprogram in 344 // entry and writes a description in scope. 345 // Nested DW_TAG_lexical_block entries are read recursively. 346 func readScope(ctxt *scopexplainContext, scope *lexblock, entry *dwarf.Entry) { 347 var err error 348 scope.ranges, err = ctxt.dwarfData.Ranges(entry) 349 if err != nil { 350 panic(err) 351 } 352 for { 353 e, err := ctxt.dwarfReader.Next() 354 if err != nil { 355 panic(err) 356 } 357 switch e.Tag { 358 case 0: 359 sort.Strings(scope.vars) 360 return 361 case dwarf.TagFormalParameter: 362 typ, err := ctxt.dwarfData.Type(e.Val(dwarf.AttrType).(dwarf.Offset)) 363 if err != nil { 364 panic(err) 365 } 366 scope.vars = append(scope.vars, "arg "+e.Val(dwarf.AttrName).(string)+" "+typ.String()) 367 case dwarf.TagVariable: 368 typ, err := ctxt.dwarfData.Type(e.Val(dwarf.AttrType).(dwarf.Offset)) 369 if err != nil { 370 panic(err) 371 } 372 scope.vars = append(scope.vars, "var "+e.Val(dwarf.AttrName).(string)+" "+typ.String()) 373 case dwarf.TagLexDwarfBlock: 374 scope.scopes = append(scope.scopes, lexblock{id: ctxt.scopegen}) 375 ctxt.scopegen++ 376 readScope(ctxt, &scope.scopes[len(scope.scopes)-1], e) 377 } 378 } 379 } 380 381 // markLines marks all lines that belong to this scope with this scope 382 // Recursively calls markLines for all children scopes. 383 func (scope *lexblock) markLines(pcln objfile.Liner, lines map[line][]*lexblock) { 384 for _, r := range scope.ranges { 385 for pc := r[0]; pc < r[1]; pc++ { 386 file, lineno, _ := pcln.PCToLine(pc) 387 l := line{file, lineno} 388 if len(lines[l]) == 0 || lines[l][len(lines[l])-1] != scope { 389 lines[l] = append(lines[l], scope) 390 } 391 } 392 } 393 394 for i := range scope.scopes { 395 scope.scopes[i].markLines(pcln, lines) 396 } 397 } 398 399 func gobuild(t *testing.T, dir string, testfile []testline) (string, *objfile.File) { 400 src := filepath.Join(dir, "test.go") 401 dst := filepath.Join(dir, "out.o") 402 403 f, err := os.Create(src) 404 if err != nil { 405 t.Fatal(err) 406 } 407 for i := range testfile { 408 f.Write([]byte(testfile[i].line)) 409 f.Write([]byte{'\n'}) 410 } 411 f.Close() 412 413 cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=-N -l", "-o", dst, src) 414 if b, err := cmd.CombinedOutput(); err != nil { 415 t.Logf("build: %s\n", string(b)) 416 t.Fatal(err) 417 } 418 419 pkg, err := objfile.Open(dst) 420 if err != nil { 421 t.Fatal(err) 422 } 423 return src, pkg 424 }