1 // Copyright 2020 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.
17 // A manually modified object file could pass unexpected characters
18 // into the files generated by cgo.
20 const magicInput = "abcdefghijklmnopqrstuvwxyz0123"
21 const magicReplace = "\n//go:cgo_ldflag \"-badflag\"\n//"
23 const cSymbol = "BadSymbol" + magicInput + "Name"
24 const cDefSource = "int " + cSymbol + " = 1;"
25 const cRefSource = "extern int " + cSymbol + "; int F() { return " + cSymbol + "; }"
27 // goSource is the source code for the trivial Go file we use.
28 // We will replace TMPDIR with the temporary directory name.
32 // #cgo LDFLAGS: TMPDIR/cbad.o TMPDIR/cbad.so
41 func TestBadSymbol(t *testing.T) {
44 mkdir := func(base string) string {
45 ret := filepath.Join(dir, base)
46 if err := os.Mkdir(ret, 0755); err != nil {
55 makeFile := func(mdir, base, source string) string {
56 ret := filepath.Join(mdir, base)
57 if err := os.WriteFile(ret, []byte(source), 0644); err != nil {
63 cDefFile := makeFile(cdir, "cdef.c", cDefSource)
64 cRefFile := makeFile(cdir, "cref.c", cRefSource)
66 ccCmd := cCompilerCmd(t)
68 cCompile := func(arg, base, src string) string {
69 out := filepath.Join(cdir, base)
70 run := append(ccCmd, arg, "-o", out, src)
71 output, err := exec.Command(run[0], run[1:]...).CombinedOutput()
77 if err := os.Remove(src); err != nil {
83 // Build a shared library that defines a symbol whose name
84 // contains magicInput.
86 cShared := cCompile("-shared", "c.so", cDefFile)
88 // Build an object file that refers to the symbol whose name
89 // contains magicInput.
91 cObj := cCompile("-c", "c.o", cRefFile)
93 // Rewrite the shared library and the object file, replacing
94 // magicInput with magicReplace. This will have the effect of
95 // introducing a symbol whose name looks like a cgo command.
96 // The cgo tool will use that name when it generates the
97 // _cgo_import.go file, thus smuggling a magic //go:cgo_ldflag
98 // pragma into a Go file. We used to not check the pragmas in
101 rewrite := func(from, to string) {
102 obj, err := os.ReadFile(from)
107 if bytes.Count(obj, []byte(magicInput)) == 0 {
108 t.Fatalf("%s: did not find magic string", from)
111 if len(magicInput) != len(magicReplace) {
112 t.Fatalf("internal test error: different magic lengths: %d != %d", len(magicInput), len(magicReplace))
115 obj = bytes.ReplaceAll(obj, []byte(magicInput), []byte(magicReplace))
117 if err := os.WriteFile(to, obj, 0644); err != nil {
122 cBadShared := filepath.Join(godir, "cbad.so")
123 rewrite(cShared, cBadShared)
125 cBadObj := filepath.Join(godir, "cbad.o")
126 rewrite(cObj, cBadObj)
128 goSourceBadObject := strings.ReplaceAll(goSource, "TMPDIR", godir)
129 makeFile(godir, "go.go", goSourceBadObject)
131 makeFile(godir, "go.mod", "module badsym")
133 // Try to build our little package.
134 cmd := exec.Command("go", "build", "-ldflags=-v")
136 output, err := cmd.CombinedOutput()
138 // The build should fail, but we want it to fail because we
139 // detected the error, not because we passed a bad flag to the
143 t.Errorf("go build succeeded unexpectedly")
148 for _, line := range bytes.Split(output, []byte("\n")) {
149 if bytes.Contains(line, []byte("dynamic symbol")) && bytes.Contains(line, []byte("contains unsupported character")) {
150 // This is the error from cgo.
154 // We passed -ldflags=-v to see the external linker invocation,
155 // which should not include -badflag.
156 if bytes.Contains(line, []byte("-badflag")) {
157 t.Error("output should not mention -badflag")
160 // Also check for compiler errors, just in case.
161 // GCC says "unrecognized command line option".
162 // clang says "unknown argument".
163 if bytes.Contains(line, []byte("unrecognized")) || bytes.Contains(output, []byte("unknown")) {
164 t.Error("problem should have been caught before invoking C linker")
169 func cCompilerCmd(t *testing.T) []string {
170 cc := []string{goEnv(t, "CC")}
172 out := goEnv(t, "GOGCCFLAGS")
178 for i, c := range s {
179 if quote == '\000' && unicode.IsSpace(c) {
181 cc = append(cc, s[start:i])
189 if quote == '\000' && !backslash && (c == '"' || c == '\'') {
192 } else if !backslash && quote == c {
194 } else if (quote == '\000' || quote == '"') && !backslash && c == '\\' {
202 cc = append(cc, s[start:])
205 // Force reallocation (and avoid aliasing bugs) for tests that append to cc.
206 cc = cc[:len(cc):len(cc)]
211 func goEnv(t *testing.T, key string) string {
212 out, err := exec.Command("go", "env", key).CombinedOutput()
214 t.Logf("go env %s\n", key)
218 return strings.TrimSpace(string(out))