]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/compile/internal/logopt/logopt_test.go
1c48351ab2900bfc3ef6e545b7e48efed4f25b35
[gostls13.git] / src / cmd / compile / internal / logopt / logopt_test.go
1 // Copyright 2019 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
5 package logopt
6
7 import (
8         "internal/testenv"
9         "os"
10         "path/filepath"
11         "runtime"
12         "strings"
13         "testing"
14 )
15
16 const srcCode = `package x
17 type pair struct {a,b int}
18 func bar(y *pair) *int {
19         return &y.b
20 }
21 var a []int
22 func foo(w, z *pair) *int {
23         if *bar(w) > 0 {
24                 return bar(z)
25         }
26         if a[1] > 0 {
27                 a = a[:2]
28         }
29         return &a[0]
30 }
31
32 // address taking prevents closure inlining
33 func n() int {
34         foo := func() int { return 1 }
35         bar := &foo
36         x := (*bar)() + foo()
37         return x
38 }
39 `
40
41 func want(t *testing.T, out string, desired string) {
42         // On Windows, Unicode escapes in the JSON output end up "normalized" elsewhere to /u....,
43         // so "normalize" what we're looking for to match that.
44         s := strings.ReplaceAll(desired, string(os.PathSeparator), "/")
45         if !strings.Contains(out, s) {
46                 t.Errorf("did not see phrase %s in \n%s", s, out)
47         }
48 }
49
50 func wantN(t *testing.T, out string, desired string, n int) {
51         if strings.Count(out, desired) != n {
52                 t.Errorf("expected exactly %d occurrences of %s in \n%s", n, desired, out)
53         }
54 }
55
56 func TestPathStuff(t *testing.T) {
57         sep := string(filepath.Separator)
58         if path, whine := parseLogPath("file:///c:foo"); path != "c:foo" || whine != "" { // good path
59                 t.Errorf("path='%s', whine='%s'", path, whine)
60         }
61         if path, whine := parseLogPath("file:///foo"); path != sep+"foo" || whine != "" { // good path
62                 t.Errorf("path='%s', whine='%s'", path, whine)
63         }
64         if path, whine := parseLogPath("foo"); path != "" || whine == "" { // BAD path
65                 t.Errorf("path='%s', whine='%s'", path, whine)
66         }
67         if sep == "\\" { // On WINDOWS ONLY
68                 if path, whine := parseLogPath("C:/foo"); path != "C:\\foo" || whine != "" { // good path
69                         t.Errorf("path='%s', whine='%s'", path, whine)
70                 }
71                 if path, whine := parseLogPath("c:foo"); path != "" || whine == "" { // BAD path
72                         t.Errorf("path='%s', whine='%s'", path, whine)
73                 }
74                 if path, whine := parseLogPath("/foo"); path != "" || whine == "" { // BAD path
75                         t.Errorf("path='%s', whine='%s'", path, whine)
76                 }
77         } else { // ON UNIX ONLY
78                 if path, whine := parseLogPath("/foo"); path != sep+"foo" || whine != "" { // good path
79                         t.Errorf("path='%s', whine='%s'", path, whine)
80                 }
81         }
82 }
83
84 func TestLogOpt(t *testing.T) {
85         t.Parallel()
86
87         testenv.MustHaveGoBuild(t)
88
89         dir := fixSlash(t.TempDir()) // Normalize the directory name as much as possible, for Windows testing
90         src := filepath.Join(dir, "file.go")
91         if err := os.WriteFile(src, []byte(srcCode), 0644); err != nil {
92                 t.Fatal(err)
93         }
94
95         outfile := filepath.Join(dir, "file.o")
96
97         t.Run("JSON_fails", func(t *testing.T) {
98                 // Test malformed flag
99                 out, err := testLogOpt(t, "-json=foo", src, outfile)
100                 if err == nil {
101                         t.Error("-json=foo succeeded unexpectedly")
102                 }
103                 want(t, out, "option should be")
104                 want(t, out, "number")
105
106                 // Test a version number that is currently unsupported (and should remain unsupported for a while)
107                 out, err = testLogOpt(t, "-json=9,foo", src, outfile)
108                 if err == nil {
109                         t.Error("-json=0,foo succeeded unexpectedly")
110                 }
111                 want(t, out, "version must be")
112
113         })
114
115         // replace d (dir)  with t ("tmpdir") and convert path separators to '/'
116         normalize := func(out []byte, d, t string) string {
117                 s := string(out)
118                 s = strings.ReplaceAll(s, d, t)
119                 s = strings.ReplaceAll(s, string(os.PathSeparator), "/")
120                 return s
121         }
122
123         // Ensure that <128 byte copies are not reported and that 128-byte copies are.
124         // Check at both 1 and 8-byte alignments.
125         t.Run("Copy", func(t *testing.T) {
126                 const copyCode = `package x
127 func s128a1(x *[128]int8) [128]int8 {
128         return *x
129 }
130 func s127a1(x *[127]int8) [127]int8 {
131         return *x
132 }
133 func s16a8(x *[16]int64) [16]int64 {
134         return *x
135 }
136 func s15a8(x *[15]int64) [15]int64 {
137         return *x
138 }
139 `
140                 copy := filepath.Join(dir, "copy.go")
141                 if err := os.WriteFile(copy, []byte(copyCode), 0644); err != nil {
142                         t.Fatal(err)
143                 }
144                 outcopy := filepath.Join(dir, "copy.o")
145
146                 // On not-amd64, test the host architecture and os
147                 arches := []string{runtime.GOARCH}
148                 goos0 := runtime.GOOS
149                 if runtime.GOARCH == "amd64" { // Test many things with "linux" (wasm will get "js")
150                         arches = []string{"arm", "arm64", "386", "amd64", "mips", "mips64", "loong64", "ppc64le", "riscv64", "s390x", "wasm"}
151                         goos0 = "linux"
152                 }
153
154                 for _, arch := range arches {
155                         t.Run(arch, func(t *testing.T) {
156                                 goos := goos0
157                                 if arch == "wasm" {
158                                         goos = "js"
159                                 }
160                                 _, err := testCopy(t, dir, arch, goos, copy, outcopy)
161                                 if err != nil {
162                                         t.Error("-json=0,file://log/opt should have succeeded")
163                                 }
164                                 logged, err := os.ReadFile(filepath.Join(dir, "log", "opt", "x", "copy.json"))
165                                 if err != nil {
166                                         t.Error("-json=0,file://log/opt missing expected log file")
167                                 }
168                                 slogged := normalize(logged, string(uriIfy(dir)), string(uriIfy("tmpdir")))
169                                 t.Logf("%s", slogged)
170                                 want(t, slogged, `{"range":{"start":{"line":3,"character":2},"end":{"line":3,"character":2}},"severity":3,"code":"copy","source":"go compiler","message":"128 bytes"}`)
171                                 want(t, slogged, `{"range":{"start":{"line":9,"character":2},"end":{"line":9,"character":2}},"severity":3,"code":"copy","source":"go compiler","message":"128 bytes"}`)
172                                 wantN(t, slogged, `"code":"copy"`, 2)
173                         })
174                 }
175         })
176
177         // Some architectures don't fault on nil dereference, so nilchecks are eliminated differently.
178         // The N-way copy test also doesn't need to run N-ways N times.
179         if runtime.GOARCH != "amd64" {
180                 return
181         }
182
183         t.Run("Success", func(t *testing.T) {
184                 // This test is supposed to succeed
185
186                 // Note 'file://' is the I-Know-What-I-Am-Doing way of specifying a file, also to deal with corner cases for Windows.
187                 _, err := testLogOptDir(t, dir, "-json=0,file://log/opt", src, outfile)
188                 if err != nil {
189                         t.Error("-json=0,file://log/opt should have succeeded")
190                 }
191                 logged, err := os.ReadFile(filepath.Join(dir, "log", "opt", "x", "file.json"))
192                 if err != nil {
193                         t.Error("-json=0,file://log/opt missing expected log file")
194                 }
195                 // All this delicacy with uriIfy and filepath.Join is to get this test to work right on Windows.
196                 slogged := normalize(logged, string(uriIfy(dir)), string(uriIfy("tmpdir")))
197                 t.Logf("%s", slogged)
198                 // below shows proper nilcheck
199                 want(t, slogged, `{"range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}},"severity":3,"code":"nilcheck","source":"go compiler","message":"",`+
200                         `"relatedInformation":[{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":4,"character":11},"end":{"line":4,"character":11}}},"message":"inlineLoc"}]}`)
201                 want(t, slogged, `{"range":{"start":{"line":11,"character":6},"end":{"line":11,"character":6}},"severity":3,"code":"isInBounds","source":"go compiler","message":""}`)
202                 want(t, slogged, `{"range":{"start":{"line":7,"character":6},"end":{"line":7,"character":6}},"severity":3,"code":"canInlineFunction","source":"go compiler","message":"cost: 35"}`)
203                 // escape analysis explanation
204                 want(t, slogged, `{"range":{"start":{"line":7,"character":13},"end":{"line":7,"character":13}},"severity":3,"code":"leak","source":"go compiler","message":"parameter z leaks to ~r0 with derefs=0",`+
205                         `"relatedInformation":[`+
206                         `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}}},"message":"escflow:    flow: y = z:"},`+
207                         `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}}},"message":"escflow:      from y := z (assign-pair)"},`+
208                         `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}}},"message":"escflow:    flow: ~R0 = y:"},`+
209                         `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":4,"character":11},"end":{"line":4,"character":11}}},"message":"inlineLoc"},`+
210                         `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}}},"message":"escflow:      from y.b (dot of pointer)"},`+
211                         `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":4,"character":11},"end":{"line":4,"character":11}}},"message":"inlineLoc"},`+
212                         `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}}},"message":"escflow:      from \u0026y.b (address-of)"},`+
213                         `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":4,"character":9},"end":{"line":4,"character":9}}},"message":"inlineLoc"},`+
214                         `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}}},"message":"escflow:      from ~R0 = \u0026y.b (assign-pair)"},`+
215                         `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":3},"end":{"line":9,"character":3}}},"message":"escflow:    flow: ~r0 = ~R0:"},`+
216                         `{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":3},"end":{"line":9,"character":3}}},"message":"escflow:      from return ~R0 (return)"}]}`)
217         })
218 }
219
220 func testLogOpt(t *testing.T, flag, src, outfile string) (string, error) {
221         run := []string{testenv.GoToolPath(t), "tool", "compile", "-p=p", flag, "-o", outfile, src}
222         t.Log(run)
223         cmd := testenv.Command(t, run[0], run[1:]...)
224         out, err := cmd.CombinedOutput()
225         t.Logf("%s", out)
226         return string(out), err
227 }
228
229 func testLogOptDir(t *testing.T, dir, flag, src, outfile string) (string, error) {
230         // Notice the specified import path "x"
231         run := []string{testenv.GoToolPath(t), "tool", "compile", "-p=x", flag, "-o", outfile, src}
232         t.Log(run)
233         cmd := testenv.Command(t, run[0], run[1:]...)
234         cmd.Dir = dir
235         out, err := cmd.CombinedOutput()
236         t.Logf("%s", out)
237         return string(out), err
238 }
239
240 func testCopy(t *testing.T, dir, goarch, goos, src, outfile string) (string, error) {
241         // Notice the specified import path "x"
242         run := []string{testenv.GoToolPath(t), "tool", "compile", "-p=x", "-json=0,file://log/opt", "-o", outfile, src}
243         t.Log(run)
244         cmd := testenv.Command(t, run[0], run[1:]...)
245         cmd.Dir = dir
246         cmd.Env = append(os.Environ(), "GOARCH="+goarch, "GOOS="+goos)
247         out, err := cmd.CombinedOutput()
248         t.Logf("%s", out)
249         return string(out), err
250 }