mirror of https://github.com/golang/go.git
259 lines
10 KiB
Go
259 lines
10 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package logopt
|
|
|
|
import (
|
|
"internal/testenv"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const srcCode = `package x
|
|
type pair struct {a,b int}
|
|
func bar(y *pair) *int {
|
|
return &y.b
|
|
}
|
|
var a []int
|
|
func foo(w, z *pair) *int {
|
|
if *bar(w) > 0 {
|
|
return bar(z)
|
|
}
|
|
if a[1] > 0 {
|
|
a = a[:2]
|
|
}
|
|
return &a[0]
|
|
}
|
|
|
|
// address taking prevents closure inlining
|
|
func n() int {
|
|
foo := func() int { return 1 }
|
|
bar := &foo
|
|
x := (*bar)() + foo()
|
|
return x
|
|
}
|
|
`
|
|
|
|
func want(t *testing.T, out string, desired string) {
|
|
// On Windows, Unicode escapes in the JSON output end up "normalized" elsewhere to /u....,
|
|
// so "normalize" what we're looking for to match that.
|
|
s := strings.ReplaceAll(desired, string(os.PathSeparator), "/")
|
|
if !strings.Contains(out, s) {
|
|
t.Errorf("did not see phrase %s in \n%s", s, out)
|
|
}
|
|
}
|
|
|
|
func wantN(t *testing.T, out string, desired string, n int) {
|
|
if strings.Count(out, desired) != n {
|
|
t.Errorf("expected exactly %d occurrences of %s in \n%s", n, desired, out)
|
|
}
|
|
}
|
|
|
|
func TestPathStuff(t *testing.T) {
|
|
sep := string(filepath.Separator)
|
|
if path, whine := parseLogPath("file:///c:foo"); path != "c:foo" || whine != "" { // good path
|
|
t.Errorf("path='%s', whine='%s'", path, whine)
|
|
}
|
|
if path, whine := parseLogPath("file:///foo"); path != sep+"foo" || whine != "" { // good path
|
|
t.Errorf("path='%s', whine='%s'", path, whine)
|
|
}
|
|
if path, whine := parseLogPath("foo"); path != "" || whine == "" { // BAD path
|
|
t.Errorf("path='%s', whine='%s'", path, whine)
|
|
}
|
|
if sep == "\\" { // On WINDOWS ONLY
|
|
if path, whine := parseLogPath("C:/foo"); path != "C:\\foo" || whine != "" { // good path
|
|
t.Errorf("path='%s', whine='%s'", path, whine)
|
|
}
|
|
if path, whine := parseLogPath("c:foo"); path != "" || whine == "" { // BAD path
|
|
t.Errorf("path='%s', whine='%s'", path, whine)
|
|
}
|
|
if path, whine := parseLogPath("/foo"); path != "" || whine == "" { // BAD path
|
|
t.Errorf("path='%s', whine='%s'", path, whine)
|
|
}
|
|
} else { // ON UNIX ONLY
|
|
if path, whine := parseLogPath("/foo"); path != sep+"foo" || whine != "" { // good path
|
|
t.Errorf("path='%s', whine='%s'", path, whine)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLogOpt(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testenv.MustHaveGoBuild(t)
|
|
|
|
dir, err := ioutil.TempDir("", "TestLogOpt")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
dir = fixSlash(dir) // Normalize the directory name as much as possible, for Windows testing
|
|
src := filepath.Join(dir, "file.go")
|
|
if err := ioutil.WriteFile(src, []byte(srcCode), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
outfile := filepath.Join(dir, "file.o")
|
|
|
|
t.Run("JSON_fails", func(t *testing.T) {
|
|
// Test malformed flag
|
|
out, err := testLogOpt(t, "-json=foo", src, outfile)
|
|
if err == nil {
|
|
t.Error("-json=foo succeeded unexpectedly")
|
|
}
|
|
want(t, out, "option should be")
|
|
want(t, out, "number")
|
|
|
|
// Test a version number that is currently unsupported (and should remain unsupported for a while)
|
|
out, err = testLogOpt(t, "-json=9,foo", src, outfile)
|
|
if err == nil {
|
|
t.Error("-json=0,foo succeeded unexpectedly")
|
|
}
|
|
want(t, out, "version must be")
|
|
|
|
})
|
|
|
|
// replace d (dir) with t ("tmpdir") and convert path separators to '/'
|
|
normalize := func(out []byte, d, t string) string {
|
|
s := string(out)
|
|
s = strings.ReplaceAll(s, d, t)
|
|
s = strings.ReplaceAll(s, string(os.PathSeparator), "/")
|
|
return s
|
|
}
|
|
|
|
// Ensure that <128 byte copies are not reported and that 128-byte copies are.
|
|
// Check at both 1 and 8-byte alignments.
|
|
t.Run("Copy", func(t *testing.T) {
|
|
const copyCode = `package x
|
|
func s128a1(x *[128]int8) [128]int8 {
|
|
return *x
|
|
}
|
|
func s127a1(x *[127]int8) [127]int8 {
|
|
return *x
|
|
}
|
|
func s16a8(x *[16]int64) [16]int64 {
|
|
return *x
|
|
}
|
|
func s15a8(x *[15]int64) [15]int64 {
|
|
return *x
|
|
}
|
|
`
|
|
copy := filepath.Join(dir, "copy.go")
|
|
if err := ioutil.WriteFile(copy, []byte(copyCode), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
outcopy := filepath.Join(dir, "copy.o")
|
|
|
|
// On not-amd64, test the host architecture and os
|
|
arches := []string{runtime.GOARCH}
|
|
goos0 := runtime.GOOS
|
|
if runtime.GOARCH == "amd64" { // Test many things with "linux" (wasm will get "js")
|
|
arches = []string{"arm", "arm64", "386", "amd64", "mips", "mips64", "ppc64le", "riscv64", "s390x", "wasm"}
|
|
goos0 = "linux"
|
|
}
|
|
|
|
for _, arch := range arches {
|
|
t.Run(arch, func(t *testing.T) {
|
|
goos := goos0
|
|
if arch == "wasm" {
|
|
goos = "js"
|
|
}
|
|
_, err := testCopy(t, dir, arch, goos, copy, outcopy)
|
|
if err != nil {
|
|
t.Error("-json=0,file://log/opt should have succeeded")
|
|
}
|
|
logged, err := ioutil.ReadFile(filepath.Join(dir, "log", "opt", "x", "copy.json"))
|
|
if err != nil {
|
|
t.Error("-json=0,file://log/opt missing expected log file")
|
|
}
|
|
slogged := normalize(logged, string(uriIfy(dir)), string(uriIfy("tmpdir")))
|
|
t.Logf("%s", slogged)
|
|
want(t, slogged, `{"range":{"start":{"line":3,"character":2},"end":{"line":3,"character":2}},"severity":3,"code":"copy","source":"go compiler","message":"128 bytes"}`)
|
|
want(t, slogged, `{"range":{"start":{"line":9,"character":2},"end":{"line":9,"character":2}},"severity":3,"code":"copy","source":"go compiler","message":"128 bytes"}`)
|
|
wantN(t, slogged, `"code":"copy"`, 2)
|
|
})
|
|
}
|
|
})
|
|
|
|
// Some architectures don't fault on nil dereference, so nilchecks are eliminated differently.
|
|
// The N-way copy test also doesn't need to run N-ways N times.
|
|
if runtime.GOARCH != "amd64" {
|
|
return
|
|
}
|
|
|
|
t.Run("Success", func(t *testing.T) {
|
|
// This test is supposed to succeed
|
|
|
|
// Note 'file://' is the I-Know-What-I-Am-Doing way of specifying a file, also to deal with corner cases for Windows.
|
|
_, err := testLogOptDir(t, dir, "-json=0,file://log/opt", src, outfile)
|
|
if err != nil {
|
|
t.Error("-json=0,file://log/opt should have succeeded")
|
|
}
|
|
logged, err := ioutil.ReadFile(filepath.Join(dir, "log", "opt", "x", "file.json"))
|
|
if err != nil {
|
|
t.Error("-json=0,file://log/opt missing expected log file")
|
|
}
|
|
// All this delicacy with uriIfy and filepath.Join is to get this test to work right on Windows.
|
|
slogged := normalize(logged, string(uriIfy(dir)), string(uriIfy("tmpdir")))
|
|
t.Logf("%s", slogged)
|
|
// below shows proper nilcheck
|
|
want(t, slogged, `{"range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}},"severity":3,"code":"nilcheck","source":"go compiler","message":"",`+
|
|
`"relatedInformation":[{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":4,"character":11},"end":{"line":4,"character":11}}},"message":"inlineLoc"}]}`)
|
|
want(t, slogged, `{"range":{"start":{"line":11,"character":6},"end":{"line":11,"character":6}},"severity":3,"code":"isInBounds","source":"go compiler","message":""}`)
|
|
want(t, slogged, `{"range":{"start":{"line":7,"character":6},"end":{"line":7,"character":6}},"severity":3,"code":"canInlineFunction","source":"go compiler","message":"cost: 35"}`)
|
|
// escape analysis explanation
|
|
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",`+
|
|
`"relatedInformation":[`+
|
|
`{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}}},"message":"escflow: flow: y = z:"},`+
|
|
`{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}}},"message":"escflow: from y := z (assign-pair)"},`+
|
|
`{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}}},"message":"escflow: flow: ~R0 = y:"},`+
|
|
`{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":4,"character":11},"end":{"line":4,"character":11}}},"message":"inlineLoc"},`+
|
|
`{"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)"},`+
|
|
`{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":4,"character":11},"end":{"line":4,"character":11}}},"message":"inlineLoc"},`+
|
|
`{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}}},"message":"escflow: from \u0026y.b (address-of)"},`+
|
|
`{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":4,"character":9},"end":{"line":4,"character":9}}},"message":"inlineLoc"},`+
|
|
`{"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)"},`+
|
|
`{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":3},"end":{"line":9,"character":3}}},"message":"escflow: flow: ~r0 = ~R0:"},`+
|
|
`{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":3},"end":{"line":9,"character":3}}},"message":"escflow: from return (*int)(~R0) (return)"}]}`)
|
|
})
|
|
}
|
|
|
|
func testLogOpt(t *testing.T, flag, src, outfile string) (string, error) {
|
|
run := []string{testenv.GoToolPath(t), "tool", "compile", flag, "-o", outfile, src}
|
|
t.Log(run)
|
|
cmd := exec.Command(run[0], run[1:]...)
|
|
out, err := cmd.CombinedOutput()
|
|
t.Logf("%s", out)
|
|
return string(out), err
|
|
}
|
|
|
|
func testLogOptDir(t *testing.T, dir, flag, src, outfile string) (string, error) {
|
|
// Notice the specified import path "x"
|
|
run := []string{testenv.GoToolPath(t), "tool", "compile", "-p", "x", flag, "-o", outfile, src}
|
|
t.Log(run)
|
|
cmd := exec.Command(run[0], run[1:]...)
|
|
cmd.Dir = dir
|
|
out, err := cmd.CombinedOutput()
|
|
t.Logf("%s", out)
|
|
return string(out), err
|
|
}
|
|
|
|
func testCopy(t *testing.T, dir, goarch, goos, src, outfile string) (string, error) {
|
|
// Notice the specified import path "x"
|
|
run := []string{testenv.GoToolPath(t), "tool", "compile", "-p", "x", "-json=0,file://log/opt", "-o", outfile, src}
|
|
t.Log(run)
|
|
cmd := exec.Command(run[0], run[1:]...)
|
|
cmd.Dir = dir
|
|
cmd.Env = append(os.Environ(), "GOARCH="+goarch, "GOOS="+goos)
|
|
out, err := cmd.CombinedOutput()
|
|
t.Logf("%s", out)
|
|
return string(out), err
|
|
}
|