• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package strip_test
16
17import (
18	"bytes"
19	"errors"
20	"fmt"
21	"os/exec"
22	"strings"
23	"testing"
24
25	"github.com/bazelbuild/rules_go/go/tools/bazel_testing"
26)
27
28func TestMain(m *testing.M) {
29	bazel_testing.TestMain(m, bazel_testing.Args{
30		Main: `
31-- BUILD.bazel --
32load("@io_bazel_rules_go//go:def.bzl", "go_binary")
33
34go_binary(
35    name = "strip",
36    srcs = ["strip.go"],
37)
38-- strip.go --
39package main
40
41import (
42	"debug/elf"
43	"debug/macho"
44	"debug/pe"
45	"errors"
46	"flag"
47	"fmt"
48	"io"
49	"os"
50	"regexp"
51	"runtime"
52	"runtime/debug"
53	"strings"
54)
55
56var wantStrip = flag.Bool("wantstrip", false, "")
57
58func main() {
59	flag.Parse()
60	stackTrace, err := panicAndRecover()
61	if err != nil {
62		panic(err)
63	}
64	gotStackTrace := strings.Split(stackTrace, "\n")
65	if len(gotStackTrace) != len(wantStackTrace) {
66		panic(fmt.Sprintf("got %d lines of stack trace, want %d", len(gotStackTrace), len(wantStackTrace)))
67	}
68	for i := range gotStackTrace {
69		expectedLine := regexp.MustCompile(wantStackTrace[i])
70		if !expectedLine.MatchString(gotStackTrace[i]) {
71			panic(fmt.Sprintf("got unexpected stack trace line %q at index %d", gotStackTrace[i], i))
72		}
73	}
74	stripped, err := isStripped()
75	if err != nil {
76		panic(err)
77	}
78	if stripped != *wantStrip {
79		panic(fmt.Sprintf("got stripped=%t, want stripped=%t", stripped, *wantStrip))
80	}
81}
82
83func panicAndRecover() (stackTrace string, err error) {
84	defer func() {
85		if r := recover(); r != nil {
86			stackTrace = string(debug.Stack())
87		}
88	}()
89	panic("test")
90	return "", errors.New("should not reach here")
91}
92
93func isStripped() (bool, error) {
94	ownLocation, err := os.Executable()
95	if err != nil {
96		return false, err
97	}
98	ownBinary, err := os.Open(ownLocation)
99	if err != nil {
100		return false, err
101	}
102	defer ownBinary.Close()
103	switch runtime.GOOS {
104	case "darwin":
105		return isStrippedMachO(ownBinary)
106	case "linux":
107		return isStrippedElf(ownBinary)
108	case "windows":
109		return isStrippedPE(ownBinary)
110	default:
111		return false, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
112	}
113}
114
115func isStrippedMachO(f io.ReaderAt) (bool, error) {
116	macho, err := macho.NewFile(f)
117	if err != nil {
118		return false, err
119	}
120	gotDwarf := macho.Segment("__DWARF") != nil
121	gotDebugInfo := macho.Section("__zdebug_info") != nil
122	if gotDwarf != gotDebugInfo {
123		return false, fmt.Errorf("inconsistent stripping: gotDwarf=%v, gotDebugInfo=%v", gotDwarf, gotDebugInfo)
124	}
125	return !gotDwarf, nil
126}
127
128func isStrippedElf(f io.ReaderAt) (bool, error) {
129	elf, err := elf.NewFile(f)
130	if err != nil {
131		return false, err
132	}
133	var gotSymtab bool
134	for _, section := range elf.Sections {
135		if section.Name == ".symtab" {
136			gotSymtab = true
137			break
138		}
139	}
140	return !gotSymtab, nil
141}
142
143
144func isStrippedPE(f io.ReaderAt) (bool, error) {
145	pe, err := pe.NewFile(f)
146	if err != nil {
147		return false, err
148	}
149	symtab := pe.Section(".symtab")
150	if symtab == nil {
151		return false, fmt.Errorf("no .symtab section")
152	}
153	emptySymtab := (symtab.VirtualSize <= 4) && (symtab.Size <= 512)
154	return emptySymtab, nil
155}
156
157
158` + embedWantedStackTraces(),
159	})
160}
161
162func Test(t *testing.T) {
163	for _, test := range []struct {
164		desc, stripFlag, compilationMode string
165		wantStrip                        bool
166	}{
167		{
168			desc:      "run_auto",
169			wantStrip: true,
170		},
171		{
172			desc:            "run_fastbuild",
173			compilationMode: "fastbuild",
174			wantStrip:       true,
175		},
176		{
177			desc:            "run_dbg",
178			compilationMode: "dbg",
179		},
180		{
181			desc:            "run_opt",
182			compilationMode: "opt",
183		},
184		{
185			desc:      "run_always",
186			stripFlag: "always",
187			wantStrip: true,
188		},
189		{
190			desc:            "run_always_opt",
191			stripFlag:       "always",
192			compilationMode: "opt",
193			wantStrip:       true,
194		},
195		{
196			desc:      "run_never",
197			stripFlag: "never",
198		},
199		{
200			desc:            "run_sometimes_fastbuild",
201			stripFlag:       "sometimes",
202			compilationMode: "fastbuild",
203			wantStrip:       true,
204		},
205		{
206			desc:            "run_sometimes_dbg",
207			stripFlag:       "sometimes",
208			compilationMode: "dbg",
209		},
210		{
211			desc:            "run_sometimes_opt",
212			stripFlag:       "sometimes",
213			compilationMode: "opt",
214		},
215	} {
216		t.Run(test.desc, func(t *testing.T) {
217			args := []string{"run"}
218			if len(test.stripFlag) > 0 {
219				args = append(args, "--strip", test.stripFlag)
220			}
221			if len(test.compilationMode) > 0 {
222				args = append(args, "--compilation_mode", test.compilationMode)
223			}
224			args = append(args, "//:strip", "--", fmt.Sprintf("-wantstrip=%v", test.wantStrip))
225			cmd := bazel_testing.BazelCmd(args...)
226			stderr := &bytes.Buffer{}
227			cmd.Stderr = stderr
228			t.Logf("running: bazel %s", strings.Join(args, " "))
229			if err := cmd.Run(); err != nil {
230				var xerr *exec.ExitError
231				if !errors.As(err, &xerr) {
232					t.Fatalf("unexpected error: %v", err)
233				}
234				if xerr.ExitCode() == bazel_testing.BUILD_FAILURE {
235					t.Fatalf("unexpected build failure: %v\nstderr:\n%s", err, stderr.Bytes())
236					return
237				} else if xerr.ExitCode() == bazel_testing.TESTS_FAILED {
238					t.Fatalf("error running %s:\n%s", strings.Join(cmd.Args, " "), stderr.Bytes())
239				} else {
240					t.Fatalf("unexpected error: %v\nstderr:\n%s", err, stderr.Bytes())
241				}
242			}
243		})
244	}
245}
246
247var wantStackTrace = []string{
248	`^goroutine \d+ \[running\]:$`,
249	`^runtime/debug\.Stack\(\)$`,
250	`^	GOROOT/src/runtime/debug/stack\.go:\d+ \+0x[0-9a-f]+$`,
251	`^main\.panicAndRecover\.func1\(\)$`,
252	`^	strip\.go:\d+ \+0x[0-9a-f]+$`,
253	`^panic\({0x[0-9a-f]+, 0x[0-9a-f]+}\)$`,
254	`^	GOROOT/src/runtime/panic\.go:\d+ \+0x[0-9a-f]+$`,
255	`^main\.panicAndRecover\(\)$`,
256	`^	strip\.go:\d+ \+0x[0-9a-f]+$`,
257	`^main\.main\(\)$`,
258	`^	strip\.go:\d+ \+0x[0-9a-f]+$`,
259	`^$`,
260}
261
262func embedWantedStackTraces() string {
263	buf := &bytes.Buffer{}
264	fmt.Fprintln(buf, "var wantStackTrace = []string{")
265	for _, s := range wantStackTrace {
266		fmt.Fprintf(buf, "`%s`,\n", s)
267	}
268	fmt.Fprintln(buf, "}")
269	return buf.String()
270}
271