| | |
| | |
| | |
| |
|
| | package ssa_test |
| |
|
| | import ( |
| | "bufio" |
| | "bytes" |
| | "cmp" |
| | "flag" |
| | "fmt" |
| | "internal/testenv" |
| | "os" |
| | "path/filepath" |
| | "reflect" |
| | "regexp" |
| | "runtime" |
| | "slices" |
| | "strconv" |
| | "strings" |
| | "testing" |
| | ) |
| |
|
| | |
| | var asmLine *regexp.Regexp = regexp.MustCompile(`^\s[vb]\d+\s+\d+\s\(\+(\d+)\)`) |
| |
|
| | |
| |
|
| | |
| | |
| | var sepRE = regexp.QuoteMeta(string(filepath.Separator)) |
| | var inlineLine *regexp.Regexp = regexp.MustCompile(`^#\s.*` + sepRE + `[-\w]+\.go:(\d+)`) |
| |
|
| | |
| |
|
| | var testGoArchFlag = flag.String("arch", "", "run test for specified architecture") |
| |
|
| | func testGoArch() string { |
| | if *testGoArchFlag == "" { |
| | return runtime.GOARCH |
| | } |
| | return *testGoArchFlag |
| | } |
| |
|
| | func hasRegisterABI() bool { |
| | switch testGoArch() { |
| | case "amd64", "arm64", "loong64", "ppc64", "ppc64le", "riscv", "s390x": |
| | return true |
| | } |
| | return false |
| | } |
| |
|
| | func unixOnly(t *testing.T) { |
| | if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { |
| | t.Skip("this test depends on creating a file with a wonky name, only works for sure on Linux and Darwin") |
| | } |
| | } |
| |
|
| | |
| | func testDebugLinesDefault(t *testing.T, gcflags, file, function string, wantStmts []int, ignoreRepeats bool) { |
| | unixOnly(t) |
| | if !hasRegisterABI() { |
| | wantStmts = wantStmts[1:] |
| | } |
| | testDebugLines(t, gcflags, file, function, wantStmts, ignoreRepeats) |
| | } |
| |
|
| | func TestDebugLinesSayHi(t *testing.T) { |
| | |
| | |
| | |
| | |
| |
|
| | testDebugLinesDefault(t, "-N -l", "sayhi.go", "sayhi", []int{8, 9, 10, 11}, false) |
| | } |
| |
|
| | func TestDebugLinesPushback(t *testing.T) { |
| | unixOnly(t) |
| |
|
| | switch testGoArch() { |
| | default: |
| | t.Skip("skipped for many architectures") |
| |
|
| | case "arm64", "amd64", "loong64": |
| | fn := "(*List[go.shape.int]).PushBack" |
| | testDebugLines(t, "-N -l", "pushback.go", fn, []int{17, 18, 19, 20, 21, 22, 24}, true) |
| | } |
| | } |
| |
|
| | func TestDebugLinesConvert(t *testing.T) { |
| | unixOnly(t) |
| |
|
| | switch testGoArch() { |
| | default: |
| | t.Skip("skipped for many architectures") |
| |
|
| | case "arm64", "amd64", "loong64": |
| | fn := "G[go.shape.int]" |
| | testDebugLines(t, "-N -l", "convertline.go", fn, []int{9, 10, 11}, true) |
| | } |
| | } |
| |
|
| | func TestInlineLines(t *testing.T) { |
| | if runtime.GOARCH != "amd64" && *testGoArchFlag == "" { |
| | |
| | t.Skip("only runs for amd64 unless -arch explicitly supplied") |
| | } |
| |
|
| | want := [][]int{{3}, {4, 10}, {4, 10, 16}, {4, 10}, {4, 11, 16}, {4, 11}, {4}, {5, 10}, {5, 10, 16}, {5, 10}, {5, 11, 16}, {5, 11}, {5}} |
| | testInlineStack(t, "inline-dump.go", "f", want) |
| | } |
| |
|
| | func TestDebugLines_53456(t *testing.T) { |
| | testDebugLinesDefault(t, "-N -l", "b53456.go", "(*T).Inc", []int{15, 16, 17, 18}, true) |
| | } |
| |
|
| | func TestDebugLines_74576(t *testing.T) { |
| | unixOnly(t) |
| |
|
| | switch testGoArch() { |
| | default: |
| | |
| | |
| | t.Skip("skipped for many architectures") |
| |
|
| | case "arm64", "amd64", "loong64": |
| | tests := []struct { |
| | file string |
| | wantStmts []int |
| | }{ |
| | {"i74576a.go", []int{12, 13, 13, 14}}, |
| | {"i74576b.go", []int{12, 13, 13, 14}}, |
| | {"i74576c.go", []int{12, 13, 13, 14}}, |
| | } |
| | t.Parallel() |
| | for _, test := range tests { |
| | t.Run(test.file, func(t *testing.T) { |
| | t.Parallel() |
| | testDebugLines(t, "-N -l", test.file, "main", test.wantStmts, false) |
| | }) |
| | } |
| | } |
| | } |
| |
|
| | func compileAndDump(t *testing.T, file, function, moreGCFlags string) []byte { |
| | testenv.MustHaveGoBuild(t) |
| |
|
| | tmpdir, err := os.MkdirTemp("", "debug_lines_test") |
| | if err != nil { |
| | panic(fmt.Sprintf("Problem creating TempDir, error %v", err)) |
| | } |
| | if testing.Verbose() { |
| | fmt.Printf("Preserving temporary directory %s\n", tmpdir) |
| | } else { |
| | defer os.RemoveAll(tmpdir) |
| | } |
| |
|
| | source, err := filepath.Abs(filepath.Join("testdata", file)) |
| | if err != nil { |
| | panic(fmt.Sprintf("Could not get abspath of testdata directory and file, %v", err)) |
| | } |
| |
|
| | cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "foo.o", "-gcflags=-d=ssa/genssa/dump="+function+" "+moreGCFlags, source) |
| | cmd.Dir = tmpdir |
| | cmd.Env = replaceEnv(cmd.Env, "GOSSADIR", tmpdir) |
| | testGoos := "linux" |
| | if testGoArch() == "wasm" { |
| | testGoos = "js" |
| | } |
| | cmd.Env = replaceEnv(cmd.Env, "GOOS", testGoos) |
| | cmd.Env = replaceEnv(cmd.Env, "GOARCH", testGoArch()) |
| |
|
| | if testing.Verbose() { |
| | fmt.Printf("About to run %s\n", asCommandLine("", cmd)) |
| | } |
| |
|
| | var stdout, stderr strings.Builder |
| | cmd.Stdout = &stdout |
| | cmd.Stderr = &stderr |
| |
|
| | if err := cmd.Run(); err != nil { |
| | t.Fatalf("error running cmd %s: %v\nstdout:\n%sstderr:\n%s\n", asCommandLine("", cmd), err, stdout.String(), stderr.String()) |
| | } |
| |
|
| | if s := stderr.String(); s != "" { |
| | t.Fatalf("Wanted empty stderr, instead got:\n%s\n", s) |
| | } |
| |
|
| | dumpFile := filepath.Join(tmpdir, function+"_01__genssa.dump") |
| | dumpBytes, err := os.ReadFile(dumpFile) |
| | if err != nil { |
| | t.Fatalf("Could not read dump file %s, err=%v", dumpFile, err) |
| | } |
| | return dumpBytes |
| | } |
| |
|
| | func sortInlineStacks(x [][]int) { |
| | slices.SortFunc(x, func(a, b []int) int { |
| | if len(a) != len(b) { |
| | return cmp.Compare(len(a), len(b)) |
| | } |
| | for k := range a { |
| | if a[k] != b[k] { |
| | return cmp.Compare(a[k], b[k]) |
| | } |
| | } |
| | return 0 |
| | }) |
| | } |
| |
|
| | |
| | func testInlineStack(t *testing.T, file, function string, wantStacks [][]int) { |
| | |
| | dumpBytes := compileAndDump(t, file, function, "-N") |
| | dump := bufio.NewScanner(bytes.NewReader(dumpBytes)) |
| | dumpLineNum := 0 |
| | var gotStmts []int |
| | var gotStacks [][]int |
| | for dump.Scan() { |
| | line := dump.Text() |
| | dumpLineNum++ |
| | matches := inlineLine.FindStringSubmatch(line) |
| | if len(matches) == 2 { |
| | stmt, err := strconv.ParseInt(matches[1], 10, 32) |
| | if err != nil { |
| | t.Fatalf("Expected to parse a line number but saw %s instead on dump line #%d, error %v", matches[1], dumpLineNum, err) |
| | } |
| | if testing.Verbose() { |
| | fmt.Printf("Saw stmt# %d for submatch '%s' on dump line #%d = '%s'\n", stmt, matches[1], dumpLineNum, line) |
| | } |
| | gotStmts = append(gotStmts, int(stmt)) |
| | } else if len(gotStmts) > 0 { |
| | gotStacks = append(gotStacks, gotStmts) |
| | gotStmts = nil |
| | } |
| | } |
| | if len(gotStmts) > 0 { |
| | gotStacks = append(gotStacks, gotStmts) |
| | gotStmts = nil |
| | } |
| | sortInlineStacks(gotStacks) |
| | sortInlineStacks(wantStacks) |
| | if !reflect.DeepEqual(wantStacks, gotStacks) { |
| | t.Errorf("wanted inlines %+v but got %+v\n%s", wantStacks, gotStacks, dumpBytes) |
| | } |
| |
|
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func testDebugLines(t *testing.T, gcflags, file, function string, wantStmts []int, ignoreRepeats bool) { |
| | dumpBytes := compileAndDump(t, file, function, gcflags) |
| | dump := bufio.NewScanner(bytes.NewReader(dumpBytes)) |
| | var gotStmts []int |
| | dumpLineNum := 0 |
| | for dump.Scan() { |
| | line := dump.Text() |
| | dumpLineNum++ |
| | matches := asmLine.FindStringSubmatch(line) |
| | if len(matches) == 2 { |
| | stmt, err := strconv.ParseInt(matches[1], 10, 32) |
| | if err != nil { |
| | t.Fatalf("Expected to parse a line number but saw %s instead on dump line #%d, error %v", matches[1], dumpLineNum, err) |
| | } |
| | if testing.Verbose() { |
| | fmt.Printf("Saw stmt# %d for submatch '%s' on dump line #%d = '%s'\n", stmt, matches[1], dumpLineNum, line) |
| | } |
| | gotStmts = append(gotStmts, int(stmt)) |
| | } |
| | } |
| | if ignoreRepeats { |
| | newGotStmts := []int{gotStmts[0]} |
| | for _, x := range gotStmts { |
| | if x != newGotStmts[len(newGotStmts)-1] { |
| | newGotStmts = append(newGotStmts, x) |
| | } |
| | } |
| | if !reflect.DeepEqual(wantStmts, newGotStmts) { |
| | t.Errorf("wanted stmts %v but got %v (with repeats still in: %v)", wantStmts, newGotStmts, gotStmts) |
| | } |
| |
|
| | } else { |
| | if !reflect.DeepEqual(wantStmts, gotStmts) { |
| | t.Errorf("wanted stmts %v but got %v", wantStmts, gotStmts) |
| | } |
| | } |
| | } |
| |
|