1// Copyright 2019 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 5package plugin_test 6 7import ( 8 "bytes" 9 "cmd/cgo/internal/cgotest" 10 "context" 11 "flag" 12 "fmt" 13 "internal/platform" 14 "internal/testenv" 15 "log" 16 "os" 17 "os/exec" 18 "path/filepath" 19 "runtime" 20 "strings" 21 "testing" 22 "time" 23) 24 25var globalSkip = func(t *testing.T) {} 26 27var gcflags string = os.Getenv("GO_GCFLAGS") 28var goroot string 29 30func TestMain(m *testing.M) { 31 flag.Parse() 32 log.SetFlags(log.Lshortfile) 33 os.Exit(testMain(m)) 34} 35 36// tmpDir is used to cleanup logged commands -- s/tmpDir/$TMPDIR/ 37var tmpDir string 38 39// prettyPrintf prints lines with tmpDir sanitized. 40func prettyPrintf(format string, args ...interface{}) { 41 s := fmt.Sprintf(format, args...) 42 if tmpDir != "" { 43 s = strings.ReplaceAll(s, tmpDir, "$TMPDIR") 44 } 45 fmt.Print(s) 46} 47 48func testMain(m *testing.M) int { 49 if testing.Short() && os.Getenv("GO_BUILDER_NAME") == "" { 50 globalSkip = func(t *testing.T) { t.Skip("short mode and $GO_BUILDER_NAME not set") } 51 return m.Run() 52 } 53 if !platform.BuildModeSupported(runtime.Compiler, "plugin", runtime.GOOS, runtime.GOARCH) { 54 globalSkip = func(t *testing.T) { t.Skip("plugin build mode not supported") } 55 return m.Run() 56 } 57 if !testenv.HasCGO() { 58 globalSkip = func(t *testing.T) { t.Skip("cgo not supported") } 59 return m.Run() 60 } 61 62 cwd, err := os.Getwd() 63 if err != nil { 64 log.Fatal(err) 65 } 66 goroot = filepath.Join(cwd, "../../../../..") 67 68 // Copy testdata into GOPATH/src/testplugin, along with a go.mod file 69 // declaring the same path. 70 71 GOPATH, err := os.MkdirTemp("", "plugin_test") 72 if err != nil { 73 log.Panic(err) 74 } 75 defer os.RemoveAll(GOPATH) 76 tmpDir = GOPATH 77 fmt.Printf("TMPDIR=%s\n", tmpDir) 78 79 modRoot := filepath.Join(GOPATH, "src", "testplugin") 80 altRoot := filepath.Join(GOPATH, "alt", "src", "testplugin") 81 for srcRoot, dstRoot := range map[string]string{ 82 "testdata": modRoot, 83 filepath.Join("altpath", "testdata"): altRoot, 84 } { 85 if err := cgotest.OverlayDir(dstRoot, srcRoot); err != nil { 86 log.Panic(err) 87 } 88 prettyPrintf("mkdir -p %s\n", dstRoot) 89 prettyPrintf("rsync -a %s/ %s\n", srcRoot, dstRoot) 90 91 if err := os.WriteFile(filepath.Join(dstRoot, "go.mod"), []byte("module testplugin\n"), 0666); err != nil { 92 log.Panic(err) 93 } 94 prettyPrintf("echo 'module testplugin' > %s/go.mod\n", dstRoot) 95 } 96 97 os.Setenv("GOPATH", filepath.Join(GOPATH, "alt")) 98 if err := os.Chdir(altRoot); err != nil { 99 log.Panic(err) 100 } else { 101 prettyPrintf("cd %s\n", altRoot) 102 } 103 os.Setenv("PWD", altRoot) 104 goCmd(nil, "build", "-buildmode=plugin", "-o", filepath.Join(modRoot, "plugin-mismatch.so"), "./plugin-mismatch") 105 106 os.Setenv("GOPATH", GOPATH) 107 if err := os.Chdir(modRoot); err != nil { 108 log.Panic(err) 109 } else { 110 prettyPrintf("cd %s\n", modRoot) 111 } 112 os.Setenv("PWD", modRoot) 113 114 os.Setenv("LD_LIBRARY_PATH", modRoot) 115 116 goCmd(nil, "build", "-buildmode=plugin", "./plugin1") 117 goCmd(nil, "build", "-buildmode=plugin", "./plugin2") 118 so, err := os.ReadFile("plugin2.so") 119 if err != nil { 120 log.Panic(err) 121 } 122 if err := os.WriteFile("plugin2-dup.so", so, 0444); err != nil { 123 log.Panic(err) 124 } 125 prettyPrintf("cp plugin2.so plugin2-dup.so\n") 126 127 goCmd(nil, "build", "-buildmode=plugin", "-o=sub/plugin1.so", "./sub/plugin1") 128 goCmd(nil, "build", "-buildmode=plugin", "-o=unnamed1.so", "./unnamed1/main.go") 129 goCmd(nil, "build", "-buildmode=plugin", "-o=unnamed2.so", "./unnamed2/main.go") 130 goCmd(nil, "build", "-o", "host.exe", "./host") 131 132 return m.Run() 133} 134 135func goCmd(t *testing.T, op string, args ...string) string { 136 if t != nil { 137 t.Helper() 138 } 139 var flags []string 140 if op != "tool" { 141 flags = []string{"-gcflags", gcflags} 142 } 143 return run(t, filepath.Join(goroot, "bin", "go"), append(append([]string{op}, flags...), args...)...) 144} 145 146// escape converts a string to something suitable for a shell command line. 147func escape(s string) string { 148 s = strings.Replace(s, "\\", "\\\\", -1) 149 s = strings.Replace(s, "'", "\\'", -1) 150 // Conservative guess at characters that will force quoting 151 if s == "" || strings.ContainsAny(s, "\\ ;#*&$~?!|[]()<>{}`") { 152 s = "'" + s + "'" 153 } 154 return s 155} 156 157// asCommandLine renders cmd as something that could be copy-and-pasted into a command line 158func asCommandLine(cwd string, cmd *exec.Cmd) string { 159 s := "(" 160 if cmd.Dir != "" && cmd.Dir != cwd { 161 s += "cd" + escape(cmd.Dir) + ";" 162 } 163 for _, e := range cmd.Env { 164 if !strings.HasPrefix(e, "PATH=") && 165 !strings.HasPrefix(e, "HOME=") && 166 !strings.HasPrefix(e, "USER=") && 167 !strings.HasPrefix(e, "SHELL=") { 168 s += " " 169 s += escape(e) 170 } 171 } 172 // These EVs are relevant to this test. 173 for _, e := range os.Environ() { 174 if strings.HasPrefix(e, "PWD=") || 175 strings.HasPrefix(e, "GOPATH=") || 176 strings.HasPrefix(e, "LD_LIBRARY_PATH=") { 177 s += " " 178 s += escape(e) 179 } 180 } 181 for _, a := range cmd.Args { 182 s += " " 183 s += escape(a) 184 } 185 s += " )" 186 return s 187} 188 189func run(t *testing.T, bin string, args ...string) string { 190 cmd := exec.Command(bin, args...) 191 cmdLine := asCommandLine(".", cmd) 192 prettyPrintf("%s\n", cmdLine) 193 cmd.Stderr = new(strings.Builder) 194 out, err := cmd.Output() 195 if err != nil { 196 if t == nil { 197 log.Panicf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr) 198 } else { 199 t.Helper() 200 t.Fatalf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, cmd.Stderr) 201 } 202 } 203 204 return string(bytes.TrimSpace(out)) 205} 206 207func TestDWARFSections(t *testing.T) { 208 // test that DWARF sections are emitted for plugins and programs importing "plugin" 209 globalSkip(t) 210 goCmd(t, "run", "./checkdwarf/main.go", "plugin2.so", "plugin2.UnexportedNameReuse") 211 goCmd(t, "run", "./checkdwarf/main.go", "./host.exe", "main.main") 212} 213 214func TestBuildID(t *testing.T) { 215 // check that plugin has build ID. 216 globalSkip(t) 217 b := goCmd(t, "tool", "buildid", "plugin1.so") 218 if len(b) == 0 { 219 t.Errorf("build id not found") 220 } 221} 222 223func TestRunHost(t *testing.T) { 224 globalSkip(t) 225 run(t, "./host.exe") 226} 227 228func TestUniqueTypesAndItabs(t *testing.T) { 229 globalSkip(t) 230 goCmd(t, "build", "-buildmode=plugin", "./iface_a") 231 goCmd(t, "build", "-buildmode=plugin", "./iface_b") 232 goCmd(t, "build", "-o", "iface.exe", "./iface") 233 run(t, "./iface.exe") 234} 235 236func TestIssue18676(t *testing.T) { 237 // make sure we don't add the same itab twice. 238 // The buggy code hangs forever, so use a timeout to check for that. 239 globalSkip(t) 240 goCmd(t, "build", "-buildmode=plugin", "-o", "plugin.so", "./issue18676/plugin.go") 241 goCmd(t, "build", "-o", "issue18676.exe", "./issue18676/main.go") 242 243 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 244 defer cancel() 245 cmd := exec.CommandContext(ctx, "./issue18676.exe") 246 out, err := cmd.CombinedOutput() 247 if err != nil { 248 t.Fatalf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, out) 249 } 250} 251 252func TestIssue19534(t *testing.T) { 253 // Test that we can load a plugin built in a path with non-alpha characters. 254 globalSkip(t) 255 goCmd(t, "build", "-buildmode=plugin", "-gcflags=-p=issue.19534", "-ldflags=-pluginpath=issue.19534", "-o", "plugin.so", "./issue19534/plugin.go") 256 goCmd(t, "build", "-o", "issue19534.exe", "./issue19534/main.go") 257 run(t, "./issue19534.exe") 258} 259 260func TestIssue18584(t *testing.T) { 261 globalSkip(t) 262 goCmd(t, "build", "-buildmode=plugin", "-o", "plugin.so", "./issue18584/plugin.go") 263 goCmd(t, "build", "-o", "issue18584.exe", "./issue18584/main.go") 264 run(t, "./issue18584.exe") 265} 266 267func TestIssue19418(t *testing.T) { 268 globalSkip(t) 269 goCmd(t, "build", "-buildmode=plugin", "-ldflags=-X main.Val=linkstr", "-o", "plugin.so", "./issue19418/plugin.go") 270 goCmd(t, "build", "-o", "issue19418.exe", "./issue19418/main.go") 271 run(t, "./issue19418.exe") 272} 273 274func TestIssue19529(t *testing.T) { 275 globalSkip(t) 276 goCmd(t, "build", "-buildmode=plugin", "-o", "plugin.so", "./issue19529/plugin.go") 277} 278 279func TestIssue22175(t *testing.T) { 280 globalSkip(t) 281 goCmd(t, "build", "-buildmode=plugin", "-o", "issue22175_plugin1.so", "./issue22175/plugin1.go") 282 goCmd(t, "build", "-buildmode=plugin", "-o", "issue22175_plugin2.so", "./issue22175/plugin2.go") 283 goCmd(t, "build", "-o", "issue22175.exe", "./issue22175/main.go") 284 run(t, "./issue22175.exe") 285} 286 287func TestIssue22295(t *testing.T) { 288 globalSkip(t) 289 goCmd(t, "build", "-buildmode=plugin", "-o", "issue.22295.so", "./issue22295.pkg") 290 goCmd(t, "build", "-o", "issue22295.exe", "./issue22295.pkg/main.go") 291 run(t, "./issue22295.exe") 292} 293 294func TestIssue24351(t *testing.T) { 295 globalSkip(t) 296 goCmd(t, "build", "-buildmode=plugin", "-o", "issue24351.so", "./issue24351/plugin.go") 297 goCmd(t, "build", "-o", "issue24351.exe", "./issue24351/main.go") 298 run(t, "./issue24351.exe") 299} 300 301func TestIssue25756(t *testing.T) { 302 globalSkip(t) 303 goCmd(t, "build", "-buildmode=plugin", "-o", "life.so", "./issue25756/plugin") 304 goCmd(t, "build", "-o", "issue25756.exe", "./issue25756/main.go") 305 // Fails intermittently, but 20 runs should cause the failure 306 for n := 20; n > 0; n-- { 307 t.Run(fmt.Sprint(n), func(t *testing.T) { 308 t.Parallel() 309 run(t, "./issue25756.exe") 310 }) 311 } 312} 313 314// Test with main using -buildmode=pie with plugin for issue #43228 315func TestIssue25756pie(t *testing.T) { 316 globalSkip(t) 317 goCmd(t, "build", "-buildmode=plugin", "-o", "life.so", "./issue25756/plugin") 318 goCmd(t, "build", "-buildmode=pie", "-o", "issue25756pie.exe", "./issue25756/main.go") 319 run(t, "./issue25756pie.exe") 320} 321 322func TestMethod(t *testing.T) { 323 // Exported symbol's method must be live. 324 globalSkip(t) 325 goCmd(t, "build", "-buildmode=plugin", "-o", "plugin.so", "./method/plugin.go") 326 goCmd(t, "build", "-o", "method.exe", "./method/main.go") 327 run(t, "./method.exe") 328} 329 330func TestMethod2(t *testing.T) { 331 globalSkip(t) 332 goCmd(t, "build", "-buildmode=plugin", "-o", "method2.so", "./method2/plugin.go") 333 goCmd(t, "build", "-o", "method2.exe", "./method2/main.go") 334 run(t, "./method2.exe") 335} 336 337func TestMethod3(t *testing.T) { 338 globalSkip(t) 339 goCmd(t, "build", "-buildmode=plugin", "-o", "method3.so", "./method3/plugin.go") 340 goCmd(t, "build", "-o", "method3.exe", "./method3/main.go") 341 run(t, "./method3.exe") 342} 343 344func TestIssue44956(t *testing.T) { 345 globalSkip(t) 346 goCmd(t, "build", "-buildmode=plugin", "-o", "issue44956p1.so", "./issue44956/plugin1.go") 347 goCmd(t, "build", "-buildmode=plugin", "-o", "issue44956p2.so", "./issue44956/plugin2.go") 348 goCmd(t, "build", "-o", "issue44956.exe", "./issue44956/main.go") 349 run(t, "./issue44956.exe") 350} 351 352func TestIssue52937(t *testing.T) { 353 globalSkip(t) 354 goCmd(t, "build", "-buildmode=plugin", "-o", "issue52937.so", "./issue52937/main.go") 355} 356 357func TestIssue53989(t *testing.T) { 358 globalSkip(t) 359 goCmd(t, "build", "-buildmode=plugin", "-o", "issue53989.so", "./issue53989/plugin.go") 360 goCmd(t, "build", "-o", "issue53989.exe", "./issue53989/main.go") 361 run(t, "./issue53989.exe") 362} 363 364func TestForkExec(t *testing.T) { 365 // Issue 38824: importing the plugin package causes it hang in forkExec on darwin. 366 globalSkip(t) 367 368 t.Parallel() 369 goCmd(t, "build", "-o", "forkexec.exe", "./forkexec/main.go") 370 371 for i := 0; i < 100; i++ { 372 cmd := testenv.Command(t, "./forkexec.exe", "1") 373 err := cmd.Run() 374 if err != nil { 375 if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 { 376 t.Logf("stderr:\n%s", ee.Stderr) 377 } 378 t.Errorf("running command failed: %v", err) 379 break 380 } 381 } 382} 383 384func TestSymbolNameMangle(t *testing.T) { 385 // Issue 58800: generic function name may contain weird characters 386 // that confuse the external linker. 387 // Issue 62098: the name mangling code doesn't handle some string 388 // symbols correctly. 389 globalSkip(t) 390 goCmd(t, "build", "-buildmode=plugin", "-o", "mangle.so", "./mangle/plugin.go") 391} 392 393func TestIssue62430(t *testing.T) { 394 globalSkip(t) 395 goCmd(t, "build", "-buildmode=plugin", "-o", "issue62430.so", "./issue62430/plugin.go") 396 goCmd(t, "build", "-o", "issue62430.exe", "./issue62430/main.go") 397 run(t, "./issue62430.exe") 398} 399 400func TestTextSectionSplit(t *testing.T) { 401 globalSkip(t) 402 if runtime.GOOS != "darwin" || runtime.GOARCH != "arm64" { 403 t.Skipf("text section splitting is not done in %s/%s", runtime.GOOS, runtime.GOARCH) 404 } 405 406 // Use -ldflags=-debugtextsize=262144 to let the linker split text section 407 // at a smaller size threshold, so it actually splits for the test binary. 408 goCmd(nil, "build", "-ldflags=-debugtextsize=262144", "-o", "host-split.exe", "./host") 409 run(t, "./host-split.exe") 410 411 // Check that we did split text sections. 412 syms := goCmd(nil, "tool", "nm", "host-split.exe") 413 if !strings.Contains(syms, "runtime.text.1") { 414 t.Errorf("runtime.text.1 not found, text section not split?") 415 } 416} 417 418func TestIssue67976(t *testing.T) { 419 // Issue 67976: build failure with loading a dynimport variable (the runtime/pprof 420 // package does this on darwin) in a plugin on darwin/amd64. 421 // The test program uses runtime/pprof in a plugin. 422 globalSkip(t) 423 goCmd(t, "build", "-buildmode=plugin", "-o", "issue67976.so", "./issue67976/plugin.go") 424} 425