blob: 8a8f18c811108d5be5fe8eefc62f431a5613c94a [file] [log] [blame] [edit]
// 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))
}