1
2
3
4
5 package ssa_test
6
7 import (
8 "bufio"
9 "bytes"
10 "flag"
11 "fmt"
12 "internal/testenv"
13 "os"
14 "path/filepath"
15 "reflect"
16 "regexp"
17 "runtime"
18 "sort"
19 "strconv"
20 "strings"
21 "testing"
22 )
23
24
25 var asmLine *regexp.Regexp = regexp.MustCompile(`^\s[vb]\d+\s+\d+\s\(\+(\d+)\)`)
26
27
28
29
30
31 var sepRE = regexp.QuoteMeta(string(filepath.Separator))
32 var inlineLine *regexp.Regexp = regexp.MustCompile(`^#\s.*` + sepRE + `[-\w]+\.go:(\d+)`)
33
34
35
36 var testGoArchFlag = flag.String("arch", "", "run test for specified architecture")
37
38 func testGoArch() string {
39 if *testGoArchFlag == "" {
40 return runtime.GOARCH
41 }
42 return *testGoArchFlag
43 }
44
45 func hasRegisterABI() bool {
46 switch testGoArch() {
47 case "amd64", "arm64", "loong64", "ppc64", "ppc64le", "riscv":
48 return true
49 }
50 return false
51 }
52
53 func unixOnly(t *testing.T) {
54 if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
55 t.Skip("this test depends on creating a file with a wonky name, only works for sure on Linux and Darwin")
56 }
57 }
58
59
60 func testDebugLinesDefault(t *testing.T, gcflags, file, function string, wantStmts []int, ignoreRepeats bool) {
61 unixOnly(t)
62 if !hasRegisterABI() {
63 wantStmts = wantStmts[1:]
64 }
65 testDebugLines(t, gcflags, file, function, wantStmts, ignoreRepeats)
66 }
67
68 func TestDebugLinesSayHi(t *testing.T) {
69
70
71
72
73
74 testDebugLinesDefault(t, "-N -l", "sayhi.go", "sayhi", []int{8, 9, 10, 11}, false)
75 }
76
77 func TestDebugLinesPushback(t *testing.T) {
78 unixOnly(t)
79
80 switch testGoArch() {
81 default:
82 t.Skip("skipped for many architectures")
83
84 case "arm64", "amd64":
85 fn := "(*List[go.shape.int_0]).PushBack"
86 if true {
87
88 fn = "(*List[go.shape.int]).PushBack"
89 }
90 testDebugLines(t, "-N -l", "pushback.go", fn, []int{17, 18, 19, 20, 21, 22, 24}, true)
91 }
92 }
93
94 func TestDebugLinesConvert(t *testing.T) {
95 unixOnly(t)
96
97 switch testGoArch() {
98 default:
99 t.Skip("skipped for many architectures")
100
101 case "arm64", "amd64":
102 fn := "G[go.shape.int_0]"
103 if true {
104
105 fn = "G[go.shape.int]"
106 }
107 testDebugLines(t, "-N -l", "convertline.go", fn, []int{9, 10, 11}, true)
108 }
109 }
110
111 func TestInlineLines(t *testing.T) {
112 if runtime.GOARCH != "amd64" && *testGoArchFlag == "" {
113
114 t.Skip("only runs for amd64 unless -arch explicitly supplied")
115 }
116
117 want := [][]int{{3}, {4, 10}, {4, 10, 16}, {4, 10}, {4, 11, 16}, {4, 11}, {4}, {5, 10}, {5, 10, 16}, {5, 10}, {5, 11, 16}, {5, 11}, {5}}
118 testInlineStack(t, "inline-dump.go", "f", want)
119 }
120
121 func TestDebugLines_53456(t *testing.T) {
122 testDebugLinesDefault(t, "-N -l", "b53456.go", "(*T).Inc", []int{15, 16, 17, 18}, true)
123 }
124
125 func compileAndDump(t *testing.T, file, function, moreGCFlags string) []byte {
126 testenv.MustHaveGoBuild(t)
127
128 tmpdir, err := os.MkdirTemp("", "debug_lines_test")
129 if err != nil {
130 panic(fmt.Sprintf("Problem creating TempDir, error %v", err))
131 }
132 if testing.Verbose() {
133 fmt.Printf("Preserving temporary directory %s\n", tmpdir)
134 } else {
135 defer os.RemoveAll(tmpdir)
136 }
137
138 source, err := filepath.Abs(filepath.Join("testdata", file))
139 if err != nil {
140 panic(fmt.Sprintf("Could not get abspath of testdata directory and file, %v", err))
141 }
142
143 cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "foo.o", "-gcflags=-d=ssa/genssa/dump="+function+" "+moreGCFlags, source)
144 cmd.Dir = tmpdir
145 cmd.Env = replaceEnv(cmd.Env, "GOSSADIR", tmpdir)
146 testGoos := "linux"
147 if testGoArch() == "wasm" {
148 testGoos = "js"
149 }
150 cmd.Env = replaceEnv(cmd.Env, "GOOS", testGoos)
151 cmd.Env = replaceEnv(cmd.Env, "GOARCH", testGoArch())
152
153 if testing.Verbose() {
154 fmt.Printf("About to run %s\n", asCommandLine("", cmd))
155 }
156
157 var stdout, stderr strings.Builder
158 cmd.Stdout = &stdout
159 cmd.Stderr = &stderr
160
161 if err := cmd.Run(); err != nil {
162 t.Fatalf("error running cmd %s: %v\nstdout:\n%sstderr:\n%s\n", asCommandLine("", cmd), err, stdout.String(), stderr.String())
163 }
164
165 if s := stderr.String(); s != "" {
166 t.Fatalf("Wanted empty stderr, instead got:\n%s\n", s)
167 }
168
169 dumpFile := filepath.Join(tmpdir, function+"_01__genssa.dump")
170 dumpBytes, err := os.ReadFile(dumpFile)
171 if err != nil {
172 t.Fatalf("Could not read dump file %s, err=%v", dumpFile, err)
173 }
174 return dumpBytes
175 }
176
177 func sortInlineStacks(x [][]int) {
178 sort.Slice(x, func(i, j int) bool {
179 if len(x[i]) != len(x[j]) {
180 return len(x[i]) < len(x[j])
181 }
182 for k := range x[i] {
183 if x[i][k] != x[j][k] {
184 return x[i][k] < x[j][k]
185 }
186 }
187 return false
188 })
189 }
190
191
192 func testInlineStack(t *testing.T, file, function string, wantStacks [][]int) {
193
194 dumpBytes := compileAndDump(t, file, function, "-N")
195 dump := bufio.NewScanner(bytes.NewReader(dumpBytes))
196 dumpLineNum := 0
197 var gotStmts []int
198 var gotStacks [][]int
199 for dump.Scan() {
200 line := dump.Text()
201 dumpLineNum++
202 matches := inlineLine.FindStringSubmatch(line)
203 if len(matches) == 2 {
204 stmt, err := strconv.ParseInt(matches[1], 10, 32)
205 if err != nil {
206 t.Fatalf("Expected to parse a line number but saw %s instead on dump line #%d, error %v", matches[1], dumpLineNum, err)
207 }
208 if testing.Verbose() {
209 fmt.Printf("Saw stmt# %d for submatch '%s' on dump line #%d = '%s'\n", stmt, matches[1], dumpLineNum, line)
210 }
211 gotStmts = append(gotStmts, int(stmt))
212 } else if len(gotStmts) > 0 {
213 gotStacks = append(gotStacks, gotStmts)
214 gotStmts = nil
215 }
216 }
217 if len(gotStmts) > 0 {
218 gotStacks = append(gotStacks, gotStmts)
219 gotStmts = nil
220 }
221 sortInlineStacks(gotStacks)
222 sortInlineStacks(wantStacks)
223 if !reflect.DeepEqual(wantStacks, gotStacks) {
224 t.Errorf("wanted inlines %+v but got %+v\n%s", wantStacks, gotStacks, dumpBytes)
225 }
226
227 }
228
229
230
231
232
233 func testDebugLines(t *testing.T, gcflags, file, function string, wantStmts []int, ignoreRepeats bool) {
234 dumpBytes := compileAndDump(t, file, function, gcflags)
235 dump := bufio.NewScanner(bytes.NewReader(dumpBytes))
236 var gotStmts []int
237 dumpLineNum := 0
238 for dump.Scan() {
239 line := dump.Text()
240 dumpLineNum++
241 matches := asmLine.FindStringSubmatch(line)
242 if len(matches) == 2 {
243 stmt, err := strconv.ParseInt(matches[1], 10, 32)
244 if err != nil {
245 t.Fatalf("Expected to parse a line number but saw %s instead on dump line #%d, error %v", matches[1], dumpLineNum, err)
246 }
247 if testing.Verbose() {
248 fmt.Printf("Saw stmt# %d for submatch '%s' on dump line #%d = '%s'\n", stmt, matches[1], dumpLineNum, line)
249 }
250 gotStmts = append(gotStmts, int(stmt))
251 }
252 }
253 if ignoreRepeats {
254 newGotStmts := []int{gotStmts[0]}
255 for _, x := range gotStmts {
256 if x != newGotStmts[len(newGotStmts)-1] {
257 newGotStmts = append(newGotStmts, x)
258 }
259 }
260 if !reflect.DeepEqual(wantStmts, newGotStmts) {
261 t.Errorf("wanted stmts %v but got %v (with repeats still in: %v)", wantStmts, newGotStmts, gotStmts)
262 }
263
264 } else {
265 if !reflect.DeepEqual(wantStmts, gotStmts) {
266 t.Errorf("wanted stmts %v but got %v", wantStmts, gotStmts)
267 }
268 }
269 }
270
View as plain text