github.com/stingnevermore/go@v0.0.0-20180120041312-3810f5bfed72/misc/cgo/testshared/shared_test.go (about)

     1  // Copyright 2015 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 shared_test
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"debug/elf"
    11  	"encoding/binary"
    12  	"errors"
    13  	"flag"
    14  	"fmt"
    15  	"go/build"
    16  	"io"
    17  	"io/ioutil"
    18  	"log"
    19  	"math/rand"
    20  	"os"
    21  	"os/exec"
    22  	"path/filepath"
    23  	"regexp"
    24  	"runtime"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  )
    29  
    30  var gopathInstallDir, gorootInstallDir, suffix string
    31  
    32  // This is the smallest set of packages we can link into a shared
    33  // library (runtime/cgo is built implicitly).
    34  var minpkgs = []string{"runtime", "sync/atomic"}
    35  var soname = "libruntime,sync-atomic.so"
    36  
    37  // run runs a command and calls t.Errorf if it fails.
    38  func run(t *testing.T, msg string, args ...string) {
    39  	c := exec.Command(args[0], args[1:]...)
    40  	if output, err := c.CombinedOutput(); err != nil {
    41  		t.Errorf("executing %s (%s) failed %s:\n%s", strings.Join(args, " "), msg, err, output)
    42  	}
    43  }
    44  
    45  // goCmd invokes the go tool with the installsuffix set up by TestMain. It calls
    46  // t.Fatalf if the command fails.
    47  func goCmd(t *testing.T, args ...string) {
    48  	newargs := []string{args[0], "-installsuffix=" + suffix}
    49  	if testing.Verbose() {
    50  		newargs = append(newargs, "-x")
    51  	}
    52  	newargs = append(newargs, args[1:]...)
    53  	c := exec.Command("go", newargs...)
    54  	var output []byte
    55  	var err error
    56  	if testing.Verbose() {
    57  		fmt.Printf("+ go %s\n", strings.Join(newargs, " "))
    58  		c.Stdout = os.Stdout
    59  		c.Stderr = os.Stderr
    60  		err = c.Run()
    61  		output = []byte("(output above)")
    62  	} else {
    63  		output, err = c.CombinedOutput()
    64  	}
    65  	if err != nil {
    66  		if t != nil {
    67  			t.Fatalf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, output)
    68  		} else {
    69  			log.Fatalf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, output)
    70  		}
    71  	}
    72  }
    73  
    74  // TestMain calls testMain so that the latter can use defer (TestMain exits with os.Exit).
    75  func testMain(m *testing.M) (int, error) {
    76  	// Because go install -buildmode=shared $standard_library_package always
    77  	// installs into $GOROOT, here are some gymnastics to come up with a unique
    78  	// installsuffix to use in this test that we can clean up afterwards.
    79  	myContext := build.Default
    80  	runtimeP, err := myContext.Import("runtime", ".", build.ImportComment)
    81  	if err != nil {
    82  		return 0, fmt.Errorf("import failed: %v", err)
    83  	}
    84  	for i := 0; i < 10000; i++ {
    85  		try := fmt.Sprintf("%s_%d_dynlink", runtimeP.PkgTargetRoot, rand.Int63())
    86  		err = os.Mkdir(try, 0700)
    87  		if os.IsExist(err) {
    88  			continue
    89  		}
    90  		if err == nil {
    91  			gorootInstallDir = try
    92  		}
    93  		break
    94  	}
    95  	if err != nil {
    96  		return 0, fmt.Errorf("can't create temporary directory: %v", err)
    97  	}
    98  	if gorootInstallDir == "" {
    99  		return 0, errors.New("could not create temporary directory after 10000 tries")
   100  	}
   101  	if testing.Verbose() {
   102  		fmt.Printf("+ mkdir -p %s\n", gorootInstallDir)
   103  	}
   104  	defer os.RemoveAll(gorootInstallDir)
   105  
   106  	// Some tests need to edit the source in GOPATH, so copy this directory to a
   107  	// temporary directory and chdir to that.
   108  	scratchDir, err := ioutil.TempDir("", "testshared")
   109  	if err != nil {
   110  		return 0, fmt.Errorf("TempDir failed: %v", err)
   111  	}
   112  	if testing.Verbose() {
   113  		fmt.Printf("+ mkdir -p %s\n", scratchDir)
   114  	}
   115  	defer os.RemoveAll(scratchDir)
   116  	err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
   117  		scratchPath := filepath.Join(scratchDir, path)
   118  		if info.IsDir() {
   119  			if path == "." {
   120  				return nil
   121  			}
   122  			if testing.Verbose() {
   123  				fmt.Printf("+ mkdir -p %s\n", scratchPath)
   124  			}
   125  			return os.Mkdir(scratchPath, info.Mode())
   126  		} else {
   127  			fromBytes, err := ioutil.ReadFile(path)
   128  			if err != nil {
   129  				return err
   130  			}
   131  			if testing.Verbose() {
   132  				fmt.Printf("+ cp %s %s\n", path, scratchPath)
   133  			}
   134  			return ioutil.WriteFile(scratchPath, fromBytes, info.Mode())
   135  		}
   136  	})
   137  	if err != nil {
   138  		return 0, fmt.Errorf("walk failed: %v", err)
   139  	}
   140  	os.Setenv("GOPATH", scratchDir)
   141  	if testing.Verbose() {
   142  		fmt.Printf("+ export GOPATH=%s\n", scratchDir)
   143  	}
   144  	myContext.GOPATH = scratchDir
   145  	if testing.Verbose() {
   146  		fmt.Printf("+ cd %s\n", scratchDir)
   147  	}
   148  	os.Chdir(scratchDir)
   149  
   150  	// All tests depend on runtime being built into a shared library. Because
   151  	// that takes a few seconds, do it here and have all tests use the version
   152  	// built here.
   153  	suffix = strings.Split(filepath.Base(gorootInstallDir), "_")[2]
   154  	goCmd(nil, append([]string{"install", "-buildmode=shared"}, minpkgs...)...)
   155  
   156  	myContext.InstallSuffix = suffix + "_dynlink"
   157  	depP, err := myContext.Import("depBase", ".", build.ImportComment)
   158  	if err != nil {
   159  		return 0, fmt.Errorf("import failed: %v", err)
   160  	}
   161  	gopathInstallDir = depP.PkgTargetRoot
   162  	return m.Run(), nil
   163  }
   164  
   165  func TestMain(m *testing.M) {
   166  	flag.Parse()
   167  
   168  	// Some of the tests install binaries into a custom GOPATH.
   169  	// That won't work if GOBIN is set.
   170  	os.Unsetenv("GOBIN")
   171  
   172  	exitCode, err := testMain(m)
   173  	if err != nil {
   174  		log.Fatal(err)
   175  	}
   176  	os.Exit(exitCode)
   177  }
   178  
   179  // The shared library was built at the expected location.
   180  func TestSOBuilt(t *testing.T) {
   181  	_, err := os.Stat(filepath.Join(gorootInstallDir, soname))
   182  	if err != nil {
   183  		t.Error(err)
   184  	}
   185  }
   186  
   187  func hasDynTag(f *elf.File, tag elf.DynTag) bool {
   188  	ds := f.SectionByType(elf.SHT_DYNAMIC)
   189  	if ds == nil {
   190  		return false
   191  	}
   192  	d, err := ds.Data()
   193  	if err != nil {
   194  		return false
   195  	}
   196  	for len(d) > 0 {
   197  		var t elf.DynTag
   198  		switch f.Class {
   199  		case elf.ELFCLASS32:
   200  			t = elf.DynTag(f.ByteOrder.Uint32(d[0:4]))
   201  			d = d[8:]
   202  		case elf.ELFCLASS64:
   203  			t = elf.DynTag(f.ByteOrder.Uint64(d[0:8]))
   204  			d = d[16:]
   205  		}
   206  		if t == tag {
   207  			return true
   208  		}
   209  	}
   210  	return false
   211  }
   212  
   213  // The shared library does not have relocations against the text segment.
   214  func TestNoTextrel(t *testing.T) {
   215  	sopath := filepath.Join(gorootInstallDir, soname)
   216  	f, err := elf.Open(sopath)
   217  	if err != nil {
   218  		t.Fatal("elf.Open failed: ", err)
   219  	}
   220  	defer f.Close()
   221  	if hasDynTag(f, elf.DT_TEXTREL) {
   222  		t.Errorf("%s has DT_TEXTREL set", soname)
   223  	}
   224  }
   225  
   226  // The shared library does not contain symbols called ".dup"
   227  func TestNoDupSymbols(t *testing.T) {
   228  	sopath := filepath.Join(gorootInstallDir, soname)
   229  	f, err := elf.Open(sopath)
   230  	if err != nil {
   231  		t.Fatal("elf.Open failed: ", err)
   232  	}
   233  	defer f.Close()
   234  	syms, err := f.Symbols()
   235  	if err != nil {
   236  		t.Errorf("error reading symbols %v", err)
   237  		return
   238  	}
   239  	for _, s := range syms {
   240  		if s.Name == ".dup" {
   241  			t.Fatalf("%s contains symbol called .dup", sopath)
   242  		}
   243  	}
   244  }
   245  
   246  // The install command should have created a "shlibname" file for the
   247  // listed packages (and runtime/cgo, and math on arm) indicating the
   248  // name of the shared library containing it.
   249  func TestShlibnameFiles(t *testing.T) {
   250  	pkgs := append([]string{}, minpkgs...)
   251  	pkgs = append(pkgs, "runtime/cgo")
   252  	if runtime.GOARCH == "arm" {
   253  		pkgs = append(pkgs, "math")
   254  	}
   255  	for _, pkg := range pkgs {
   256  		shlibnamefile := filepath.Join(gorootInstallDir, pkg+".shlibname")
   257  		contentsb, err := ioutil.ReadFile(shlibnamefile)
   258  		if err != nil {
   259  			t.Errorf("error reading shlibnamefile for %s: %v", pkg, err)
   260  			continue
   261  		}
   262  		contents := strings.TrimSpace(string(contentsb))
   263  		if contents != soname {
   264  			t.Errorf("shlibnamefile for %s has wrong contents: %q", pkg, contents)
   265  		}
   266  	}
   267  }
   268  
   269  // Is a given offset into the file contained in a loaded segment?
   270  func isOffsetLoaded(f *elf.File, offset uint64) bool {
   271  	for _, prog := range f.Progs {
   272  		if prog.Type == elf.PT_LOAD {
   273  			if prog.Off <= offset && offset < prog.Off+prog.Filesz {
   274  				return true
   275  			}
   276  		}
   277  	}
   278  	return false
   279  }
   280  
   281  func rnd(v int32, r int32) int32 {
   282  	if r <= 0 {
   283  		return v
   284  	}
   285  	v += r - 1
   286  	c := v % r
   287  	if c < 0 {
   288  		c += r
   289  	}
   290  	v -= c
   291  	return v
   292  }
   293  
   294  func readwithpad(r io.Reader, sz int32) ([]byte, error) {
   295  	data := make([]byte, rnd(sz, 4))
   296  	_, err := io.ReadFull(r, data)
   297  	if err != nil {
   298  		return nil, err
   299  	}
   300  	data = data[:sz]
   301  	return data, nil
   302  }
   303  
   304  type note struct {
   305  	name    string
   306  	tag     int32
   307  	desc    string
   308  	section *elf.Section
   309  }
   310  
   311  // Read all notes from f. As ELF section names are not supposed to be special, one
   312  // looks for a particular note by scanning all SHT_NOTE sections looking for a note
   313  // with a particular "name" and "tag".
   314  func readNotes(f *elf.File) ([]*note, error) {
   315  	var notes []*note
   316  	for _, sect := range f.Sections {
   317  		if sect.Type != elf.SHT_NOTE {
   318  			continue
   319  		}
   320  		r := sect.Open()
   321  		for {
   322  			var namesize, descsize, tag int32
   323  			err := binary.Read(r, f.ByteOrder, &namesize)
   324  			if err != nil {
   325  				if err == io.EOF {
   326  					break
   327  				}
   328  				return nil, fmt.Errorf("read namesize failed: %v", err)
   329  			}
   330  			err = binary.Read(r, f.ByteOrder, &descsize)
   331  			if err != nil {
   332  				return nil, fmt.Errorf("read descsize failed: %v", err)
   333  			}
   334  			err = binary.Read(r, f.ByteOrder, &tag)
   335  			if err != nil {
   336  				return nil, fmt.Errorf("read type failed: %v", err)
   337  			}
   338  			name, err := readwithpad(r, namesize)
   339  			if err != nil {
   340  				return nil, fmt.Errorf("read name failed: %v", err)
   341  			}
   342  			desc, err := readwithpad(r, descsize)
   343  			if err != nil {
   344  				return nil, fmt.Errorf("read desc failed: %v", err)
   345  			}
   346  			notes = append(notes, &note{name: string(name), tag: tag, desc: string(desc), section: sect})
   347  		}
   348  	}
   349  	return notes, nil
   350  }
   351  
   352  func dynStrings(t *testing.T, path string, flag elf.DynTag) []string {
   353  	f, err := elf.Open(path)
   354  	if err != nil {
   355  		t.Fatalf("elf.Open(%q) failed: %v", path, err)
   356  	}
   357  	defer f.Close()
   358  	dynstrings, err := f.DynString(flag)
   359  	if err != nil {
   360  		t.Fatalf("DynString(%s) failed on %s: %v", flag, path, err)
   361  	}
   362  	return dynstrings
   363  }
   364  
   365  func AssertIsLinkedToRegexp(t *testing.T, path string, re *regexp.Regexp) {
   366  	for _, dynstring := range dynStrings(t, path, elf.DT_NEEDED) {
   367  		if re.MatchString(dynstring) {
   368  			return
   369  		}
   370  	}
   371  	t.Errorf("%s is not linked to anything matching %v", path, re)
   372  }
   373  
   374  func AssertIsLinkedTo(t *testing.T, path, lib string) {
   375  	AssertIsLinkedToRegexp(t, path, regexp.MustCompile(regexp.QuoteMeta(lib)))
   376  }
   377  
   378  func AssertHasRPath(t *testing.T, path, dir string) {
   379  	for _, tag := range []elf.DynTag{elf.DT_RPATH, elf.DT_RUNPATH} {
   380  		for _, dynstring := range dynStrings(t, path, tag) {
   381  			for _, rpath := range strings.Split(dynstring, ":") {
   382  				if filepath.Clean(rpath) == filepath.Clean(dir) {
   383  					return
   384  				}
   385  			}
   386  		}
   387  	}
   388  	t.Errorf("%s does not have rpath %s", path, dir)
   389  }
   390  
   391  // Build a trivial program that links against the shared runtime and check it runs.
   392  func TestTrivialExecutable(t *testing.T) {
   393  	goCmd(t, "install", "-linkshared", "trivial")
   394  	run(t, "trivial executable", "./bin/trivial")
   395  	AssertIsLinkedTo(t, "./bin/trivial", soname)
   396  	AssertHasRPath(t, "./bin/trivial", gorootInstallDir)
   397  }
   398  
   399  // Build a trivial program in PIE mode that links against the shared runtime and check it runs.
   400  func TestTrivialExecutablePIE(t *testing.T) {
   401  	goCmd(t, "build", "-buildmode=pie", "-o", "trivial.pie", "-linkshared", "trivial")
   402  	run(t, "trivial executable", "./trivial.pie")
   403  	AssertIsLinkedTo(t, "./trivial.pie", soname)
   404  	AssertHasRPath(t, "./trivial.pie", gorootInstallDir)
   405  }
   406  
   407  // Build a division test program and check it runs.
   408  func TestDivisionExecutable(t *testing.T) {
   409  	goCmd(t, "install", "-linkshared", "division")
   410  	run(t, "division executable", "./bin/division")
   411  }
   412  
   413  // Build an executable that uses cgo linked against the shared runtime and check it
   414  // runs.
   415  func TestCgoExecutable(t *testing.T) {
   416  	goCmd(t, "install", "-linkshared", "execgo")
   417  	run(t, "cgo executable", "./bin/execgo")
   418  }
   419  
   420  func checkPIE(t *testing.T, name string) {
   421  	f, err := elf.Open(name)
   422  	if err != nil {
   423  		t.Fatal("elf.Open failed: ", err)
   424  	}
   425  	defer f.Close()
   426  	if f.Type != elf.ET_DYN {
   427  		t.Errorf("%s has type %v, want ET_DYN", name, f.Type)
   428  	}
   429  	if hasDynTag(f, elf.DT_TEXTREL) {
   430  		t.Errorf("%s has DT_TEXTREL set", name)
   431  	}
   432  }
   433  
   434  func TestTrivialPIE(t *testing.T) {
   435  	name := "trivial_pie"
   436  	goCmd(t, "build", "-buildmode=pie", "-o="+name, "trivial")
   437  	defer os.Remove(name)
   438  	run(t, name, "./"+name)
   439  	checkPIE(t, name)
   440  }
   441  
   442  func TestCgoPIE(t *testing.T) {
   443  	name := "cgo_pie"
   444  	goCmd(t, "build", "-buildmode=pie", "-o="+name, "execgo")
   445  	defer os.Remove(name)
   446  	run(t, name, "./"+name)
   447  	checkPIE(t, name)
   448  }
   449  
   450  // Build a GOPATH package into a shared library that links against the goroot runtime
   451  // and an executable that links against both.
   452  func TestGopathShlib(t *testing.T) {
   453  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
   454  	AssertIsLinkedTo(t, filepath.Join(gopathInstallDir, "libdepBase.so"), soname)
   455  	goCmd(t, "install", "-linkshared", "exe")
   456  	AssertIsLinkedTo(t, "./bin/exe", soname)
   457  	AssertIsLinkedTo(t, "./bin/exe", "libdepBase.so")
   458  	AssertHasRPath(t, "./bin/exe", gorootInstallDir)
   459  	AssertHasRPath(t, "./bin/exe", gopathInstallDir)
   460  	// And check it runs.
   461  	run(t, "executable linked to GOPATH library", "./bin/exe")
   462  }
   463  
   464  // The shared library contains a note listing the packages it contains in a section
   465  // that is not mapped into memory.
   466  func testPkgListNote(t *testing.T, f *elf.File, note *note) {
   467  	if note.section.Flags != 0 {
   468  		t.Errorf("package list section has flags %v, want 0", note.section.Flags)
   469  	}
   470  	if isOffsetLoaded(f, note.section.Offset) {
   471  		t.Errorf("package list section contained in PT_LOAD segment")
   472  	}
   473  	if note.desc != "depBase\n" {
   474  		t.Errorf("incorrect package list %q, want %q", note.desc, "depBase\n")
   475  	}
   476  }
   477  
   478  // The shared library contains a note containing the ABI hash that is mapped into
   479  // memory and there is a local symbol called go.link.abihashbytes that points 16
   480  // bytes into it.
   481  func testABIHashNote(t *testing.T, f *elf.File, note *note) {
   482  	if note.section.Flags != elf.SHF_ALLOC {
   483  		t.Errorf("abi hash section has flags %v, want SHF_ALLOC", note.section.Flags)
   484  	}
   485  	if !isOffsetLoaded(f, note.section.Offset) {
   486  		t.Errorf("abihash section not contained in PT_LOAD segment")
   487  	}
   488  	var hashbytes elf.Symbol
   489  	symbols, err := f.Symbols()
   490  	if err != nil {
   491  		t.Errorf("error reading symbols %v", err)
   492  		return
   493  	}
   494  	for _, sym := range symbols {
   495  		if sym.Name == "go.link.abihashbytes" {
   496  			hashbytes = sym
   497  		}
   498  	}
   499  	if hashbytes.Name == "" {
   500  		t.Errorf("no symbol called go.link.abihashbytes")
   501  		return
   502  	}
   503  	if elf.ST_BIND(hashbytes.Info) != elf.STB_LOCAL {
   504  		t.Errorf("%s has incorrect binding %v, want STB_LOCAL", hashbytes.Name, elf.ST_BIND(hashbytes.Info))
   505  	}
   506  	if f.Sections[hashbytes.Section] != note.section {
   507  		t.Errorf("%s has incorrect section %v, want %s", hashbytes.Name, f.Sections[hashbytes.Section].Name, note.section.Name)
   508  	}
   509  	if hashbytes.Value-note.section.Addr != 16 {
   510  		t.Errorf("%s has incorrect offset into section %d, want 16", hashbytes.Name, hashbytes.Value-note.section.Addr)
   511  	}
   512  }
   513  
   514  // A Go shared library contains a note indicating which other Go shared libraries it
   515  // was linked against in an unmapped section.
   516  func testDepsNote(t *testing.T, f *elf.File, note *note) {
   517  	if note.section.Flags != 0 {
   518  		t.Errorf("package list section has flags %v, want 0", note.section.Flags)
   519  	}
   520  	if isOffsetLoaded(f, note.section.Offset) {
   521  		t.Errorf("package list section contained in PT_LOAD segment")
   522  	}
   523  	// libdepBase.so just links against the lib containing the runtime.
   524  	if note.desc != soname {
   525  		t.Errorf("incorrect dependency list %q, want %q", note.desc, soname)
   526  	}
   527  }
   528  
   529  // The shared library contains notes with defined contents; see above.
   530  func TestNotes(t *testing.T) {
   531  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
   532  	f, err := elf.Open(filepath.Join(gopathInstallDir, "libdepBase.so"))
   533  	if err != nil {
   534  		t.Fatal(err)
   535  	}
   536  	defer f.Close()
   537  	notes, err := readNotes(f)
   538  	if err != nil {
   539  		t.Fatal(err)
   540  	}
   541  	pkgListNoteFound := false
   542  	abiHashNoteFound := false
   543  	depsNoteFound := false
   544  	for _, note := range notes {
   545  		if note.name != "Go\x00\x00" {
   546  			continue
   547  		}
   548  		switch note.tag {
   549  		case 1: // ELF_NOTE_GOPKGLIST_TAG
   550  			if pkgListNoteFound {
   551  				t.Error("multiple package list notes")
   552  			}
   553  			testPkgListNote(t, f, note)
   554  			pkgListNoteFound = true
   555  		case 2: // ELF_NOTE_GOABIHASH_TAG
   556  			if abiHashNoteFound {
   557  				t.Error("multiple abi hash notes")
   558  			}
   559  			testABIHashNote(t, f, note)
   560  			abiHashNoteFound = true
   561  		case 3: // ELF_NOTE_GODEPS_TAG
   562  			if depsNoteFound {
   563  				t.Error("multiple depedency list notes")
   564  			}
   565  			testDepsNote(t, f, note)
   566  			depsNoteFound = true
   567  		}
   568  	}
   569  	if !pkgListNoteFound {
   570  		t.Error("package list note not found")
   571  	}
   572  	if !abiHashNoteFound {
   573  		t.Error("abi hash note not found")
   574  	}
   575  	if !depsNoteFound {
   576  		t.Error("deps note not found")
   577  	}
   578  }
   579  
   580  // Build a GOPATH package (depBase) into a shared library that links against the goroot
   581  // runtime, another package (dep2) that links against the first, and and an
   582  // executable that links against dep2.
   583  func TestTwoGopathShlibs(t *testing.T) {
   584  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
   585  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep2")
   586  	goCmd(t, "install", "-linkshared", "exe2")
   587  	run(t, "executable linked to GOPATH library", "./bin/exe2")
   588  }
   589  
   590  func TestThreeGopathShlibs(t *testing.T) {
   591  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
   592  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep2")
   593  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep3")
   594  	goCmd(t, "install", "-linkshared", "exe3")
   595  	run(t, "executable linked to GOPATH library", "./bin/exe3")
   596  }
   597  
   598  // If gccgo is not available or not new enough call t.Skip. Otherwise,
   599  // return a build.Context that is set up for gccgo.
   600  func prepGccgo(t *testing.T) build.Context {
   601  	gccgoName := os.Getenv("GCCGO")
   602  	if gccgoName == "" {
   603  		gccgoName = "gccgo"
   604  	}
   605  	gccgoPath, err := exec.LookPath(gccgoName)
   606  	if err != nil {
   607  		t.Skip("gccgo not found")
   608  	}
   609  	cmd := exec.Command(gccgoPath, "-dumpversion")
   610  	output, err := cmd.CombinedOutput()
   611  	if err != nil {
   612  		t.Fatalf("%s -dumpversion failed: %v\n%s", gccgoPath, err, output)
   613  	}
   614  	if string(output) < "5" {
   615  		t.Skipf("gccgo too old (%s)", strings.TrimSpace(string(output)))
   616  	}
   617  	gccgoContext := build.Default
   618  	gccgoContext.InstallSuffix = suffix + "_fPIC"
   619  	gccgoContext.Compiler = "gccgo"
   620  	gccgoContext.GOPATH = os.Getenv("GOPATH")
   621  	return gccgoContext
   622  }
   623  
   624  // Build a GOPATH package into a shared library with gccgo and an executable that
   625  // links against it.
   626  func TestGoPathShlibGccgo(t *testing.T) {
   627  	gccgoContext := prepGccgo(t)
   628  
   629  	libgoRE := regexp.MustCompile("libgo.so.[0-9]+")
   630  
   631  	depP, err := gccgoContext.Import("depBase", ".", build.ImportComment)
   632  	if err != nil {
   633  		t.Fatalf("import failed: %v", err)
   634  	}
   635  	gccgoInstallDir := filepath.Join(depP.PkgTargetRoot, "shlibs")
   636  	goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "depBase")
   637  	AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdepBase.so"), libgoRE)
   638  	goCmd(t, "install", "-compiler=gccgo", "-linkshared", "exe")
   639  	AssertIsLinkedToRegexp(t, "./bin/exe", libgoRE)
   640  	AssertIsLinkedTo(t, "./bin/exe", "libdepBase.so")
   641  	AssertHasRPath(t, "./bin/exe", gccgoInstallDir)
   642  	// And check it runs.
   643  	run(t, "gccgo-built", "./bin/exe")
   644  }
   645  
   646  // The gccgo version of TestTwoGopathShlibs: build a GOPATH package into a shared
   647  // library with gccgo, another GOPATH package that depends on the first and an
   648  // executable that links the second library.
   649  func TestTwoGopathShlibsGccgo(t *testing.T) {
   650  	gccgoContext := prepGccgo(t)
   651  
   652  	libgoRE := regexp.MustCompile("libgo.so.[0-9]+")
   653  
   654  	depP, err := gccgoContext.Import("depBase", ".", build.ImportComment)
   655  	if err != nil {
   656  		t.Fatalf("import failed: %v", err)
   657  	}
   658  	gccgoInstallDir := filepath.Join(depP.PkgTargetRoot, "shlibs")
   659  	goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "depBase")
   660  	goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "dep2")
   661  	goCmd(t, "install", "-compiler=gccgo", "-linkshared", "exe2")
   662  
   663  	AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdepBase.so"), libgoRE)
   664  	AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdep2.so"), libgoRE)
   665  	AssertIsLinkedTo(t, filepath.Join(gccgoInstallDir, "libdep2.so"), "libdepBase.so")
   666  	AssertIsLinkedToRegexp(t, "./bin/exe2", libgoRE)
   667  	AssertIsLinkedTo(t, "./bin/exe2", "libdep2")
   668  	AssertIsLinkedTo(t, "./bin/exe2", "libdepBase.so")
   669  
   670  	// And check it runs.
   671  	run(t, "gccgo-built", "./bin/exe2")
   672  }
   673  
   674  // Testing rebuilding of shared libraries when they are stale is a bit more
   675  // complicated that it seems like it should be. First, we make everything "old": but
   676  // only a few seconds old, or it might be older than gc (or the runtime source) and
   677  // everything will get rebuilt. Then define a timestamp slightly newer than this
   678  // time, which is what we set the mtime to of a file to cause it to be seen as new,
   679  // and finally another slightly even newer one that we can compare files against to
   680  // see if they have been rebuilt.
   681  var oldTime = time.Now().Add(-9 * time.Second)
   682  var nearlyNew = time.Now().Add(-6 * time.Second)
   683  var stampTime = time.Now().Add(-3 * time.Second)
   684  
   685  // resetFileStamps makes "everything" (bin, src, pkg from GOPATH and the
   686  // test-specific parts of GOROOT) appear old.
   687  func resetFileStamps() {
   688  	chtime := func(path string, info os.FileInfo, err error) error {
   689  		return os.Chtimes(path, oldTime, oldTime)
   690  	}
   691  	reset := func(path string) {
   692  		if err := filepath.Walk(path, chtime); err != nil {
   693  			log.Fatalf("resetFileStamps failed: %v", err)
   694  		}
   695  
   696  	}
   697  	reset("bin")
   698  	reset("pkg")
   699  	reset("src")
   700  	reset(gorootInstallDir)
   701  }
   702  
   703  // touch changes path and returns a function that changes it back.
   704  // It also sets the time of the file, so that we can see if it is rewritten.
   705  func touch(t *testing.T, path string) (cleanup func()) {
   706  	data, err := ioutil.ReadFile(path)
   707  	if err != nil {
   708  		t.Fatal(err)
   709  	}
   710  	old := make([]byte, len(data))
   711  	copy(old, data)
   712  	if bytes.HasPrefix(data, []byte("!<arch>\n")) {
   713  		// Change last digit of build ID.
   714  		// (Content ID in the new content-based build IDs.)
   715  		const marker = `build id "`
   716  		i := bytes.Index(data, []byte(marker))
   717  		if i < 0 {
   718  			t.Fatal("cannot find build id in archive")
   719  		}
   720  		j := bytes.IndexByte(data[i+len(marker):], '"')
   721  		if j < 0 {
   722  			t.Fatal("cannot find build id in archive")
   723  		}
   724  		i += len(marker) + j - 1
   725  		if data[i] == 'a' {
   726  			data[i] = 'b'
   727  		} else {
   728  			data[i] = 'a'
   729  		}
   730  	} else {
   731  		// assume it's a text file
   732  		data = append(data, '\n')
   733  	}
   734  	if err := ioutil.WriteFile(path, data, 0666); err != nil {
   735  		t.Fatal(err)
   736  	}
   737  	if err := os.Chtimes(path, nearlyNew, nearlyNew); err != nil {
   738  		t.Fatal(err)
   739  	}
   740  	return func() {
   741  		if err := ioutil.WriteFile(path, old, 0666); err != nil {
   742  			t.Fatal(err)
   743  		}
   744  	}
   745  }
   746  
   747  // isNew returns if the path is newer than the time stamp used by touch.
   748  func isNew(t *testing.T, path string) bool {
   749  	fi, err := os.Stat(path)
   750  	if err != nil {
   751  		t.Fatal(err)
   752  	}
   753  	return fi.ModTime().After(stampTime)
   754  }
   755  
   756  // Fail unless path has been rebuilt (i.e. is newer than the time stamp used by
   757  // isNew)
   758  func AssertRebuilt(t *testing.T, msg, path string) {
   759  	t.Helper()
   760  	if !isNew(t, path) {
   761  		t.Errorf("%s was not rebuilt (%s)", msg, path)
   762  	}
   763  }
   764  
   765  // Fail if path has been rebuilt (i.e. is newer than the time stamp used by isNew)
   766  func AssertNotRebuilt(t *testing.T, msg, path string) {
   767  	t.Helper()
   768  	if isNew(t, path) {
   769  		t.Errorf("%s was rebuilt (%s)", msg, path)
   770  	}
   771  }
   772  
   773  func TestRebuilding(t *testing.T) {
   774  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
   775  	goCmd(t, "install", "-linkshared", "exe")
   776  
   777  	// If the source is newer than both the .a file and the .so, both are rebuilt.
   778  	t.Run("newsource", func(t *testing.T) {
   779  		resetFileStamps()
   780  		cleanup := touch(t, "src/depBase/dep.go")
   781  		defer func() {
   782  			cleanup()
   783  			goCmd(t, "install", "-linkshared", "exe")
   784  		}()
   785  		goCmd(t, "install", "-linkshared", "exe")
   786  		AssertRebuilt(t, "new source", filepath.Join(gopathInstallDir, "depBase.a"))
   787  		AssertRebuilt(t, "new source", filepath.Join(gopathInstallDir, "libdepBase.so"))
   788  	})
   789  
   790  	// If the .a file is newer than the .so, the .so is rebuilt (but not the .a)
   791  	t.Run("newarchive", func(t *testing.T) {
   792  		resetFileStamps()
   793  		goCmd(t, "list", "-linkshared", "-f={{.ImportPath}} {{.Stale}} {{.StaleReason}} {{.Target}}", "depBase")
   794  		AssertNotRebuilt(t, "new .a file before build", filepath.Join(gopathInstallDir, "depBase.a"))
   795  		cleanup := touch(t, filepath.Join(gopathInstallDir, "depBase.a"))
   796  		defer func() {
   797  			cleanup()
   798  			goCmd(t, "install", "-v", "-linkshared", "exe")
   799  		}()
   800  		goCmd(t, "install", "-v", "-linkshared", "exe")
   801  		AssertNotRebuilt(t, "new .a file", filepath.Join(gopathInstallDir, "depBase.a"))
   802  		AssertRebuilt(t, "new .a file", filepath.Join(gopathInstallDir, "libdepBase.so"))
   803  	})
   804  }
   805  
   806  func appendFile(t *testing.T, path, content string) {
   807  	f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0660)
   808  	if err != nil {
   809  		t.Fatalf("os.OpenFile failed: %v", err)
   810  	}
   811  	defer func() {
   812  		err := f.Close()
   813  		if err != nil {
   814  			t.Fatalf("f.Close failed: %v", err)
   815  		}
   816  	}()
   817  	_, err = f.WriteString(content)
   818  	if err != nil {
   819  		t.Fatalf("f.WriteString failed: %v", err)
   820  	}
   821  }
   822  
   823  func writeFile(t *testing.T, path, content string) {
   824  	err := ioutil.WriteFile(path, []byte(content), 0644)
   825  	if err != nil {
   826  		t.Fatalf("ioutil.WriteFile failed: %v", err)
   827  	}
   828  }
   829  
   830  func TestABIChecking(t *testing.T) {
   831  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
   832  	goCmd(t, "install", "-linkshared", "exe")
   833  
   834  	// If we make an ABI-breaking change to depBase and rebuild libp.so but not exe,
   835  	// exe will abort with a complaint on startup.
   836  	// This assumes adding an exported function breaks ABI, which is not true in
   837  	// some senses but suffices for the narrow definition of ABI compatibility the
   838  	// toolchain uses today.
   839  	resetFileStamps()
   840  	appendFile(t, "src/depBase/dep.go", "func ABIBreak() {}\n")
   841  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
   842  	c := exec.Command("./bin/exe")
   843  	output, err := c.CombinedOutput()
   844  	if err == nil {
   845  		t.Fatal("executing exe did not fail after ABI break")
   846  	}
   847  	scanner := bufio.NewScanner(bytes.NewReader(output))
   848  	foundMsg := false
   849  	const wantLine = "abi mismatch detected between the executable and libdepBase.so"
   850  	for scanner.Scan() {
   851  		if scanner.Text() == wantLine {
   852  			foundMsg = true
   853  			break
   854  		}
   855  	}
   856  	if err = scanner.Err(); err != nil {
   857  		t.Errorf("scanner encountered error: %v", err)
   858  	}
   859  	if !foundMsg {
   860  		t.Fatalf("exe failed, but without line %q; got output:\n%s", wantLine, output)
   861  	}
   862  
   863  	// Rebuilding exe makes it work again.
   864  	goCmd(t, "install", "-linkshared", "exe")
   865  	run(t, "rebuilt exe", "./bin/exe")
   866  
   867  	// If we make a change which does not break ABI (such as adding an unexported
   868  	// function) and rebuild libdepBase.so, exe still works, even if new function
   869  	// is in a file by itself.
   870  	resetFileStamps()
   871  	writeFile(t, "src/depBase/dep2.go", "package depBase\nfunc noABIBreak() {}\n")
   872  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
   873  	run(t, "after non-ABI breaking change", "./bin/exe")
   874  }
   875  
   876  // If a package 'explicit' imports a package 'implicit', building
   877  // 'explicit' into a shared library implicitly includes implicit in
   878  // the shared library. Building an executable that imports both
   879  // explicit and implicit builds the code from implicit into the
   880  // executable rather than fetching it from the shared library. The
   881  // link still succeeds and the executable still runs though.
   882  func TestImplicitInclusion(t *testing.T) {
   883  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "explicit")
   884  	goCmd(t, "install", "-linkshared", "implicitcmd")
   885  	run(t, "running executable linked against library that contains same package as it", "./bin/implicitcmd")
   886  }
   887  
   888  // Tests to make sure that the type fields of empty interfaces and itab
   889  // fields of nonempty interfaces are unique even across modules,
   890  // so that interface equality works correctly.
   891  func TestInterface(t *testing.T) {
   892  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "iface_a")
   893  	// Note: iface_i gets installed implicitly as a dependency of iface_a.
   894  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "iface_b")
   895  	goCmd(t, "install", "-linkshared", "iface")
   896  	run(t, "running type/itab uniqueness tester", "./bin/iface")
   897  }
   898  
   899  // Access a global variable from a library.
   900  func TestGlobal(t *testing.T) {
   901  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "globallib")
   902  	goCmd(t, "install", "-linkshared", "global")
   903  	run(t, "global executable", "./bin/global")
   904  	AssertIsLinkedTo(t, "./bin/global", soname)
   905  	AssertHasRPath(t, "./bin/global", gorootInstallDir)
   906  }