github.com/golang/gofrontend@v0.0.0-20240429183944-60f985a78526/libgo/misc/cgo/errors/badsym_test.go (about) 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 }