• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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