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