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