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 }