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