| // Copyright 2022 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package ld | |
| import ( | |
| "fmt" | |
| "internal/testenv" | |
| "os" | |
| "regexp" | |
| "runtime" | |
| "strconv" | |
| "testing" | |
| ) | |
| // See also $GOROOT/test/nosplit.go for multi-platform edge case tests. | |
| func TestStackCheckOutput(t *testing.T) { | |
| // Don't build library for non-standard target in short mode. | |
| if testing.Short() && (runtime.GOOS != "linux" || runtime.GOARCH != "amd64") { | |
| t.Skipf("skipping on %s/%s in short mode", runtime.GOOS, runtime.GOARCH) | |
| } | |
| testenv.MustHaveGoBuild(t) | |
| t.Parallel() | |
| cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", os.DevNull, "./testdata/stackcheck") | |
| // The rules for computing frame sizes on all of the | |
| // architectures are complicated, so just do this on amd64. | |
| cmd.Env = append(os.Environ(), "GOARCH=amd64", "GOOS=linux") | |
| outB, err := cmd.CombinedOutput() | |
| if err == nil { | |
| t.Fatalf("expected link to fail") | |
| } | |
| out := string(outB) | |
| t.Logf("linker output:\n%s", out) | |
| // Get expected limit. | |
| limitRe := regexp.MustCompile(`nosplit stack over (\d+) byte limit`) | |
| m := limitRe.FindStringSubmatch(out) | |
| if m == nil { | |
| t.Fatalf("no overflow errors in output") | |
| } | |
| limit, _ := strconv.Atoi(m[1]) | |
| wantMap := map[string]string{ | |
| "main.startSelf": fmt.Sprintf( | |
| `main.startSelf<0> | |
| grows 1008 bytes | |
| %d bytes over limit | |
| `, 1008-limit), | |
| "main.startChain": fmt.Sprintf( | |
| `main.startChain<0> | |
| grows 32 bytes, calls main.chain0<0> | |
| grows 48 bytes, calls main.chainEnd<0> | |
| grows 1008 bytes | |
| %d bytes over limit | |
| grows 32 bytes, calls main.chain2<0> | |
| grows 80 bytes, calls main.chainEnd<0> | |
| grows 1008 bytes | |
| %d bytes over limit | |
| `, 32+48+1008-limit, 32+80+1008-limit), | |
| "main.startRec": `main.startRec<0> | |
| grows 8 bytes, calls main.startRec0<0> | |
| grows 8 bytes, calls main.startRec<0> | |
| infinite cycle | |
| `, | |
| } | |
| // Parse stanzas | |
| stanza := regexp.MustCompile(`^(.*): nosplit stack over \d+ byte limit\n(.*\n(?: .*\n)*)`) | |
| // Strip comments from cmd/go | |
| out = regexp.MustCompile(`(?m)^#.*\n`).ReplaceAllString(out, "") | |
| for len(out) > 0 { | |
| m := stanza.FindStringSubmatch(out) | |
| if m == nil { | |
| t.Fatalf("unexpected output:\n%s", out) | |
| } | |
| out = out[len(m[0]):] | |
| fn := m[1] | |
| got := m[2] | |
| want, ok := wantMap[fn] | |
| if !ok { | |
| t.Errorf("unexpected function: %s", fn) | |
| } else if want != got { | |
| t.Errorf("want:\n%sgot:\n%s", want, got) | |
| } | |
| } | |
| } |