mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
In golang.org/cl/266199, I reused the existing code in inlining that recognizes anonymous variables. However, it turns out that code mistakenly recognizes anonymous return parameters as named when inlining a function from the same package. The issue is funcargs (which is only used for functions parsed from source) synthesizes ~r names for anonymous return parameters, but funcargs2 (which is only used for functions imported from export data) does not. This CL fixes the behavior so that anonymous return parameters are handled identically whether a function is inlined within the same package or across packages. It also adds a proper cross-package test case demonstrating #33160 is fixed in both cases. Change-Id: Iaa39a23f5666979a1f5ca6d09fc8c398e55b784c Reviewed-on: https://go-review.googlesource.com/c/go/+/266719 Run-TryBot: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com> Reviewed-by: David Chase <drchase@google.com> Trust: Matthew Dempsky <mdempsky@google.com>
258 lines
10 KiB
Go
258 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 occurences 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 ~r2 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 = \u003cN\u003e (assign-pair)"},`+
|
|
`{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":9,"character":3},"end":{"line":9,"character":3}}},"message":"escflow: flow: ~r2 = ~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
|
|
}
|