| // Copyright 2018 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 ssa_test |
| |
| import ( |
| cmddwarf "cmd/internal/dwarf" |
| "cmd/internal/quoted" |
| "cmp" |
| "debug/dwarf" |
| "debug/elf" |
| "debug/macho" |
| "debug/pe" |
| "fmt" |
| "internal/platform" |
| "internal/testenv" |
| "internal/xcoff" |
| "io" |
| "os" |
| "runtime" |
| "slices" |
| "strings" |
| "testing" |
| ) |
| |
| func open(path string) (*dwarf.Data, error) { |
| if fh, err := elf.Open(path); err == nil { |
| return fh.DWARF() |
| } |
| |
| if fh, err := pe.Open(path); err == nil { |
| return fh.DWARF() |
| } |
| |
| if fh, err := macho.Open(path); err == nil { |
| return fh.DWARF() |
| } |
| |
| if fh, err := xcoff.Open(path); err == nil { |
| return fh.DWARF() |
| } |
| |
| return nil, fmt.Errorf("unrecognized executable format") |
| } |
| |
| func must(err error) { |
| if err != nil { |
| panic(err) |
| } |
| } |
| |
| type Line struct { |
| File string |
| Line int |
| } |
| |
| func TestStmtLines(t *testing.T) { |
| if !platform.ExecutableHasDWARF(runtime.GOOS, runtime.GOARCH) { |
| t.Skipf("skipping on %s/%s: no DWARF symbol table in executables", runtime.GOOS, runtime.GOARCH) |
| } |
| |
| if runtime.GOOS == "aix" { |
| extld := os.Getenv("CC") |
| if extld == "" { |
| extld = "gcc" |
| } |
| extldArgs, err := quoted.Split(extld) |
| if err != nil { |
| t.Fatal(err) |
| } |
| enabled, err := cmddwarf.IsDWARFEnabledOnAIXLd(extldArgs) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !enabled { |
| t.Skip("skipping on aix: no DWARF with ld version < 7.2.2 ") |
| } |
| } |
| |
| // Build cmd/go forcing DWARF enabled, as a large test case. |
| dir := t.TempDir() |
| out, err := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-w=0", "-o", dir+"/test.exe", "cmd/go").CombinedOutput() |
| if err != nil { |
| t.Fatalf("go build: %v\n%s", err, out) |
| } |
| |
| lines := map[Line]bool{} |
| dw, err := open(dir + "/test.exe") |
| must(err) |
| rdr := dw.Reader() |
| rdr.Seek(0) |
| for { |
| e, err := rdr.Next() |
| must(err) |
| if e == nil { |
| break |
| } |
| if e.Tag != dwarf.TagCompileUnit { |
| continue |
| } |
| pkgname, _ := e.Val(dwarf.AttrName).(string) |
| if pkgname == "runtime" { |
| continue |
| } |
| if pkgname == "crypto/internal/nistec/fiat" { |
| continue // golang.org/issue/49372 |
| } |
| if e.Val(dwarf.AttrStmtList) == nil { |
| continue |
| } |
| lrdr, err := dw.LineReader(e) |
| must(err) |
| |
| var le dwarf.LineEntry |
| |
| for { |
| err := lrdr.Next(&le) |
| if err == io.EOF { |
| break |
| } |
| must(err) |
| fl := Line{le.File.Name, le.Line} |
| lines[fl] = lines[fl] || le.IsStmt |
| } |
| } |
| |
| nonStmtLines := []Line{} |
| for line, isstmt := range lines { |
| if !isstmt { |
| nonStmtLines = append(nonStmtLines, line) |
| } |
| } |
| |
| var m int |
| if runtime.GOARCH == "amd64" { |
| m = 1 // > 99% obtained on amd64, no backsliding |
| } else if runtime.GOARCH == "riscv64" { |
| m = 3 // XXX temporary update threshold to 97% for regabi |
| } else { |
| m = 2 // expect 98% elsewhere. |
| } |
| |
| if len(nonStmtLines)*100 > m*len(lines) { |
| t.Errorf("Saw too many (%s, > %d%%) lines without statement marks, total=%d, nostmt=%d ('-run TestStmtLines -v' lists failing lines)\n", runtime.GOARCH, m, len(lines), len(nonStmtLines)) |
| } |
| t.Logf("Saw %d out of %d lines without statement marks", len(nonStmtLines), len(lines)) |
| if testing.Verbose() { |
| slices.SortFunc(nonStmtLines, func(a, b Line) int { |
| if a.File != b.File { |
| return strings.Compare(a.File, b.File) |
| } |
| return cmp.Compare(a.Line, b.Line) |
| }) |
| for _, l := range nonStmtLines { |
| t.Logf("%s:%d has no DWARF is_stmt mark\n", l.File, l.Line) |
| } |
| } |
| t.Logf("total=%d, nostmt=%d\n", len(lines), len(nonStmtLines)) |
| } |