Source file misc/cgo/testcshared/cshared_test.go

     1  // Copyright 2017 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 cshared_test
     6  
     7  import (
     8  	"bytes"
     9  	"debug/elf"
    10  	"debug/pe"
    11  	"encoding/binary"
    12  	"flag"
    13  	"fmt"
    14  	"log"
    15  	"os"
    16  	"os/exec"
    17  	"path/filepath"
    18  	"runtime"
    19  	"strings"
    20  	"sync"
    21  	"testing"
    22  	"unicode"
    23  )
    24  
    25  // C compiler with args (from $(go env CC) $(go env GOGCCFLAGS)).
    26  var cc []string
    27  
    28  // ".exe" on Windows.
    29  var exeSuffix string
    30  
    31  var GOOS, GOARCH, GOROOT string
    32  var installdir, androiddir string
    33  var libSuffix, libgoname string
    34  
    35  func TestMain(m *testing.M) {
    36  	os.Exit(testMain(m))
    37  }
    38  
    39  func testMain(m *testing.M) int {
    40  	log.SetFlags(log.Lshortfile)
    41  	flag.Parse()
    42  	if testing.Short() && os.Getenv("GO_BUILDER_NAME") == "" {
    43  		fmt.Printf("SKIP - short mode and $GO_BUILDER_NAME not set\n")
    44  		os.Exit(0)
    45  	}
    46  
    47  	GOOS = goEnv("GOOS")
    48  	GOARCH = goEnv("GOARCH")
    49  	GOROOT = goEnv("GOROOT")
    50  
    51  	if _, err := os.Stat(GOROOT); os.IsNotExist(err) {
    52  		log.Fatalf("Unable able to find GOROOT at '%s'", GOROOT)
    53  	}
    54  
    55  	androiddir = fmt.Sprintf("/data/local/tmp/testcshared-%d", os.Getpid())
    56  	if runtime.GOOS != GOOS && GOOS == "android" {
    57  		args := append(adbCmd(), "exec-out", "mkdir", "-p", androiddir)
    58  		cmd := exec.Command(args[0], args[1:]...)
    59  		out, err := cmd.CombinedOutput()
    60  		if err != nil {
    61  			log.Fatalf("setupAndroid failed: %v\n%s\n", err, out)
    62  		}
    63  		defer cleanupAndroid()
    64  	}
    65  
    66  	cc = []string{goEnv("CC")}
    67  
    68  	out := goEnv("GOGCCFLAGS")
    69  	quote := '\000'
    70  	start := 0
    71  	lastSpace := true
    72  	backslash := false
    73  	s := string(out)
    74  	for i, c := range s {
    75  		if quote == '\000' && unicode.IsSpace(c) {
    76  			if !lastSpace {
    77  				cc = append(cc, s[start:i])
    78  				lastSpace = true
    79  			}
    80  		} else {
    81  			if lastSpace {
    82  				start = i
    83  				lastSpace = false
    84  			}
    85  			if quote == '\000' && !backslash && (c == '"' || c == '\'') {
    86  				quote = c
    87  				backslash = false
    88  			} else if !backslash && quote == c {
    89  				quote = '\000'
    90  			} else if (quote == '\000' || quote == '"') && !backslash && c == '\\' {
    91  				backslash = true
    92  			} else {
    93  				backslash = false
    94  			}
    95  		}
    96  	}
    97  	if !lastSpace {
    98  		cc = append(cc, s[start:])
    99  	}
   100  
   101  	switch GOOS {
   102  	case "darwin", "ios":
   103  		// For Darwin/ARM.
   104  		// TODO(crawshaw): can we do better?
   105  		cc = append(cc, []string{"-framework", "CoreFoundation", "-framework", "Foundation"}...)
   106  	case "android":
   107  		cc = append(cc, "-pie")
   108  	}
   109  	libgodir := GOOS + "_" + GOARCH
   110  	switch GOOS {
   111  	case "darwin", "ios":
   112  		if GOARCH == "arm64" {
   113  			libgodir += "_shared"
   114  		}
   115  	case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris", "illumos":
   116  		libgodir += "_shared"
   117  	}
   118  	cc = append(cc, "-I", filepath.Join("pkg", libgodir))
   119  
   120  	if GOOS == "windows" {
   121  		exeSuffix = ".exe"
   122  	}
   123  
   124  	// Copy testdata into GOPATH/src/testcshared, along with a go.mod file
   125  	// declaring the same path.
   126  
   127  	GOPATH, err := os.MkdirTemp("", "cshared_test")
   128  	if err != nil {
   129  		log.Panic(err)
   130  	}
   131  	defer os.RemoveAll(GOPATH)
   132  	os.Setenv("GOPATH", GOPATH)
   133  
   134  	modRoot := filepath.Join(GOPATH, "src", "testcshared")
   135  	if err := overlayDir(modRoot, "testdata"); err != nil {
   136  		log.Panic(err)
   137  	}
   138  	if err := os.Chdir(modRoot); err != nil {
   139  		log.Panic(err)
   140  	}
   141  	os.Setenv("PWD", modRoot)
   142  	if err := os.WriteFile("go.mod", []byte("module testcshared\n"), 0666); err != nil {
   143  		log.Panic(err)
   144  	}
   145  
   146  	// Directory where cgo headers and outputs will be installed.
   147  	// The installation directory format varies depending on the platform.
   148  	output, err := exec.Command("go", "list",
   149  		"-buildmode=c-shared",
   150  		"-installsuffix", "testcshared",
   151  		"-f", "{{.Target}}",
   152  		"./libgo").CombinedOutput()
   153  	if err != nil {
   154  		log.Panicf("go list failed: %v\n%s", err, output)
   155  	}
   156  	target := string(bytes.TrimSpace(output))
   157  	libgoname = filepath.Base(target)
   158  	installdir = filepath.Dir(target)
   159  	libSuffix = strings.TrimPrefix(filepath.Ext(target), ".")
   160  
   161  	return m.Run()
   162  }
   163  
   164  func goEnv(key string) string {
   165  	out, err := exec.Command("go", "env", key).Output()
   166  	if err != nil {
   167  		log.Printf("go env %s failed:\n%s", key, err)
   168  		log.Panicf("%s", err.(*exec.ExitError).Stderr)
   169  	}
   170  	return strings.TrimSpace(string(out))
   171  }
   172  
   173  func cmdToRun(name string) string {
   174  	return "./" + name + exeSuffix
   175  }
   176  
   177  func adbCmd() []string {
   178  	cmd := []string{"adb"}
   179  	if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
   180  		cmd = append(cmd, strings.Split(flags, " ")...)
   181  	}
   182  	return cmd
   183  }
   184  
   185  func adbPush(t *testing.T, filename string) {
   186  	if runtime.GOOS == GOOS || GOOS != "android" {
   187  		return
   188  	}
   189  	args := append(adbCmd(), "push", filename, fmt.Sprintf("%s/%s", androiddir, filename))
   190  	cmd := exec.Command(args[0], args[1:]...)
   191  	if out, err := cmd.CombinedOutput(); err != nil {
   192  		t.Fatalf("adb command failed: %v\n%s\n", err, out)
   193  	}
   194  }
   195  
   196  func adbRun(t *testing.T, env []string, adbargs ...string) string {
   197  	if GOOS != "android" {
   198  		t.Fatalf("trying to run adb command when operating system is not android.")
   199  	}
   200  	args := append(adbCmd(), "exec-out")
   201  	// Propagate LD_LIBRARY_PATH to the adb shell invocation.
   202  	for _, e := range env {
   203  		if strings.Index(e, "LD_LIBRARY_PATH=") != -1 {
   204  			adbargs = append([]string{e}, adbargs...)
   205  			break
   206  		}
   207  	}
   208  	shellcmd := fmt.Sprintf("cd %s; %s", androiddir, strings.Join(adbargs, " "))
   209  	args = append(args, shellcmd)
   210  	cmd := exec.Command(args[0], args[1:]...)
   211  	out, err := cmd.CombinedOutput()
   212  	if err != nil {
   213  		t.Fatalf("adb command failed: %v\n%s\n", err, out)
   214  	}
   215  	return strings.Replace(string(out), "\r", "", -1)
   216  }
   217  
   218  func run(t *testing.T, extraEnv []string, args ...string) string {
   219  	t.Helper()
   220  	cmd := exec.Command(args[0], args[1:]...)
   221  	if len(extraEnv) > 0 {
   222  		cmd.Env = append(os.Environ(), extraEnv...)
   223  	}
   224  
   225  	if GOOS != "windows" {
   226  		// TestUnexportedSymbols relies on file descriptor 30
   227  		// being closed when the program starts, so enforce
   228  		// that in all cases. (The first three descriptors are
   229  		// stdin/stdout/stderr, so we just need to make sure
   230  		// that cmd.ExtraFiles[27] exists and is nil.)
   231  		cmd.ExtraFiles = make([]*os.File, 28)
   232  	}
   233  
   234  	out, err := cmd.CombinedOutput()
   235  	if err != nil {
   236  		t.Fatalf("command failed: %v\n%v\n%s\n", args, err, out)
   237  	} else {
   238  		t.Logf("run: %v", args)
   239  	}
   240  	return string(out)
   241  }
   242  
   243  func runExe(t *testing.T, extraEnv []string, args ...string) string {
   244  	t.Helper()
   245  	if runtime.GOOS != GOOS && GOOS == "android" {
   246  		return adbRun(t, append(os.Environ(), extraEnv...), args...)
   247  	}
   248  	return run(t, extraEnv, args...)
   249  }
   250  
   251  func runCC(t *testing.T, args ...string) string {
   252  	t.Helper()
   253  	// This function is run in parallel, so append to a copy of cc
   254  	// rather than cc itself.
   255  	return run(t, nil, append(append([]string(nil), cc...), args...)...)
   256  }
   257  
   258  func createHeaders() error {
   259  	// The 'cgo' command generates a number of additional artifacts,
   260  	// but we're only interested in the header.
   261  	// Shunt the rest of the outputs to a temporary directory.
   262  	objDir, err := os.MkdirTemp("", "testcshared_obj")
   263  	if err != nil {
   264  		return err
   265  	}
   266  	defer os.RemoveAll(objDir)
   267  
   268  	// Generate a C header file for p, which is a non-main dependency
   269  	// of main package libgo.
   270  	//
   271  	// TODO(golang.org/issue/35715): This should be simpler.
   272  	args := []string{"go", "tool", "cgo",
   273  		"-objdir", objDir,
   274  		"-exportheader", "p.h",
   275  		filepath.Join(".", "p", "p.go")}
   276  	cmd := exec.Command(args[0], args[1:]...)
   277  	out, err := cmd.CombinedOutput()
   278  	if err != nil {
   279  		return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out)
   280  	}
   281  
   282  	// Generate a C header file for libgo itself.
   283  	args = []string{"go", "install", "-buildmode=c-shared",
   284  		"-installsuffix", "testcshared", "./libgo"}
   285  	cmd = exec.Command(args[0], args[1:]...)
   286  	out, err = cmd.CombinedOutput()
   287  	if err != nil {
   288  		return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out)
   289  	}
   290  
   291  	args = []string{"go", "build", "-buildmode=c-shared",
   292  		"-installsuffix", "testcshared",
   293  		"-o", libgoname,
   294  		filepath.Join(".", "libgo", "libgo.go")}
   295  	if GOOS == "windows" && strings.HasSuffix(args[6], ".a") {
   296  		args[6] = strings.TrimSuffix(args[6], ".a") + ".dll"
   297  	}
   298  	cmd = exec.Command(args[0], args[1:]...)
   299  	out, err = cmd.CombinedOutput()
   300  	if err != nil {
   301  		return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out)
   302  	}
   303  	if GOOS == "windows" {
   304  		// We can't simply pass -Wl,--out-implib, because this relies on having imports from multiple packages,
   305  		// which results in the linkers output implib getting overwritten at each step. So instead build the
   306  		// import library the traditional way, using a def file.
   307  		err = os.WriteFile("libgo.def",
   308  			[]byte("LIBRARY libgo.dll\nEXPORTS\n\tDidInitRun\n\tDidMainRun\n\tDivu\n\tFromPkg\n\t_cgo_dummy_export\n"),
   309  			0644)
   310  		if err != nil {
   311  			return fmt.Errorf("unable to write def file: %v", err)
   312  		}
   313  		out, err = exec.Command(cc[0], append(cc[1:], "-print-prog-name=dlltool")...).CombinedOutput()
   314  		if err != nil {
   315  			return fmt.Errorf("unable to find dlltool path: %v\n%s\n", err, out)
   316  		}
   317  		args := []string{strings.TrimSpace(string(out)), "-D", args[6], "-l", libgoname, "-d", "libgo.def"}
   318  
   319  		// This is an unfortunate workaround for https://github.com/mstorsjo/llvm-mingw/issues/205 in which
   320  		// we basically reimplement the contents of the dlltool.sh wrapper: https://git.io/JZFlU
   321  		dlltoolContents, err := os.ReadFile(args[0])
   322  		if err != nil {
   323  			return fmt.Errorf("unable to read dlltool: %v\n", err)
   324  		}
   325  		if bytes.HasPrefix(dlltoolContents, []byte("#!/bin/sh")) && bytes.Contains(dlltoolContents, []byte("llvm-dlltool")) {
   326  			base, name := filepath.Split(args[0])
   327  			args[0] = filepath.Join(base, "llvm-dlltool")
   328  			var machine string
   329  			switch strings.SplitN(name, "-", 2)[0] {
   330  			case "i686":
   331  				machine = "i386"
   332  			case "x86_64":
   333  				machine = "i386:x86-64"
   334  			case "armv7":
   335  				machine = "arm"
   336  			case "aarch64":
   337  				machine = "arm64"
   338  			}
   339  			if len(machine) > 0 {
   340  				args = append(args, "-m", machine)
   341  			}
   342  		}
   343  
   344  		out, err = exec.Command(args[0], args[1:]...).CombinedOutput()
   345  		if err != nil {
   346  			return fmt.Errorf("unable to run dlltool to create import library: %v\n%s\n", err, out)
   347  		}
   348  	}
   349  
   350  	if runtime.GOOS != GOOS && GOOS == "android" {
   351  		args = append(adbCmd(), "push", libgoname, fmt.Sprintf("%s/%s", androiddir, libgoname))
   352  		cmd = exec.Command(args[0], args[1:]...)
   353  		out, err = cmd.CombinedOutput()
   354  		if err != nil {
   355  			return fmt.Errorf("adb command failed: %v\n%s\n", err, out)
   356  		}
   357  	}
   358  
   359  	return nil
   360  }
   361  
   362  var (
   363  	headersOnce sync.Once
   364  	headersErr  error
   365  )
   366  
   367  func createHeadersOnce(t *testing.T) {
   368  	headersOnce.Do(func() {
   369  		headersErr = createHeaders()
   370  	})
   371  	if headersErr != nil {
   372  		t.Fatal(headersErr)
   373  	}
   374  }
   375  
   376  func cleanupAndroid() {
   377  	if GOOS != "android" {
   378  		return
   379  	}
   380  	args := append(adbCmd(), "exec-out", "rm", "-rf", androiddir)
   381  	cmd := exec.Command(args[0], args[1:]...)
   382  	out, err := cmd.CombinedOutput()
   383  	if err != nil {
   384  		log.Panicf("cleanupAndroid failed: %v\n%s\n", err, out)
   385  	}
   386  }
   387  
   388  // test0: exported symbols in shared lib are accessible.
   389  func TestExportedSymbols(t *testing.T) {
   390  	t.Parallel()
   391  
   392  	cmd := "testp0"
   393  	bin := cmdToRun(cmd)
   394  
   395  	createHeadersOnce(t)
   396  
   397  	runCC(t, "-I", installdir, "-o", cmd, "main0.c", libgoname)
   398  	adbPush(t, cmd)
   399  
   400  	defer os.Remove(bin)
   401  
   402  	out := runExe(t, []string{"LD_LIBRARY_PATH=."}, bin)
   403  	if strings.TrimSpace(out) != "PASS" {
   404  		t.Error(out)
   405  	}
   406  }
   407  
   408  func checkNumberOfExportedFunctionsWindows(t *testing.T, exportAllSymbols bool) {
   409  	const prog = `
   410  package main
   411  
   412  import "C"
   413  
   414  //export GoFunc
   415  func GoFunc() {
   416  	println(42)
   417  }
   418  
   419  //export GoFunc2
   420  func GoFunc2() {
   421  	println(24)
   422  }
   423  
   424  func main() {
   425  }
   426  `
   427  
   428  	tmpdir := t.TempDir()
   429  
   430  	srcfile := filepath.Join(tmpdir, "test.go")
   431  	objfile := filepath.Join(tmpdir, "test.dll")
   432  	if err := os.WriteFile(srcfile, []byte(prog), 0666); err != nil {
   433  		t.Fatal(err)
   434  	}
   435  	argv := []string{"build", "-buildmode=c-shared"}
   436  	if exportAllSymbols {
   437  		argv = append(argv, "-ldflags", "-extldflags=-Wl,--export-all-symbols")
   438  	}
   439  	argv = append(argv, "-o", objfile, srcfile)
   440  	out, err := exec.Command("go", argv...).CombinedOutput()
   441  	if err != nil {
   442  		t.Fatalf("build failure: %s\n%s\n", err, string(out))
   443  	}
   444  
   445  	f, err := pe.Open(objfile)
   446  	if err != nil {
   447  		t.Fatalf("pe.Open failed: %v", err)
   448  	}
   449  	defer f.Close()
   450  	section := f.Section(".edata")
   451  	if section == nil {
   452  		t.Skip(".edata section is not present")
   453  	}
   454  
   455  	// TODO: deduplicate this struct from cmd/link/internal/ld/pe.go
   456  	type IMAGE_EXPORT_DIRECTORY struct {
   457  		_                 [2]uint32
   458  		_                 [2]uint16
   459  		_                 [2]uint32
   460  		NumberOfFunctions uint32
   461  		NumberOfNames     uint32
   462  		_                 [3]uint32
   463  	}
   464  	var e IMAGE_EXPORT_DIRECTORY
   465  	if err := binary.Read(section.Open(), binary.LittleEndian, &e); err != nil {
   466  		t.Fatalf("binary.Read failed: %v", err)
   467  	}
   468  
   469  	// Only the two exported functions and _cgo_dummy_export should be exported
   470  	expectedNumber := uint32(3)
   471  
   472  	if exportAllSymbols {
   473  		if e.NumberOfFunctions <= expectedNumber {
   474  			t.Fatalf("missing exported functions: %v", e.NumberOfFunctions)
   475  		}
   476  		if e.NumberOfNames <= expectedNumber {
   477  			t.Fatalf("missing exported names: %v", e.NumberOfNames)
   478  		}
   479  	} else {
   480  		if e.NumberOfFunctions != expectedNumber {
   481  			t.Fatalf("got %d exported functions; want %d", e.NumberOfFunctions, expectedNumber)
   482  		}
   483  		if e.NumberOfNames != expectedNumber {
   484  			t.Fatalf("got %d exported names; want %d", e.NumberOfNames, expectedNumber)
   485  		}
   486  	}
   487  }
   488  
   489  func TestNumberOfExportedFunctions(t *testing.T) {
   490  	if GOOS != "windows" {
   491  		t.Skip("skipping windows only test")
   492  	}
   493  	t.Parallel()
   494  
   495  	t.Run("OnlyExported", func(t *testing.T) {
   496  		checkNumberOfExportedFunctionsWindows(t, false)
   497  	})
   498  	t.Run("All", func(t *testing.T) {
   499  		checkNumberOfExportedFunctionsWindows(t, true)
   500  	})
   501  }
   502  
   503  // test1: shared library can be dynamically loaded and exported symbols are accessible.
   504  func TestExportedSymbolsWithDynamicLoad(t *testing.T) {
   505  	t.Parallel()
   506  
   507  	if GOOS == "windows" {
   508  		t.Logf("Skipping on %s", GOOS)
   509  		return
   510  	}
   511  
   512  	cmd := "testp1"
   513  	bin := cmdToRun(cmd)
   514  
   515  	createHeadersOnce(t)
   516  
   517  	if GOOS != "freebsd" {
   518  		runCC(t, "-o", cmd, "main1.c", "-ldl")
   519  	} else {
   520  		runCC(t, "-o", cmd, "main1.c")
   521  	}
   522  	adbPush(t, cmd)
   523  
   524  	defer os.Remove(bin)
   525  
   526  	out := runExe(t, nil, bin, "./"+libgoname)
   527  	if strings.TrimSpace(out) != "PASS" {
   528  		t.Error(out)
   529  	}
   530  }
   531  
   532  // test2: tests libgo2 which does not export any functions.
   533  func TestUnexportedSymbols(t *testing.T) {
   534  	t.Parallel()
   535  
   536  	if GOOS == "windows" {
   537  		t.Logf("Skipping on %s", GOOS)
   538  		return
   539  	}
   540  
   541  	cmd := "testp2"
   542  	bin := cmdToRun(cmd)
   543  	libname := "libgo2." + libSuffix
   544  
   545  	run(t,
   546  		nil,
   547  		"go", "build",
   548  		"-buildmode=c-shared",
   549  		"-installsuffix", "testcshared",
   550  		"-o", libname, "./libgo2",
   551  	)
   552  	adbPush(t, libname)
   553  
   554  	linkFlags := "-Wl,--no-as-needed"
   555  	if GOOS == "darwin" || GOOS == "ios" {
   556  		linkFlags = ""
   557  	}
   558  
   559  	runCC(t, "-o", cmd, "main2.c", linkFlags, libname)
   560  	adbPush(t, cmd)
   561  
   562  	defer os.Remove(libname)
   563  	defer os.Remove(bin)
   564  
   565  	out := runExe(t, []string{"LD_LIBRARY_PATH=."}, bin)
   566  
   567  	if strings.TrimSpace(out) != "PASS" {
   568  		t.Error(out)
   569  	}
   570  }
   571  
   572  // test3: tests main.main is exported on android.
   573  func TestMainExportedOnAndroid(t *testing.T) {
   574  	t.Parallel()
   575  
   576  	switch GOOS {
   577  	case "android":
   578  		break
   579  	default:
   580  		t.Logf("Skipping on %s", GOOS)
   581  		return
   582  	}
   583  
   584  	cmd := "testp3"
   585  	bin := cmdToRun(cmd)
   586  
   587  	createHeadersOnce(t)
   588  
   589  	runCC(t, "-o", cmd, "main3.c", "-ldl")
   590  	adbPush(t, cmd)
   591  
   592  	defer os.Remove(bin)
   593  
   594  	out := runExe(t, nil, bin, "./"+libgoname)
   595  	if strings.TrimSpace(out) != "PASS" {
   596  		t.Error(out)
   597  	}
   598  }
   599  
   600  func testSignalHandlers(t *testing.T, pkgname, cfile, cmd string) {
   601  	libname := pkgname + "." + libSuffix
   602  	run(t,
   603  		nil,
   604  		"go", "build",
   605  		"-buildmode=c-shared",
   606  		"-installsuffix", "testcshared",
   607  		"-o", libname, pkgname,
   608  	)
   609  	adbPush(t, libname)
   610  	if GOOS != "freebsd" {
   611  		runCC(t, "-pthread", "-o", cmd, cfile, "-ldl")
   612  	} else {
   613  		runCC(t, "-pthread", "-o", cmd, cfile)
   614  	}
   615  	adbPush(t, cmd)
   616  
   617  	bin := cmdToRun(cmd)
   618  
   619  	defer os.Remove(libname)
   620  	defer os.Remove(bin)
   621  	defer os.Remove(pkgname + ".h")
   622  
   623  	out := runExe(t, nil, bin, "./"+libname)
   624  	if strings.TrimSpace(out) != "PASS" {
   625  		t.Error(run(t, nil, bin, libname, "verbose"))
   626  	}
   627  }
   628  
   629  // test4: test signal handlers
   630  func TestSignalHandlers(t *testing.T) {
   631  	t.Parallel()
   632  	if GOOS == "windows" {
   633  		t.Logf("Skipping on %s", GOOS)
   634  		return
   635  	}
   636  	testSignalHandlers(t, "./libgo4", "main4.c", "testp4")
   637  }
   638  
   639  // test5: test signal handlers with os/signal.Notify
   640  func TestSignalHandlersWithNotify(t *testing.T) {
   641  	t.Parallel()
   642  	if GOOS == "windows" {
   643  		t.Logf("Skipping on %s", GOOS)
   644  		return
   645  	}
   646  	testSignalHandlers(t, "./libgo5", "main5.c", "testp5")
   647  }
   648  
   649  func TestPIE(t *testing.T) {
   650  	t.Parallel()
   651  
   652  	switch GOOS {
   653  	case "linux", "android":
   654  		break
   655  	default:
   656  		t.Logf("Skipping on %s", GOOS)
   657  		return
   658  	}
   659  
   660  	createHeadersOnce(t)
   661  
   662  	f, err := elf.Open(libgoname)
   663  	if err != nil {
   664  		t.Fatalf("elf.Open failed: %v", err)
   665  	}
   666  	defer f.Close()
   667  
   668  	ds := f.SectionByType(elf.SHT_DYNAMIC)
   669  	if ds == nil {
   670  		t.Fatalf("no SHT_DYNAMIC section")
   671  	}
   672  	d, err := ds.Data()
   673  	if err != nil {
   674  		t.Fatalf("can't read SHT_DYNAMIC contents: %v", err)
   675  	}
   676  	for len(d) > 0 {
   677  		var tag elf.DynTag
   678  		switch f.Class {
   679  		case elf.ELFCLASS32:
   680  			tag = elf.DynTag(f.ByteOrder.Uint32(d[:4]))
   681  			d = d[8:]
   682  		case elf.ELFCLASS64:
   683  			tag = elf.DynTag(f.ByteOrder.Uint64(d[:8]))
   684  			d = d[16:]
   685  		}
   686  		if tag == elf.DT_TEXTREL {
   687  			t.Fatalf("%s has DT_TEXTREL flag", libgoname)
   688  		}
   689  	}
   690  }
   691  
   692  // Test that installing a second time recreates the header file.
   693  func TestCachedInstall(t *testing.T) {
   694  	tmpdir, err := os.MkdirTemp("", "cshared")
   695  	if err != nil {
   696  		t.Fatal(err)
   697  	}
   698  	defer os.RemoveAll(tmpdir)
   699  
   700  	copyFile(t, filepath.Join(tmpdir, "src", "testcshared", "go.mod"), "go.mod")
   701  	copyFile(t, filepath.Join(tmpdir, "src", "testcshared", "libgo", "libgo.go"), filepath.Join("libgo", "libgo.go"))
   702  	copyFile(t, filepath.Join(tmpdir, "src", "testcshared", "p", "p.go"), filepath.Join("p", "p.go"))
   703  
   704  	env := append(os.Environ(), "GOPATH="+tmpdir, "GOBIN="+filepath.Join(tmpdir, "bin"))
   705  
   706  	buildcmd := []string{"go", "install", "-x", "-buildmode=c-shared", "-installsuffix", "testcshared", "./libgo"}
   707  
   708  	cmd := exec.Command(buildcmd[0], buildcmd[1:]...)
   709  	cmd.Dir = filepath.Join(tmpdir, "src", "testcshared")
   710  	cmd.Env = env
   711  	t.Log(buildcmd)
   712  	out, err := cmd.CombinedOutput()
   713  	t.Logf("%s", out)
   714  	if err != nil {
   715  		t.Fatal(err)
   716  	}
   717  
   718  	var libgoh, ph string
   719  
   720  	walker := func(path string, info os.FileInfo, err error) error {
   721  		if err != nil {
   722  			t.Fatal(err)
   723  		}
   724  		var ps *string
   725  		switch filepath.Base(path) {
   726  		case "libgo.h":
   727  			ps = &libgoh
   728  		case "p.h":
   729  			ps = &ph
   730  		}
   731  		if ps != nil {
   732  			if *ps != "" {
   733  				t.Fatalf("%s found again", *ps)
   734  			}
   735  			*ps = path
   736  		}
   737  		return nil
   738  	}
   739  
   740  	if err := filepath.Walk(tmpdir, walker); err != nil {
   741  		t.Fatal(err)
   742  	}
   743  
   744  	if libgoh == "" {
   745  		t.Fatal("libgo.h not installed")
   746  	}
   747  
   748  	if err := os.Remove(libgoh); err != nil {
   749  		t.Fatal(err)
   750  	}
   751  
   752  	cmd = exec.Command(buildcmd[0], buildcmd[1:]...)
   753  	cmd.Dir = filepath.Join(tmpdir, "src", "testcshared")
   754  	cmd.Env = env
   755  	t.Log(buildcmd)
   756  	out, err = cmd.CombinedOutput()
   757  	t.Logf("%s", out)
   758  	if err != nil {
   759  		t.Fatal(err)
   760  	}
   761  
   762  	if _, err := os.Stat(libgoh); err != nil {
   763  		t.Errorf("libgo.h not installed in second run: %v", err)
   764  	}
   765  }
   766  
   767  // copyFile copies src to dst.
   768  func copyFile(t *testing.T, dst, src string) {
   769  	t.Helper()
   770  	data, err := os.ReadFile(src)
   771  	if err != nil {
   772  		t.Fatal(err)
   773  	}
   774  	if err := os.MkdirAll(filepath.Dir(dst), 0777); err != nil {
   775  		t.Fatal(err)
   776  	}
   777  	if err := os.WriteFile(dst, data, 0666); err != nil {
   778  		t.Fatal(err)
   779  	}
   780  }
   781  
   782  func TestGo2C2Go(t *testing.T) {
   783  	switch GOOS {
   784  	case "darwin", "ios":
   785  		// Darwin shared libraries don't support the multiple
   786  		// copies of the runtime package implied by this test.
   787  		t.Skip("linking c-shared into Go programs not supported on Darwin; issue 29061")
   788  	case "android":
   789  		t.Skip("test fails on android; issue 29087")
   790  	}
   791  
   792  	t.Parallel()
   793  
   794  	tmpdir, err := os.MkdirTemp("", "cshared-TestGo2C2Go")
   795  	if err != nil {
   796  		t.Fatal(err)
   797  	}
   798  	defer os.RemoveAll(tmpdir)
   799  
   800  	lib := filepath.Join(tmpdir, "libtestgo2c2go."+libSuffix)
   801  	var env []string
   802  	if GOOS == "windows" && strings.HasSuffix(lib, ".a") {
   803  		env = append(env, "CGO_LDFLAGS=-Wl,--out-implib,"+lib, "CGO_LDFLAGS_ALLOW=.*")
   804  		lib = strings.TrimSuffix(lib, ".a") + ".dll"
   805  	}
   806  	run(t, env, "go", "build", "-buildmode=c-shared", "-o", lib, "./go2c2go/go")
   807  
   808  	cgoCflags := os.Getenv("CGO_CFLAGS")
   809  	if cgoCflags != "" {
   810  		cgoCflags += " "
   811  	}
   812  	cgoCflags += "-I" + tmpdir
   813  
   814  	cgoLdflags := os.Getenv("CGO_LDFLAGS")
   815  	if cgoLdflags != "" {
   816  		cgoLdflags += " "
   817  	}
   818  	cgoLdflags += "-L" + tmpdir + " -ltestgo2c2go"
   819  
   820  	goenv := []string{"CGO_CFLAGS=" + cgoCflags, "CGO_LDFLAGS=" + cgoLdflags}
   821  
   822  	ldLibPath := os.Getenv("LD_LIBRARY_PATH")
   823  	if ldLibPath != "" {
   824  		ldLibPath += ":"
   825  	}
   826  	ldLibPath += tmpdir
   827  
   828  	runenv := []string{"LD_LIBRARY_PATH=" + ldLibPath}
   829  
   830  	bin := filepath.Join(tmpdir, "m1") + exeSuffix
   831  	run(t, goenv, "go", "build", "-o", bin, "./go2c2go/m1")
   832  	runExe(t, runenv, bin)
   833  
   834  	bin = filepath.Join(tmpdir, "m2") + exeSuffix
   835  	run(t, goenv, "go", "build", "-o", bin, "./go2c2go/m2")
   836  	runExe(t, runenv, bin)
   837  }
   838  

View as plain text