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 5package logopt 6 7import ( 8 "internal/testenv" 9 "os" 10 "path/filepath" 11 "runtime" 12 "strings" 13 "testing" 14) 15 16const srcCode = `package x 17type pair struct {a,b int} 18func bar(y *pair) *int { 19 return &y.b 20} 21var a []int 22func 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 33func n() int { 34 foo := func() int { return 1 } 35 bar := &foo 36 x := (*bar)() + foo() 37 return x 38} 39` 40 41func 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 50func 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 56func 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 84func 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 127func s128a1(x *[128]int8) [128]int8 { 128 return *x 129} 130func s127a1(x *[127]int8) [127]int8 { 131 return *x 132} 133func s16a8(x *[16]int64) [16]int64 { 134 return *x 135} 136func 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 220func 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 229func 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 240func 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} 251