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

View as plain text