1
2
3
4
5 package dwarfgen
6
7 import (
8 "debug/dwarf"
9 "fmt"
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 "cmd/internal/objfile"
22 )
23
24 type testline struct {
25
26 line string
27
28
29
30
31
32
33
34
35
36
37 scopes []int
38
39
40
41
42
43 vars []string
44
45
46 decl []string
47
48
49 declBefore []string
50 }
51
52 var testfile = []testline{
53 {line: "package main"},
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 fi(x interface{}) { if a, ok := x.(error); ok { a.Error() } }"},
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: " fi(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"}},
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
211 const detailOutput = false
212
213
214
215
216 func TestScopeRanges(t *testing.T) {
217 testenv.MustHaveGoBuild(t)
218 t.Parallel()
219
220 if runtime.GOOS == "plan9" {
221 t.Skip("skipping on plan9; no DWARF symbol table in executables")
222 }
223
224 dir, err := ioutil.TempDir("", "TestScopeRanges")
225 if err != nil {
226 t.Fatalf("could not create directory: %v", err)
227 }
228 defer os.RemoveAll(dir)
229
230 src, f := gobuild(t, dir, false, testfile)
231 defer f.Close()
232
233
234 src = strings.Replace(src, "\\", "/", -1)
235
236 pcln, err := f.PCLineTable()
237 if err != nil {
238 t.Fatal(err)
239 }
240 dwarfData, err := f.DWARF()
241 if err != nil {
242 t.Fatal(err)
243 }
244 dwarfReader := dwarfData.Reader()
245
246 lines := make(map[line][]*lexblock)
247
248 for {
249 entry, err := dwarfReader.Next()
250 if err != nil {
251 t.Fatal(err)
252 }
253 if entry == nil {
254 break
255 }
256
257 if entry.Tag != dwarf.TagSubprogram {
258 continue
259 }
260
261 name, ok := entry.Val(dwarf.AttrName).(string)
262 if !ok || !strings.HasPrefix(name, "main.Test") {
263 continue
264 }
265
266 var scope lexblock
267 ctxt := scopexplainContext{
268 dwarfData: dwarfData,
269 dwarfReader: dwarfReader,
270 scopegen: 1,
271 }
272
273 readScope(&ctxt, &scope, entry)
274
275 scope.markLines(pcln, lines)
276 }
277
278 anyerror := false
279 for i := range testfile {
280 tgt := testfile[i].scopes
281 out := lines[line{src, i + 1}]
282
283 if detailOutput {
284 t.Logf("%s // %v", testfile[i].line, out)
285 }
286
287 scopesok := checkScopes(tgt, out)
288 if !scopesok {
289 t.Logf("mismatch at line %d %q: expected: %v got: %v\n", i, testfile[i].line, tgt, scopesToString(out))
290 }
291
292 varsok := true
293 if testfile[i].vars != nil {
294 if len(out) > 0 {
295 varsok = checkVars(testfile[i].vars, out[len(out)-1].vars)
296 if !varsok {
297 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)
298 }
299 for j := range testfile[i].decl {
300 if line := declLineForVar(out[len(out)-1].vars, testfile[i].decl[j]); line != i+1 {
301 t.Errorf("wrong declaration line for variable %s, expected %d got: %d", testfile[i].decl[j], i+1, line)
302 }
303 }
304
305 for j := range testfile[i].declBefore {
306 if line := declLineForVar(out[len(out)-1].vars, testfile[i].declBefore[j]); line > i+1 {
307 t.Errorf("wrong declaration line for variable %s, expected %d (or less) got: %d", testfile[i].declBefore[j], i+1, line)
308 }
309 }
310 }
311 }
312
313 anyerror = anyerror || !scopesok || !varsok
314 }
315
316 if anyerror {
317 t.Fatalf("mismatched output")
318 }
319 }
320
321 func scopesToString(v []*lexblock) string {
322 r := make([]string, len(v))
323 for i, s := range v {
324 r[i] = strconv.Itoa(s.id)
325 }
326 return "[ " + strings.Join(r, ", ") + " ]"
327 }
328
329 func checkScopes(tgt []int, out []*lexblock) bool {
330 if len(out) > 0 {
331
332 out = out[1:]
333 }
334 if len(tgt) != len(out) {
335 return false
336 }
337 for i := range tgt {
338 if tgt[i] != out[i].id {
339 return false
340 }
341 }
342 return true
343 }
344
345 func checkVars(tgt []string, out []variable) bool {
346 if len(tgt) != len(out) {
347 return false
348 }
349 for i := range tgt {
350 if tgt[i] != out[i].expr {
351 return false
352 }
353 }
354 return true
355 }
356
357 func declLineForVar(scope []variable, name string) int {
358 for i := range scope {
359 if scope[i].name() == name {
360 return scope[i].declLine
361 }
362 }
363 return -1
364 }
365
366 type lexblock struct {
367 id int
368 ranges [][2]uint64
369 vars []variable
370 scopes []lexblock
371 }
372
373 type variable struct {
374 expr string
375 declLine int
376 }
377
378 func (v *variable) name() string {
379 return strings.Split(v.expr, " ")[1]
380 }
381
382 type line struct {
383 file string
384 lineno int
385 }
386
387 type scopexplainContext struct {
388 dwarfData *dwarf.Data
389 dwarfReader *dwarf.Reader
390 scopegen int
391 }
392
393
394
395
396 func readScope(ctxt *scopexplainContext, scope *lexblock, entry *dwarf.Entry) {
397 var err error
398 scope.ranges, err = ctxt.dwarfData.Ranges(entry)
399 if err != nil {
400 panic(err)
401 }
402 for {
403 e, err := ctxt.dwarfReader.Next()
404 if err != nil {
405 panic(err)
406 }
407 switch e.Tag {
408 case 0:
409 sort.Slice(scope.vars, func(i, j int) bool {
410 return scope.vars[i].expr < scope.vars[j].expr
411 })
412 return
413 case dwarf.TagFormalParameter:
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, "arg", typ))
419 case dwarf.TagVariable:
420 typ, err := ctxt.dwarfData.Type(e.Val(dwarf.AttrType).(dwarf.Offset))
421 if err != nil {
422 panic(err)
423 }
424 scope.vars = append(scope.vars, entryToVar(e, "var", typ))
425 case dwarf.TagLexDwarfBlock:
426 scope.scopes = append(scope.scopes, lexblock{id: ctxt.scopegen})
427 ctxt.scopegen++
428 readScope(ctxt, &scope.scopes[len(scope.scopes)-1], e)
429 }
430 }
431 }
432
433 func entryToVar(e *dwarf.Entry, kind string, typ dwarf.Type) variable {
434 return variable{
435 fmt.Sprintf("%s %s %s", kind, e.Val(dwarf.AttrName).(string), typ.String()),
436 int(e.Val(dwarf.AttrDeclLine).(int64)),
437 }
438 }
439
440
441
442 func (scope *lexblock) markLines(pcln objfile.Liner, lines map[line][]*lexblock) {
443 for _, r := range scope.ranges {
444 for pc := r[0]; pc < r[1]; pc++ {
445 file, lineno, _ := pcln.PCToLine(pc)
446 l := line{file, lineno}
447 if len(lines[l]) == 0 || lines[l][len(lines[l])-1] != scope {
448 lines[l] = append(lines[l], scope)
449 }
450 }
451 }
452
453 for i := range scope.scopes {
454 scope.scopes[i].markLines(pcln, lines)
455 }
456 }
457
458 func gobuild(t *testing.T, dir string, optimized bool, testfile []testline) (string, *objfile.File) {
459 src := filepath.Join(dir, "test.go")
460 dst := filepath.Join(dir, "out.o")
461
462 f, err := os.Create(src)
463 if err != nil {
464 t.Fatal(err)
465 }
466 for i := range testfile {
467 f.Write([]byte(testfile[i].line))
468 f.Write([]byte{'\n'})
469 }
470 f.Close()
471
472 args := []string{"build"}
473 if !optimized {
474 args = append(args, "-gcflags=-N -l")
475 }
476 args = append(args, "-o", dst, src)
477
478 cmd := exec.Command(testenv.GoToolPath(t), args...)
479 if b, err := cmd.CombinedOutput(); err != nil {
480 t.Logf("build: %s\n", string(b))
481 t.Fatal(err)
482 }
483
484 pkg, err := objfile.Open(dst)
485 if err != nil {
486 t.Fatal(err)
487 }
488 return src, pkg
489 }
490
491
492
493 func TestEmptyDwarfRanges(t *testing.T) {
494 testenv.MustHaveGoRun(t)
495 t.Parallel()
496
497 if runtime.GOOS == "plan9" {
498 t.Skip("skipping on plan9; no DWARF symbol table in executables")
499 }
500
501 dir, err := ioutil.TempDir("", "TestEmptyDwarfRanges")
502 if err != nil {
503 t.Fatalf("could not create directory: %v", err)
504 }
505 defer os.RemoveAll(dir)
506
507 _, f := gobuild(t, dir, true, []testline{{line: "package main"}, {line: "func main(){ println(\"hello\") }"}})
508 defer f.Close()
509
510 dwarfData, err := f.DWARF()
511 if err != nil {
512 t.Fatal(err)
513 }
514 dwarfReader := dwarfData.Reader()
515
516 for {
517 entry, err := dwarfReader.Next()
518 if err != nil {
519 t.Fatal(err)
520 }
521 if entry == nil {
522 break
523 }
524
525 ranges, err := dwarfData.Ranges(entry)
526 if err != nil {
527 t.Fatal(err)
528 }
529 if ranges == nil {
530 continue
531 }
532
533 for _, rng := range ranges {
534 if rng[0] == rng[1] {
535 t.Errorf("range entry with start == end: %v", rng)
536 }
537 }
538 }
539 }
540
View as plain text