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

View as plain text