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

View as plain text