• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2018 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 ssa_test
6
7import (
8	cmddwarf "cmd/internal/dwarf"
9	"cmd/internal/quoted"
10	"debug/dwarf"
11	"debug/elf"
12	"debug/macho"
13	"debug/pe"
14	"fmt"
15	"internal/platform"
16	"internal/testenv"
17	"internal/xcoff"
18	"io"
19	"os"
20	"runtime"
21	"sort"
22	"testing"
23)
24
25func open(path string) (*dwarf.Data, error) {
26	if fh, err := elf.Open(path); err == nil {
27		return fh.DWARF()
28	}
29
30	if fh, err := pe.Open(path); err == nil {
31		return fh.DWARF()
32	}
33
34	if fh, err := macho.Open(path); err == nil {
35		return fh.DWARF()
36	}
37
38	if fh, err := xcoff.Open(path); err == nil {
39		return fh.DWARF()
40	}
41
42	return nil, fmt.Errorf("unrecognized executable format")
43}
44
45func must(err error) {
46	if err != nil {
47		panic(err)
48	}
49}
50
51type Line struct {
52	File string
53	Line int
54}
55
56func TestStmtLines(t *testing.T) {
57	if !platform.ExecutableHasDWARF(runtime.GOOS, runtime.GOARCH) {
58		t.Skipf("skipping on %s/%s: no DWARF symbol table in executables", runtime.GOOS, runtime.GOARCH)
59	}
60
61	if runtime.GOOS == "aix" {
62		extld := os.Getenv("CC")
63		if extld == "" {
64			extld = "gcc"
65		}
66		extldArgs, err := quoted.Split(extld)
67		if err != nil {
68			t.Fatal(err)
69		}
70		enabled, err := cmddwarf.IsDWARFEnabledOnAIXLd(extldArgs)
71		if err != nil {
72			t.Fatal(err)
73		}
74		if !enabled {
75			t.Skip("skipping on aix: no DWARF with ld version < 7.2.2 ")
76		}
77	}
78
79	// Build cmd/go forcing DWARF enabled, as a large test case.
80	dir := t.TempDir()
81	out, err := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-w=0", "-o", dir+"/test.exe", "cmd/go").CombinedOutput()
82	if err != nil {
83		t.Fatalf("go build: %v\n%s", err, out)
84	}
85
86	lines := map[Line]bool{}
87	dw, err := open(dir + "/test.exe")
88	must(err)
89	rdr := dw.Reader()
90	rdr.Seek(0)
91	for {
92		e, err := rdr.Next()
93		must(err)
94		if e == nil {
95			break
96		}
97		if e.Tag != dwarf.TagCompileUnit {
98			continue
99		}
100		pkgname, _ := e.Val(dwarf.AttrName).(string)
101		if pkgname == "runtime" {
102			continue
103		}
104		if pkgname == "crypto/internal/nistec/fiat" {
105			continue // golang.org/issue/49372
106		}
107		if e.Val(dwarf.AttrStmtList) == nil {
108			continue
109		}
110		lrdr, err := dw.LineReader(e)
111		must(err)
112
113		var le dwarf.LineEntry
114
115		for {
116			err := lrdr.Next(&le)
117			if err == io.EOF {
118				break
119			}
120			must(err)
121			fl := Line{le.File.Name, le.Line}
122			lines[fl] = lines[fl] || le.IsStmt
123		}
124	}
125
126	nonStmtLines := []Line{}
127	for line, isstmt := range lines {
128		if !isstmt {
129			nonStmtLines = append(nonStmtLines, line)
130		}
131	}
132
133	var m int
134	if runtime.GOARCH == "amd64" {
135		m = 1 // > 99% obtained on amd64, no backsliding
136	} else if runtime.GOARCH == "riscv64" {
137		m = 3 // XXX temporary update threshold to 97% for regabi
138	} else {
139		m = 2 // expect 98% elsewhere.
140	}
141
142	if len(nonStmtLines)*100 > m*len(lines) {
143		t.Errorf("Saw too many (%s, > %d%%) lines without statement marks, total=%d, nostmt=%d ('-run TestStmtLines -v' lists failing lines)\n", runtime.GOARCH, m, len(lines), len(nonStmtLines))
144	}
145	t.Logf("Saw %d out of %d lines without statement marks", len(nonStmtLines), len(lines))
146	if testing.Verbose() {
147		sort.Slice(nonStmtLines, func(i, j int) bool {
148			if nonStmtLines[i].File != nonStmtLines[j].File {
149				return nonStmtLines[i].File < nonStmtLines[j].File
150			}
151			return nonStmtLines[i].Line < nonStmtLines[j].Line
152		})
153		for _, l := range nonStmtLines {
154			t.Logf("%s:%d has no DWARF is_stmt mark\n", l.File, l.Line)
155		}
156	}
157	t.Logf("total=%d, nostmt=%d\n", len(lines), len(nonStmtLines))
158}
159