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 5package dwarfgen 6 7import ( 8 "debug/dwarf" 9 "fmt" 10 "internal/platform" 11 "internal/testenv" 12 "os" 13 "path/filepath" 14 "runtime" 15 "sort" 16 "strconv" 17 "strings" 18 "testing" 19 20 "cmd/internal/objfile" 21) 22 23type testline struct { 24 // line is one line of go source 25 line string 26 27 // scopes is a list of scope IDs of all the lexical scopes that this line 28 // of code belongs to. 29 // Scope IDs are assigned by traversing the tree of lexical blocks of a 30 // function in pre-order 31 // Scope IDs are function specific, i.e. scope 0 is always the root scope 32 // of the function that this line belongs to. Empty scopes are not assigned 33 // an ID (because they are not saved in debug_info). 34 // Scope 0 is always omitted from this list since all lines always belong 35 // to it. 36 scopes []int 37 38 // vars is the list of variables that belong in scopes[len(scopes)-1]. 39 // Local variables are prefixed with "var ", formal parameters with "arg ". 40 // Must be ordered alphabetically. 41 // Set to nil to skip the check. 42 vars []string 43 44 // decl is the list of variables declared at this line. 45 decl []string 46 47 // declBefore is the list of variables declared at or before this line. 48 declBefore []string 49} 50 51var testfile = []testline{ 52 {line: "package main"}, 53 {line: "var sink any"}, 54 {line: "func f1(x int) { }"}, 55 {line: "func f2(x int) { }"}, 56 {line: "func f3(x int) { }"}, 57 {line: "func f4(x int) { }"}, 58 {line: "func f5(x int) { }"}, 59 {line: "func f6(x int) { }"}, 60 {line: "func leak(x interface{}) { sink = x }"}, 61 {line: "func gret1() int { return 2 }"}, 62 {line: "func gretbool() bool { return true }"}, 63 {line: "func gret3() (int, int, int) { return 0, 1, 2 }"}, 64 {line: "var v = []int{ 0, 1, 2 }"}, 65 {line: "var ch = make(chan int)"}, 66 {line: "var floatch = make(chan float64)"}, 67 {line: "var iface interface{}"}, 68 {line: "func TestNestedFor() {", vars: []string{"var a int"}}, 69 {line: " a := 0", decl: []string{"a"}}, 70 {line: " f1(a)"}, 71 {line: " for i := 0; i < 5; i++ {", scopes: []int{1}, vars: []string{"var i int"}, decl: []string{"i"}}, 72 {line: " f2(i)", scopes: []int{1}}, 73 {line: " for i := 0; i < 5; i++ {", scopes: []int{1, 2}, vars: []string{"var i int"}, decl: []string{"i"}}, 74 {line: " f3(i)", scopes: []int{1, 2}}, 75 {line: " }"}, 76 {line: " f4(i)", scopes: []int{1}}, 77 {line: " }"}, 78 {line: " f5(a)"}, 79 {line: "}"}, 80 {line: "func TestOas2() {", vars: []string{}}, 81 {line: " if a, b, c := gret3(); a != 1 {", scopes: []int{1}, vars: []string{"var a int", "var b int", "var c int"}}, 82 {line: " f1(a)", scopes: []int{1}}, 83 {line: " f1(b)", scopes: []int{1}}, 84 {line: " f1(c)", scopes: []int{1}}, 85 {line: " }"}, 86 {line: " for i, x := range v {", scopes: []int{2}, vars: []string{"var i int", "var x int"}}, 87 {line: " f1(i)", scopes: []int{2}}, 88 {line: " f1(x)", scopes: []int{2}}, 89 {line: " }"}, 90 {line: " if a, ok := <- ch; ok {", scopes: []int{3}, vars: []string{"var a int", "var ok bool"}}, 91 {line: " f1(a)", scopes: []int{3}}, 92 {line: " }"}, 93 {line: " if a, ok := iface.(int); ok {", scopes: []int{4}, vars: []string{"var a int", "var ok bool"}}, 94 {line: " f1(a)", scopes: []int{4}}, 95 {line: " }"}, 96 {line: "}"}, 97 {line: "func TestIfElse() {"}, 98 {line: " if x := gret1(); x != 0 {", scopes: []int{1}, vars: []string{"var x int"}}, 99 {line: " a := 0", scopes: []int{1, 2}, vars: []string{"var a int"}}, 100 {line: " f1(a); f1(x)", scopes: []int{1, 2}}, 101 {line: " } else {"}, 102 {line: " b := 1", scopes: []int{1, 3}, vars: []string{"var b int"}}, 103 {line: " f1(b); f1(x+1)", scopes: []int{1, 3}}, 104 {line: " }"}, 105 {line: "}"}, 106 {line: "func TestSwitch() {", vars: []string{}}, 107 {line: " switch x := gret1(); x {", scopes: []int{1}, vars: []string{"var x int"}}, 108 {line: " case 0:", scopes: []int{1, 2}}, 109 {line: " i := x + 5", scopes: []int{1, 2}, vars: []string{"var i int"}}, 110 {line: " f1(x); f1(i)", scopes: []int{1, 2}}, 111 {line: " case 1:", scopes: []int{1, 3}}, 112 {line: " j := x + 10", scopes: []int{1, 3}, vars: []string{"var j int"}}, 113 {line: " f1(x); f1(j)", scopes: []int{1, 3}}, 114 {line: " case 2:", scopes: []int{1, 4}}, 115 {line: " k := x + 2", scopes: []int{1, 4}, vars: []string{"var k int"}}, 116 {line: " f1(x); f1(k)", scopes: []int{1, 4}}, 117 {line: " }"}, 118 {line: "}"}, 119 {line: "func TestTypeSwitch() {", vars: []string{}}, 120 {line: " switch x := iface.(type) {"}, 121 {line: " case int:", scopes: []int{1}}, 122 {line: " f1(x)", scopes: []int{1}, vars: []string{"var x int"}}, 123 {line: " case uint8:", scopes: []int{2}}, 124 {line: " f1(int(x))", scopes: []int{2}, vars: []string{"var x uint8"}}, 125 {line: " case float64:", scopes: []int{3}}, 126 {line: " f1(int(x)+1)", scopes: []int{3}, vars: []string{"var x float64"}}, 127 {line: " }"}, 128 {line: "}"}, 129 {line: "func TestSelectScope() {"}, 130 {line: " select {"}, 131 {line: " case i := <- ch:", scopes: []int{1}}, 132 {line: " f1(i)", scopes: []int{1}, vars: []string{"var i int"}}, 133 {line: " case f := <- floatch:", scopes: []int{2}}, 134 {line: " f1(int(f))", scopes: []int{2}, vars: []string{"var f float64"}}, 135 {line: " }"}, 136 {line: "}"}, 137 {line: "func TestBlock() {", vars: []string{"var a int"}}, 138 {line: " a := 1"}, 139 {line: " {"}, 140 {line: " b := 2", scopes: []int{1}, vars: []string{"var b int"}}, 141 {line: " f1(b)", scopes: []int{1}}, 142 {line: " f1(a)", scopes: []int{1}}, 143 {line: " }"}, 144 {line: "}"}, 145 {line: "func TestDiscontiguousRanges() {", vars: []string{"var a int"}}, 146 {line: " a := 0"}, 147 {line: " f1(a)"}, 148 {line: " {"}, 149 {line: " b := 0", scopes: []int{1}, vars: []string{"var b int"}}, 150 {line: " f2(b)", scopes: []int{1}}, 151 {line: " if gretbool() {", scopes: []int{1}}, 152 {line: " c := 0", scopes: []int{1, 2}, vars: []string{"var c int"}}, 153 {line: " f3(c)", scopes: []int{1, 2}}, 154 {line: " } else {"}, 155 {line: " c := 1.1", scopes: []int{1, 3}, vars: []string{"var c float64"}}, 156 {line: " f4(int(c))", scopes: []int{1, 3}}, 157 {line: " }"}, 158 {line: " f5(b)", scopes: []int{1}}, 159 {line: " }"}, 160 {line: " f6(a)"}, 161 {line: "}"}, 162 {line: "func TestClosureScope() {", vars: []string{"var a int", "var b int", "var f func(int)"}}, 163 {line: " a := 1; b := 1"}, 164 {line: " f := func(c int) {", scopes: []int{0}, vars: []string{"arg c int", "var &b *int", "var a int", "var d int"}, declBefore: []string{"&b", "a"}}, 165 {line: " d := 3"}, 166 {line: " f1(c); f1(d)"}, 167 {line: " if e := 3; e != 0 {", scopes: []int{1}, vars: []string{"var e int"}}, 168 {line: " f1(e)", scopes: []int{1}}, 169 {line: " f1(a)", scopes: []int{1}}, 170 {line: " b = 2", scopes: []int{1}}, 171 {line: " }"}, 172 {line: " }"}, 173 {line: " f(3); f1(b)"}, 174 {line: "}"}, 175 {line: "func TestEscape() {"}, 176 {line: " a := 1", vars: []string{"var a int"}}, 177 {line: " {"}, 178 {line: " b := 2", scopes: []int{1}, vars: []string{"var &b *int", "var p *int"}}, 179 {line: " p := &b", scopes: []int{1}}, 180 {line: " f1(a)", scopes: []int{1}}, 181 {line: " leak(p)", scopes: []int{1}}, 182 {line: " }"}, 183 {line: "}"}, 184 {line: "var fglob func() int"}, 185 {line: "func TestCaptureVar(flag bool) {"}, 186 {line: " a := 1", vars: []string{"arg flag bool", "var a int"}}, // TODO(register args) restore "arg ~r1 func() int", 187 {line: " if flag {"}, 188 {line: " b := 2", scopes: []int{1}, vars: []string{"var b int", "var f func() int"}}, 189 {line: " f := func() int {", scopes: []int{1, 0}}, 190 {line: " return b + 1"}, 191 {line: " }"}, 192 {line: " fglob = f", scopes: []int{1}}, 193 {line: " }"}, 194 {line: " f1(a)"}, 195 {line: "}"}, 196 {line: "func main() {"}, 197 {line: " TestNestedFor()"}, 198 {line: " TestOas2()"}, 199 {line: " TestIfElse()"}, 200 {line: " TestSwitch()"}, 201 {line: " TestTypeSwitch()"}, 202 {line: " TestSelectScope()"}, 203 {line: " TestBlock()"}, 204 {line: " TestDiscontiguousRanges()"}, 205 {line: " TestClosureScope()"}, 206 {line: " TestEscape()"}, 207 {line: " TestCaptureVar(true)"}, 208 {line: "}"}, 209} 210 211const detailOutput = false 212 213// Compiles testfile checks that the description of lexical blocks emitted 214// by the linker in debug_info, for each function in the main package, 215// corresponds to what we expect it to be. 216func TestScopeRanges(t *testing.T) { 217 testenv.MustHaveGoBuild(t) 218 t.Parallel() 219 220 if !platform.ExecutableHasDWARF(runtime.GOOS, runtime.GOARCH) { 221 t.Skipf("skipping on %s/%s: no DWARF symbol table in executables", runtime.GOOS, runtime.GOARCH) 222 } 223 224 src, f := gobuild(t, t.TempDir(), false, testfile) 225 defer f.Close() 226 227 // the compiler uses forward slashes for paths even on windows 228 src = strings.Replace(src, "\\", "/", -1) 229 230 pcln, err := f.PCLineTable() 231 if err != nil { 232 t.Fatal(err) 233 } 234 dwarfData, err := f.DWARF() 235 if err != nil { 236 t.Fatal(err) 237 } 238 dwarfReader := dwarfData.Reader() 239 240 lines := make(map[line][]*lexblock) 241 242 for { 243 entry, err := dwarfReader.Next() 244 if err != nil { 245 t.Fatal(err) 246 } 247 if entry == nil { 248 break 249 } 250 251 if entry.Tag != dwarf.TagSubprogram { 252 continue 253 } 254 255 name, ok := entry.Val(dwarf.AttrName).(string) 256 if !ok || !strings.HasPrefix(name, "main.Test") { 257 continue 258 } 259 260 var scope lexblock 261 ctxt := scopexplainContext{ 262 dwarfData: dwarfData, 263 dwarfReader: dwarfReader, 264 scopegen: 1, 265 } 266 267 readScope(&ctxt, &scope, entry) 268 269 scope.markLines(pcln, lines) 270 } 271 272 anyerror := false 273 for i := range testfile { 274 tgt := testfile[i].scopes 275 out := lines[line{src, i + 1}] 276 277 if detailOutput { 278 t.Logf("%s // %v", testfile[i].line, out) 279 } 280 281 scopesok := checkScopes(tgt, out) 282 if !scopesok { 283 t.Logf("mismatch at line %d %q: expected: %v got: %v\n", i, testfile[i].line, tgt, scopesToString(out)) 284 } 285 286 varsok := true 287 if testfile[i].vars != nil { 288 if len(out) > 0 { 289 varsok = checkVars(testfile[i].vars, out[len(out)-1].vars) 290 if !varsok { 291 t.Logf("variable mismatch at line %d %q for scope %d: expected: %v got: %v\n", i+1, testfile[i].line, out[len(out)-1].id, testfile[i].vars, out[len(out)-1].vars) 292 } 293 for j := range testfile[i].decl { 294 if line := declLineForVar(out[len(out)-1].vars, testfile[i].decl[j]); line != i+1 { 295 t.Errorf("wrong declaration line for variable %s, expected %d got: %d", testfile[i].decl[j], i+1, line) 296 } 297 } 298 299 for j := range testfile[i].declBefore { 300 if line := declLineForVar(out[len(out)-1].vars, testfile[i].declBefore[j]); line > i+1 { 301 t.Errorf("wrong declaration line for variable %s, expected %d (or less) got: %d", testfile[i].declBefore[j], i+1, line) 302 } 303 } 304 } 305 } 306 307 anyerror = anyerror || !scopesok || !varsok 308 } 309 310 if anyerror { 311 t.Fatalf("mismatched output") 312 } 313} 314 315func scopesToString(v []*lexblock) string { 316 r := make([]string, len(v)) 317 for i, s := range v { 318 r[i] = strconv.Itoa(s.id) 319 } 320 return "[ " + strings.Join(r, ", ") + " ]" 321} 322 323func checkScopes(tgt []int, out []*lexblock) bool { 324 if len(out) > 0 { 325 // omit scope 0 326 out = out[1:] 327 } 328 if len(tgt) != len(out) { 329 return false 330 } 331 for i := range tgt { 332 if tgt[i] != out[i].id { 333 return false 334 } 335 } 336 return true 337} 338 339func checkVars(tgt []string, out []variable) bool { 340 if len(tgt) != len(out) { 341 return false 342 } 343 for i := range tgt { 344 if tgt[i] != out[i].expr { 345 return false 346 } 347 } 348 return true 349} 350 351func declLineForVar(scope []variable, name string) int { 352 for i := range scope { 353 if scope[i].name() == name { 354 return scope[i].declLine 355 } 356 } 357 return -1 358} 359 360type lexblock struct { 361 id int 362 ranges [][2]uint64 363 vars []variable 364 scopes []lexblock 365} 366 367type variable struct { 368 expr string 369 declLine int 370} 371 372func (v *variable) name() string { 373 return strings.Split(v.expr, " ")[1] 374} 375 376type line struct { 377 file string 378 lineno int 379} 380 381type scopexplainContext struct { 382 dwarfData *dwarf.Data 383 dwarfReader *dwarf.Reader 384 scopegen int 385} 386 387// readScope reads the DW_TAG_lexical_block or the DW_TAG_subprogram in 388// entry and writes a description in scope. 389// Nested DW_TAG_lexical_block entries are read recursively. 390func readScope(ctxt *scopexplainContext, scope *lexblock, entry *dwarf.Entry) { 391 var err error 392 scope.ranges, err = ctxt.dwarfData.Ranges(entry) 393 if err != nil { 394 panic(err) 395 } 396 for { 397 e, err := ctxt.dwarfReader.Next() 398 if err != nil { 399 panic(err) 400 } 401 switch e.Tag { 402 case 0: 403 sort.Slice(scope.vars, func(i, j int) bool { 404 return scope.vars[i].expr < scope.vars[j].expr 405 }) 406 return 407 case dwarf.TagFormalParameter: 408 typ, err := ctxt.dwarfData.Type(e.Val(dwarf.AttrType).(dwarf.Offset)) 409 if err != nil { 410 panic(err) 411 } 412 scope.vars = append(scope.vars, entryToVar(e, "arg", typ)) 413 case dwarf.TagVariable: 414 typ, err := ctxt.dwarfData.Type(e.Val(dwarf.AttrType).(dwarf.Offset)) 415 if err != nil { 416 panic(err) 417 } 418 scope.vars = append(scope.vars, entryToVar(e, "var", typ)) 419 case dwarf.TagLexDwarfBlock: 420 scope.scopes = append(scope.scopes, lexblock{id: ctxt.scopegen}) 421 ctxt.scopegen++ 422 readScope(ctxt, &scope.scopes[len(scope.scopes)-1], e) 423 } 424 } 425} 426 427func entryToVar(e *dwarf.Entry, kind string, typ dwarf.Type) variable { 428 return variable{ 429 fmt.Sprintf("%s %s %s", kind, e.Val(dwarf.AttrName).(string), typ.String()), 430 int(e.Val(dwarf.AttrDeclLine).(int64)), 431 } 432} 433 434// markLines marks all lines that belong to this scope with this scope 435// Recursively calls markLines for all children scopes. 436func (scope *lexblock) markLines(pcln objfile.Liner, lines map[line][]*lexblock) { 437 for _, r := range scope.ranges { 438 for pc := r[0]; pc < r[1]; pc++ { 439 file, lineno, _ := pcln.PCToLine(pc) 440 l := line{file, lineno} 441 if len(lines[l]) == 0 || lines[l][len(lines[l])-1] != scope { 442 lines[l] = append(lines[l], scope) 443 } 444 } 445 } 446 447 for i := range scope.scopes { 448 scope.scopes[i].markLines(pcln, lines) 449 } 450} 451 452func gobuild(t *testing.T, dir string, optimized bool, testfile []testline) (string, *objfile.File) { 453 src := filepath.Join(dir, "test.go") 454 dst := filepath.Join(dir, "out.o") 455 456 f, err := os.Create(src) 457 if err != nil { 458 t.Fatal(err) 459 } 460 for i := range testfile { 461 f.Write([]byte(testfile[i].line)) 462 f.Write([]byte{'\n'}) 463 } 464 f.Close() 465 466 args := []string{"build"} 467 if !optimized { 468 args = append(args, "-gcflags=-N -l") 469 } 470 args = append(args, "-o", dst, src) 471 472 cmd := testenv.Command(t, testenv.GoToolPath(t), args...) 473 if b, err := cmd.CombinedOutput(); err != nil { 474 t.Logf("build: %s\n", string(b)) 475 t.Fatal(err) 476 } 477 478 pkg, err := objfile.Open(dst) 479 if err != nil { 480 t.Fatal(err) 481 } 482 return src, pkg 483} 484 485// TestEmptyDwarfRanges tests that no list entry in debug_ranges has start == end. 486// See issue #23928. 487func TestEmptyDwarfRanges(t *testing.T) { 488 testenv.MustHaveGoRun(t) 489 t.Parallel() 490 491 if !platform.ExecutableHasDWARF(runtime.GOOS, runtime.GOARCH) { 492 t.Skipf("skipping on %s/%s: no DWARF symbol table in executables", runtime.GOOS, runtime.GOARCH) 493 } 494 495 _, f := gobuild(t, t.TempDir(), true, []testline{{line: "package main"}, {line: "func main(){ println(\"hello\") }"}}) 496 defer f.Close() 497 498 dwarfData, err := f.DWARF() 499 if err != nil { 500 t.Fatal(err) 501 } 502 dwarfReader := dwarfData.Reader() 503 504 for { 505 entry, err := dwarfReader.Next() 506 if err != nil { 507 t.Fatal(err) 508 } 509 if entry == nil { 510 break 511 } 512 513 ranges, err := dwarfData.Ranges(entry) 514 if err != nil { 515 t.Fatal(err) 516 } 517 if ranges == nil { 518 continue 519 } 520 521 for _, rng := range ranges { 522 if rng[0] == rng[1] { 523 t.Errorf("range entry with start == end: %v", rng) 524 } 525 } 526 } 527} 528