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