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
5package shared_test
6
7import (
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
30var 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).
34var minpkgs = []string{"runtime", "sync/atomic"}
35var soname = "libruntime,sync-atomic.so"
36
37// run runs a command and calls t.Errorf if it fails.
38func 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.
47func 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).
75func 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
165func 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.
180func TestSOBuilt(t *testing.T) {
181	_, err := os.Stat(filepath.Join(gorootInstallDir, soname))
182	if err != nil {
183		t.Error(err)
184	}
185}
186
187func 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.
214func 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"
227func 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.
249func 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?
270func 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
281func 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
294func 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
304type 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".
314func 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
352func 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
365func 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
374func AssertIsLinkedTo(t *testing.T, path, lib string) {
375	AssertIsLinkedToRegexp(t, path, regexp.MustCompile(regexp.QuoteMeta(lib)))
376}
377
378func 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.
392func 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.
400func 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.
408func 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.
415func TestCgoExecutable(t *testing.T) {
416	goCmd(t, "install", "-linkshared", "execgo")
417	run(t, "cgo executable", "./bin/execgo")
418}
419
420func 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
434func 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
442func 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.
452func 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.
466func 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.
481func 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.
516func 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.
530func 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.
583func 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
590func 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.
600func 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.
626func 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.
649func 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.
681var oldTime = time.Now().Add(-9 * time.Second)
682var nearlyNew = time.Now().Add(-6 * time.Second)
683var 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.
687func 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.
705func 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.
748func 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)
758func 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)
766func 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
773func 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		AssertNotRebuilt(t, "new .a file before build", filepath.Join(gopathInstallDir, "depBase.a"))
794		goCmd(t, "list", "-linkshared", "-f={{.ImportPath}} {{.Stale}} {{.StaleReason}} {{.Target}}", "depBase")
795		AssertNotRebuilt(t, "new .a file before build", filepath.Join(gopathInstallDir, "depBase.a"))
796		cleanup := touch(t, filepath.Join(gopathInstallDir, "depBase.a"))
797		defer func() {
798			cleanup()
799			goCmd(t, "install", "-v", "-linkshared", "exe")
800		}()
801		goCmd(t, "install", "-v", "-linkshared", "exe")
802		AssertNotRebuilt(t, "new .a file", filepath.Join(gopathInstallDir, "depBase.a"))
803		AssertRebuilt(t, "new .a file", filepath.Join(gopathInstallDir, "libdepBase.so"))
804	})
805}
806
807func appendFile(t *testing.T, path, content string) {
808	f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0660)
809	if err != nil {
810		t.Fatalf("os.OpenFile failed: %v", err)
811	}
812	defer func() {
813		err := f.Close()
814		if err != nil {
815			t.Fatalf("f.Close failed: %v", err)
816		}
817	}()
818	_, err = f.WriteString(content)
819	if err != nil {
820		t.Fatalf("f.WriteString failed: %v", err)
821	}
822}
823
824func writeFile(t *testing.T, path, content string) {
825	err := ioutil.WriteFile(path, []byte(content), 0644)
826	if err != nil {
827		t.Fatalf("ioutil.WriteFile failed: %v", err)
828	}
829}
830
831func TestABIChecking(t *testing.T) {
832	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
833	goCmd(t, "install", "-linkshared", "exe")
834
835	// If we make an ABI-breaking change to depBase and rebuild libp.so but not exe,
836	// exe will abort with a complaint on startup.
837	// This assumes adding an exported function breaks ABI, which is not true in
838	// some senses but suffices for the narrow definition of ABI compatibility the
839	// toolchain uses today.
840	resetFileStamps()
841	appendFile(t, "src/depBase/dep.go", "func ABIBreak() {}\n")
842	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
843	c := exec.Command("./bin/exe")
844	output, err := c.CombinedOutput()
845	if err == nil {
846		t.Fatal("executing exe did not fail after ABI break")
847	}
848	scanner := bufio.NewScanner(bytes.NewReader(output))
849	foundMsg := false
850	const wantLine = "abi mismatch detected between the executable and libdepBase.so"
851	for scanner.Scan() {
852		if scanner.Text() == wantLine {
853			foundMsg = true
854			break
855		}
856	}
857	if err = scanner.Err(); err != nil {
858		t.Errorf("scanner encountered error: %v", err)
859	}
860	if !foundMsg {
861		t.Fatalf("exe failed, but without line %q; got output:\n%s", wantLine, output)
862	}
863
864	// Rebuilding exe makes it work again.
865	goCmd(t, "install", "-linkshared", "exe")
866	run(t, "rebuilt exe", "./bin/exe")
867
868	// If we make a change which does not break ABI (such as adding an unexported
869	// function) and rebuild libdepBase.so, exe still works, even if new function
870	// is in a file by itself.
871	resetFileStamps()
872	writeFile(t, "src/depBase/dep2.go", "package depBase\nfunc noABIBreak() {}\n")
873	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
874	run(t, "after non-ABI breaking change", "./bin/exe")
875}
876
877// If a package 'explicit' imports a package 'implicit', building
878// 'explicit' into a shared library implicitly includes implicit in
879// the shared library. Building an executable that imports both
880// explicit and implicit builds the code from implicit into the
881// executable rather than fetching it from the shared library. The
882// link still succeeds and the executable still runs though.
883func TestImplicitInclusion(t *testing.T) {
884	goCmd(t, "install", "-buildmode=shared", "-linkshared", "explicit")
885	goCmd(t, "install", "-linkshared", "implicitcmd")
886	run(t, "running executable linked against library that contains same package as it", "./bin/implicitcmd")
887}
888
889// Tests to make sure that the type fields of empty interfaces and itab
890// fields of nonempty interfaces are unique even across modules,
891// so that interface equality works correctly.
892func TestInterface(t *testing.T) {
893	goCmd(t, "install", "-buildmode=shared", "-linkshared", "iface_a")
894	// Note: iface_i gets installed implicitly as a dependency of iface_a.
895	goCmd(t, "install", "-buildmode=shared", "-linkshared", "iface_b")
896	goCmd(t, "install", "-linkshared", "iface")
897	run(t, "running type/itab uniqueness tester", "./bin/iface")
898}
899
900// Access a global variable from a library.
901func TestGlobal(t *testing.T) {
902	goCmd(t, "install", "-buildmode=shared", "-linkshared", "globallib")
903	goCmd(t, "install", "-linkshared", "global")
904	run(t, "global executable", "./bin/global")
905	AssertIsLinkedTo(t, "./bin/global", soname)
906	AssertHasRPath(t, "./bin/global", gorootInstallDir)
907}
908