]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/cgo/internal/testerrors/badsym_test.go
misc/cgo: move easy tests to cmd/cgo/internal
[gostls13.git] / src / cmd / cgo / internal / testerrors / badsym_test.go
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.
4
5 package errorstest
6
7 import (
8         "bytes"
9         "os"
10         "os/exec"
11         "path/filepath"
12         "strings"
13         "testing"
14         "unicode"
15 )
16
17 // A manually modified object file could pass unexpected characters
18 // into the files generated by cgo.
19
20 const magicInput = "abcdefghijklmnopqrstuvwxyz0123"
21 const magicReplace = "\n//go:cgo_ldflag \"-badflag\"\n//"
22
23 const cSymbol = "BadSymbol" + magicInput + "Name"
24 const cDefSource = "int " + cSymbol + " = 1;"
25 const cRefSource = "extern int " + cSymbol + "; int F() { return " + cSymbol + "; }"
26
27 // goSource is the source code for the trivial Go file we use.
28 // We will replace TMPDIR with the temporary directory name.
29 const goSource = `
30 package main
31
32 // #cgo LDFLAGS: TMPDIR/cbad.o TMPDIR/cbad.so
33 // extern int F();
34 import "C"
35
36 func main() {
37         println(C.F())
38 }
39 `
40
41 func TestBadSymbol(t *testing.T) {
42         dir := t.TempDir()
43
44         mkdir := func(base string) string {
45                 ret := filepath.Join(dir, base)
46                 if err := os.Mkdir(ret, 0755); err != nil {
47                         t.Fatal(err)
48                 }
49                 return ret
50         }
51
52         cdir := mkdir("c")
53         godir := mkdir("go")
54
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 {
58                         t.Fatal(err)
59                 }
60                 return ret
61         }
62
63         cDefFile := makeFile(cdir, "cdef.c", cDefSource)
64         cRefFile := makeFile(cdir, "cref.c", cRefSource)
65
66         ccCmd := cCompilerCmd(t)
67
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()
72                 if err != nil {
73                         t.Log(run)
74                         t.Logf("%s", output)
75                         t.Fatal(err)
76                 }
77                 if err := os.Remove(src); err != nil {
78                         t.Fatal(err)
79                 }
80                 return out
81         }
82
83         // Build a shared library that defines a symbol whose name
84         // contains magicInput.
85
86         cShared := cCompile("-shared", "c.so", cDefFile)
87
88         // Build an object file that refers to the symbol whose name
89         // contains magicInput.
90
91         cObj := cCompile("-c", "c.o", cRefFile)
92
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
99         // _cgo_import.go.
100
101         rewrite := func(from, to string) {
102                 obj, err := os.ReadFile(from)
103                 if err != nil {
104                         t.Fatal(err)
105                 }
106
107                 if bytes.Count(obj, []byte(magicInput)) == 0 {
108                         t.Fatalf("%s: did not find magic string", from)
109                 }
110
111                 if len(magicInput) != len(magicReplace) {
112                         t.Fatalf("internal test error: different magic lengths: %d != %d", len(magicInput), len(magicReplace))
113                 }
114
115                 obj = bytes.ReplaceAll(obj, []byte(magicInput), []byte(magicReplace))
116
117                 if err := os.WriteFile(to, obj, 0644); err != nil {
118                         t.Fatal(err)
119                 }
120         }
121
122         cBadShared := filepath.Join(godir, "cbad.so")
123         rewrite(cShared, cBadShared)
124
125         cBadObj := filepath.Join(godir, "cbad.o")
126         rewrite(cObj, cBadObj)
127
128         goSourceBadObject := strings.ReplaceAll(goSource, "TMPDIR", godir)
129         makeFile(godir, "go.go", goSourceBadObject)
130
131         makeFile(godir, "go.mod", "module badsym")
132
133         // Try to build our little package.
134         cmd := exec.Command("go", "build", "-ldflags=-v")
135         cmd.Dir = godir
136         output, err := cmd.CombinedOutput()
137
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
140         // C linker.
141
142         if err == nil {
143                 t.Errorf("go build succeeded unexpectedly")
144         }
145
146         t.Logf("%s", output)
147
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.
151                         continue
152                 }
153
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")
158                 }
159
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")
165                 }
166         }
167 }
168
169 func cCompilerCmd(t *testing.T) []string {
170         cc := []string{goEnv(t, "CC")}
171
172         out := goEnv(t, "GOGCCFLAGS")
173         quote := '\000'
174         start := 0
175         lastSpace := true
176         backslash := false
177         s := string(out)
178         for i, c := range s {
179                 if quote == '\000' && unicode.IsSpace(c) {
180                         if !lastSpace {
181                                 cc = append(cc, s[start:i])
182                                 lastSpace = true
183                         }
184                 } else {
185                         if lastSpace {
186                                 start = i
187                                 lastSpace = false
188                         }
189                         if quote == '\000' && !backslash && (c == '"' || c == '\'') {
190                                 quote = c
191                                 backslash = false
192                         } else if !backslash && quote == c {
193                                 quote = '\000'
194                         } else if (quote == '\000' || quote == '"') && !backslash && c == '\\' {
195                                 backslash = true
196                         } else {
197                                 backslash = false
198                         }
199                 }
200         }
201         if !lastSpace {
202                 cc = append(cc, s[start:])
203         }
204
205         // Force reallocation (and avoid aliasing bugs) for tests that append to cc.
206         cc = cc[:len(cc):len(cc)]
207
208         return cc
209 }
210
211 func goEnv(t *testing.T, key string) string {
212         out, err := exec.Command("go", "env", key).CombinedOutput()
213         if err != nil {
214                 t.Logf("go env %s\n", key)
215                 t.Logf("%s", out)
216                 t.Fatal(err)
217         }
218         return strings.TrimSpace(string(out))
219 }