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

View as plain text