1
2
3
4
5 package dwarfgen
6
7 import (
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
23 type testline struct {
24
25 line string
26
27
28
29
30
31
32
33
34
35
36 scopes []int
37
38
39
40
41
42 vars []string
43
44
45 decl []string
46
47
48 declBefore []string
49 }
50
51 var 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"}},
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 !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
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
315 func 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
323 func checkScopes(tgt []int, out []*lexblock) bool {
324 if len(out) > 0 {
325
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
339 func 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
351 func 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
360 type lexblock struct {
361 id int
362 ranges [][2]uint64
363 vars []variable
364 scopes []lexblock
365 }
366
367 type variable struct {
368 expr string
369 declLine int
370 }
371
372 func (v *variable) name() string {
373 return strings.Split(v.expr, " ")[1]
374 }
375
376 type line struct {
377 file string
378 lineno int
379 }
380
381 type scopexplainContext struct {
382 dwarfData *dwarf.Data
383 dwarfReader *dwarf.Reader
384 scopegen int
385 }
386
387
388
389
390 func 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
427 func 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
435
436 func (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
452 func 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
486
487 func 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
View as plain text