1// Copyright 2017 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 archive 6 7import ( 8 "bytes" 9 "debug/elf" 10 "debug/macho" 11 "debug/pe" 12 "fmt" 13 "internal/testenv" 14 "internal/xcoff" 15 "io" 16 "os" 17 "path/filepath" 18 "runtime" 19 "sync" 20 "testing" 21 "unicode/utf8" 22) 23 24var buildDir string 25 26func TestMain(m *testing.M) { 27 if !testenv.HasGoBuild() { 28 return 29 } 30 31 exit := m.Run() 32 33 if buildDir != "" { 34 os.RemoveAll(buildDir) 35 } 36 os.Exit(exit) 37} 38 39func copyDir(dst, src string) error { 40 err := os.MkdirAll(dst, 0777) 41 if err != nil { 42 return err 43 } 44 entries, err := os.ReadDir(src) 45 if err != nil { 46 return err 47 } 48 for _, entry := range entries { 49 err = copyFile(filepath.Join(dst, entry.Name()), filepath.Join(src, entry.Name())) 50 if err != nil { 51 return err 52 } 53 } 54 return nil 55} 56 57func copyFile(dst, src string) (err error) { 58 var s, d *os.File 59 s, err = os.Open(src) 60 if err != nil { 61 return err 62 } 63 defer s.Close() 64 d, err = os.Create(dst) 65 if err != nil { 66 return err 67 } 68 defer func() { 69 e := d.Close() 70 if err == nil { 71 err = e 72 } 73 }() 74 _, err = io.Copy(d, s) 75 if err != nil { 76 return err 77 } 78 return nil 79} 80 81var ( 82 buildOnce sync.Once 83 builtGoobjs goobjPaths 84 buildErr error 85) 86 87type goobjPaths struct { 88 go1obj string 89 go2obj string 90 goarchive string 91 cgoarchive string 92} 93 94func buildGoobj(t *testing.T) goobjPaths { 95 buildOnce.Do(func() { 96 buildErr = func() (err error) { 97 buildDir, err = os.MkdirTemp("", "TestGoobj") 98 if err != nil { 99 return err 100 } 101 102 go1obj := filepath.Join(buildDir, "go1.o") 103 go2obj := filepath.Join(buildDir, "go2.o") 104 goarchive := filepath.Join(buildDir, "go.a") 105 cgoarchive := "" 106 107 gotool, err := testenv.GoTool() 108 if err != nil { 109 return err 110 } 111 112 go1src := filepath.Join("testdata", "go1.go") 113 go2src := filepath.Join("testdata", "go2.go") 114 115 importcfgfile := filepath.Join(buildDir, "importcfg") 116 testenv.WriteImportcfg(t, importcfgfile, nil, go1src, go2src) 117 118 out, err := testenv.Command(t, gotool, "tool", "compile", "-importcfg="+importcfgfile, "-p=p", "-o", go1obj, go1src).CombinedOutput() 119 if err != nil { 120 return fmt.Errorf("go tool compile -o %s %s: %v\n%s", go1obj, go1src, err, out) 121 } 122 out, err = testenv.Command(t, gotool, "tool", "compile", "-importcfg="+importcfgfile, "-p=p", "-o", go2obj, go2src).CombinedOutput() 123 if err != nil { 124 return fmt.Errorf("go tool compile -o %s %s: %v\n%s", go2obj, go2src, err, out) 125 } 126 out, err = testenv.Command(t, gotool, "tool", "pack", "c", goarchive, go1obj, go2obj).CombinedOutput() 127 if err != nil { 128 return fmt.Errorf("go tool pack c %s %s %s: %v\n%s", goarchive, go1obj, go2obj, err, out) 129 } 130 131 if testenv.HasCGO() { 132 cgoarchive = filepath.Join(buildDir, "mycgo.a") 133 gopath := filepath.Join(buildDir, "gopath") 134 err = copyDir(filepath.Join(gopath, "src", "mycgo"), filepath.Join("testdata", "mycgo")) 135 if err == nil { 136 err = os.WriteFile(filepath.Join(gopath, "src", "mycgo", "go.mod"), []byte("module mycgo\n"), 0666) 137 } 138 if err != nil { 139 return err 140 } 141 cmd := testenv.Command(t, gotool, "build", "-buildmode=archive", "-o", cgoarchive, "-gcflags=all="+os.Getenv("GO_GCFLAGS"), "mycgo") 142 cmd.Dir = filepath.Join(gopath, "src", "mycgo") 143 cmd.Env = append(os.Environ(), "GOPATH="+gopath) 144 out, err = cmd.CombinedOutput() 145 if err != nil { 146 return fmt.Errorf("go install mycgo: %v\n%s", err, out) 147 } 148 } 149 150 builtGoobjs = goobjPaths{ 151 go1obj: go1obj, 152 go2obj: go2obj, 153 goarchive: goarchive, 154 cgoarchive: cgoarchive, 155 } 156 return nil 157 }() 158 }) 159 160 if buildErr != nil { 161 t.Helper() 162 t.Fatal(buildErr) 163 } 164 return builtGoobjs 165} 166 167func TestParseGoobj(t *testing.T) { 168 path := buildGoobj(t).go1obj 169 170 f, err := os.Open(path) 171 if err != nil { 172 t.Fatal(err) 173 } 174 defer f.Close() 175 176 a, err := Parse(f, false) 177 if err != nil { 178 t.Fatal(err) 179 } 180 if len(a.Entries) != 2 { 181 t.Errorf("expect 2 entry, found %d", len(a.Entries)) 182 } 183 for _, e := range a.Entries { 184 if e.Type == EntryPkgDef { 185 continue 186 } 187 if e.Type != EntryGoObj { 188 t.Errorf("wrong type of object: want EntryGoObj, got %v", e.Type) 189 } 190 if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) { 191 t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader) 192 } 193 } 194} 195 196func TestParseArchive(t *testing.T) { 197 path := buildGoobj(t).goarchive 198 199 f, err := os.Open(path) 200 if err != nil { 201 t.Fatal(err) 202 } 203 defer f.Close() 204 205 a, err := Parse(f, false) 206 if err != nil { 207 t.Fatal(err) 208 } 209 if len(a.Entries) != 3 { 210 t.Errorf("expect 3 entry, found %d", len(a.Entries)) 211 } 212 var found1 bool 213 var found2 bool 214 for _, e := range a.Entries { 215 if e.Type == EntryPkgDef { 216 continue 217 } 218 if e.Type != EntryGoObj { 219 t.Errorf("wrong type of object: want EntryGoObj, got %v", e.Type) 220 } 221 if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) { 222 t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader) 223 } 224 if e.Name == "go1.o" { 225 found1 = true 226 } 227 if e.Name == "go2.o" { 228 found2 = true 229 } 230 } 231 if !found1 { 232 t.Errorf(`object "go1.o" not found`) 233 } 234 if !found2 { 235 t.Errorf(`object "go2.o" not found`) 236 } 237} 238 239func TestParseCGOArchive(t *testing.T) { 240 testenv.MustHaveCGO(t) 241 242 path := buildGoobj(t).cgoarchive 243 244 f, err := os.Open(path) 245 if err != nil { 246 t.Fatal(err) 247 } 248 defer f.Close() 249 250 a, err := Parse(f, false) 251 if err != nil { 252 t.Fatal(err) 253 } 254 255 c1 := "c1" 256 c2 := "c2" 257 switch runtime.GOOS { 258 case "darwin", "ios": 259 c1 = "_" + c1 260 c2 = "_" + c2 261 case "windows": 262 if runtime.GOARCH == "386" { 263 c1 = "_" + c1 264 c2 = "_" + c2 265 } 266 case "aix": 267 c1 = "." + c1 268 c2 = "." + c2 269 } 270 271 var foundgo, found1, found2 bool 272 273 for _, e := range a.Entries { 274 switch e.Type { 275 default: 276 t.Errorf("unknown object type") 277 case EntryPkgDef: 278 continue 279 case EntryGoObj: 280 foundgo = true 281 if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) { 282 t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader) 283 } 284 continue 285 case EntryNativeObj: 286 } 287 288 obj := io.NewSectionReader(f, e.Offset, e.Size) 289 switch runtime.GOOS { 290 case "darwin", "ios": 291 mf, err := macho.NewFile(obj) 292 if err != nil { 293 t.Fatal(err) 294 } 295 if mf.Symtab == nil { 296 continue 297 } 298 for _, s := range mf.Symtab.Syms { 299 switch s.Name { 300 case c1: 301 found1 = true 302 case c2: 303 found2 = true 304 } 305 } 306 case "windows": 307 pf, err := pe.NewFile(obj) 308 if err != nil { 309 t.Fatal(err) 310 } 311 for _, s := range pf.Symbols { 312 switch s.Name { 313 case c1: 314 found1 = true 315 case c2: 316 found2 = true 317 } 318 } 319 case "aix": 320 xf, err := xcoff.NewFile(obj) 321 if err != nil { 322 t.Fatal(err) 323 } 324 for _, s := range xf.Symbols { 325 switch s.Name { 326 case c1: 327 found1 = true 328 case c2: 329 found2 = true 330 } 331 } 332 default: // ELF 333 ef, err := elf.NewFile(obj) 334 if err != nil { 335 t.Fatal(err) 336 } 337 syms, err := ef.Symbols() 338 if err != nil { 339 t.Fatal(err) 340 } 341 for _, s := range syms { 342 switch s.Name { 343 case c1: 344 found1 = true 345 case c2: 346 found2 = true 347 } 348 } 349 } 350 } 351 352 if !foundgo { 353 t.Errorf(`go object not found`) 354 } 355 if !found1 { 356 t.Errorf(`symbol %q not found`, c1) 357 } 358 if !found2 { 359 t.Errorf(`symbol %q not found`, c2) 360 } 361} 362 363func TestExactly16Bytes(t *testing.T) { 364 var tests = []string{ 365 "", 366 "a", 367 "日本語", 368 "1234567890123456", 369 "12345678901234567890", 370 "1234567890123本語4567890", 371 "12345678901234日本語567890", 372 "123456789012345日本語67890", 373 "1234567890123456日本語7890", 374 "1234567890123456日本語7日本語890", 375 } 376 for _, str := range tests { 377 got := exactly16Bytes(str) 378 if len(got) != 16 { 379 t.Errorf("exactly16Bytes(%q) is %q, length %d", str, got, len(got)) 380 } 381 // Make sure it is full runes. 382 for _, c := range got { 383 if c == utf8.RuneError { 384 t.Errorf("exactly16Bytes(%q) is %q, has partial rune", str, got) 385 } 386 } 387 } 388} 389