123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- // Copyright 2020 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package errorstest
- import (
- "bytes"
- "os"
- "os/exec"
- "path/filepath"
- "strings"
- "testing"
- "unicode"
- )
- // A manually modified object file could pass unexpected characters
- // into the files generated by cgo.
- const magicInput = "abcdefghijklmnopqrstuvwxyz0123"
- const magicReplace = "\n//go:cgo_ldflag \"-badflag\"\n//"
- const cSymbol = "BadSymbol" + magicInput + "Name"
- const cDefSource = "int " + cSymbol + " = 1;"
- const cRefSource = "extern int " + cSymbol + "; int F() { return " + cSymbol + "; }"
- // goSource is the source code for the trivial Go file we use.
- // We will replace TMPDIR with the temporary directory name.
- const goSource = `
- package main
- // #cgo LDFLAGS: TMPDIR/cbad.o TMPDIR/cbad.so
- // extern int F();
- import "C"
- func main() {
- println(C.F())
- }
- `
- func TestBadSymbol(t *testing.T) {
- dir := t.TempDir()
- mkdir := func(base string) string {
- ret := filepath.Join(dir, base)
- if err := os.Mkdir(ret, 0755); err != nil {
- t.Fatal(err)
- }
- return ret
- }
- cdir := mkdir("c")
- godir := mkdir("go")
- makeFile := func(mdir, base, source string) string {
- ret := filepath.Join(mdir, base)
- if err := os.WriteFile(ret, []byte(source), 0644); err != nil {
- t.Fatal(err)
- }
- return ret
- }
- cDefFile := makeFile(cdir, "cdef.c", cDefSource)
- cRefFile := makeFile(cdir, "cref.c", cRefSource)
- ccCmd := cCompilerCmd(t)
- cCompile := func(arg, base, src string) string {
- out := filepath.Join(cdir, base)
- run := append(ccCmd, arg, "-o", out, src)
- output, err := exec.Command(run[0], run[1:]...).CombinedOutput()
- if err != nil {
- t.Log(run)
- t.Logf("%s", output)
- t.Fatal(err)
- }
- if err := os.Remove(src); err != nil {
- t.Fatal(err)
- }
- return out
- }
- // Build a shared library that defines a symbol whose name
- // contains magicInput.
- cShared := cCompile("-shared", "c.so", cDefFile)
- // Build an object file that refers to the symbol whose name
- // contains magicInput.
- cObj := cCompile("-c", "c.o", cRefFile)
- // Rewrite the shared library and the object file, replacing
- // magicInput with magicReplace. This will have the effect of
- // introducing a symbol whose name looks like a cgo command.
- // The cgo tool will use that name when it generates the
- // _cgo_import.go file, thus smuggling a magic //go:cgo_ldflag
- // pragma into a Go file. We used to not check the pragmas in
- // _cgo_import.go.
- rewrite := func(from, to string) {
- obj, err := os.ReadFile(from)
- if err != nil {
- t.Fatal(err)
- }
- if bytes.Count(obj, []byte(magicInput)) == 0 {
- t.Fatalf("%s: did not find magic string", from)
- }
- if len(magicInput) != len(magicReplace) {
- t.Fatalf("internal test error: different magic lengths: %d != %d", len(magicInput), len(magicReplace))
- }
- obj = bytes.ReplaceAll(obj, []byte(magicInput), []byte(magicReplace))
- if err := os.WriteFile(to, obj, 0644); err != nil {
- t.Fatal(err)
- }
- }
- cBadShared := filepath.Join(godir, "cbad.so")
- rewrite(cShared, cBadShared)
- cBadObj := filepath.Join(godir, "cbad.o")
- rewrite(cObj, cBadObj)
- goSourceBadObject := strings.ReplaceAll(goSource, "TMPDIR", godir)
- makeFile(godir, "go.go", goSourceBadObject)
- makeFile(godir, "go.mod", "module badsym")
- // Try to build our little package.
- cmd := exec.Command("go", "build", "-ldflags=-v")
- cmd.Dir = godir
- output, err := cmd.CombinedOutput()
- // The build should fail, but we want it to fail because we
- // detected the error, not because we passed a bad flag to the
- // C linker.
- if err == nil {
- t.Errorf("go build succeeded unexpectedly")
- }
- t.Logf("%s", output)
- for _, line := range bytes.Split(output, []byte("\n")) {
- if bytes.Contains(line, []byte("dynamic symbol")) && bytes.Contains(line, []byte("contains unsupported character")) {
- // This is the error from cgo.
- continue
- }
- // We passed -ldflags=-v to see the external linker invocation,
- // which should not include -badflag.
- if bytes.Contains(line, []byte("-badflag")) {
- t.Error("output should not mention -badflag")
- }
- // Also check for compiler errors, just in case.
- // GCC says "unrecognized command line option".
- // clang says "unknown argument".
- if bytes.Contains(line, []byte("unrecognized")) || bytes.Contains(output, []byte("unknown")) {
- t.Error("problem should have been caught before invoking C linker")
- }
- }
- }
- func cCompilerCmd(t *testing.T) []string {
- cc := []string{goEnv(t, "CC")}
- out := goEnv(t, "GOGCCFLAGS")
- quote := '\000'
- start := 0
- lastSpace := true
- backslash := false
- s := string(out)
- for i, c := range s {
- if quote == '\000' && unicode.IsSpace(c) {
- if !lastSpace {
- cc = append(cc, s[start:i])
- lastSpace = true
- }
- } else {
- if lastSpace {
- start = i
- lastSpace = false
- }
- if quote == '\000' && !backslash && (c == '"' || c == '\'') {
- quote = c
- backslash = false
- } else if !backslash && quote == c {
- quote = '\000'
- } else if (quote == '\000' || quote == '"') && !backslash && c == '\\' {
- backslash = true
- } else {
- backslash = false
- }
- }
- }
- if !lastSpace {
- cc = append(cc, s[start:])
- }
- // Force reallocation (and avoid aliasing bugs) for tests that append to cc.
- cc = cc[:len(cc):len(cc)]
- return cc
- }
- func goEnv(t *testing.T, key string) string {
- out, err := exec.Command("go", "env", key).CombinedOutput()
- if err != nil {
- t.Logf("go env %s\n", key)
- t.Logf("%s", out)
- t.Fatal(err)
- }
- return strings.TrimSpace(string(out))
- }
|