1// Copyright 2018 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package ssa_test 6 7import ( 8 cmddwarf "cmd/internal/dwarf" 9 "cmd/internal/quoted" 10 "debug/dwarf" 11 "debug/elf" 12 "debug/macho" 13 "debug/pe" 14 "fmt" 15 "internal/platform" 16 "internal/testenv" 17 "internal/xcoff" 18 "io" 19 "os" 20 "runtime" 21 "sort" 22 "testing" 23) 24 25func open(path string) (*dwarf.Data, error) { 26 if fh, err := elf.Open(path); err == nil { 27 return fh.DWARF() 28 } 29 30 if fh, err := pe.Open(path); err == nil { 31 return fh.DWARF() 32 } 33 34 if fh, err := macho.Open(path); err == nil { 35 return fh.DWARF() 36 } 37 38 if fh, err := xcoff.Open(path); err == nil { 39 return fh.DWARF() 40 } 41 42 return nil, fmt.Errorf("unrecognized executable format") 43} 44 45func must(err error) { 46 if err != nil { 47 panic(err) 48 } 49} 50 51type Line struct { 52 File string 53 Line int 54} 55 56func TestStmtLines(t *testing.T) { 57 if !platform.ExecutableHasDWARF(runtime.GOOS, runtime.GOARCH) { 58 t.Skipf("skipping on %s/%s: no DWARF symbol table in executables", runtime.GOOS, runtime.GOARCH) 59 } 60 61 if runtime.GOOS == "aix" { 62 extld := os.Getenv("CC") 63 if extld == "" { 64 extld = "gcc" 65 } 66 extldArgs, err := quoted.Split(extld) 67 if err != nil { 68 t.Fatal(err) 69 } 70 enabled, err := cmddwarf.IsDWARFEnabledOnAIXLd(extldArgs) 71 if err != nil { 72 t.Fatal(err) 73 } 74 if !enabled { 75 t.Skip("skipping on aix: no DWARF with ld version < 7.2.2 ") 76 } 77 } 78 79 // Build cmd/go forcing DWARF enabled, as a large test case. 80 dir := t.TempDir() 81 out, err := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-w=0", "-o", dir+"/test.exe", "cmd/go").CombinedOutput() 82 if err != nil { 83 t.Fatalf("go build: %v\n%s", err, out) 84 } 85 86 lines := map[Line]bool{} 87 dw, err := open(dir + "/test.exe") 88 must(err) 89 rdr := dw.Reader() 90 rdr.Seek(0) 91 for { 92 e, err := rdr.Next() 93 must(err) 94 if e == nil { 95 break 96 } 97 if e.Tag != dwarf.TagCompileUnit { 98 continue 99 } 100 pkgname, _ := e.Val(dwarf.AttrName).(string) 101 if pkgname == "runtime" { 102 continue 103 } 104 if pkgname == "crypto/internal/nistec/fiat" { 105 continue // golang.org/issue/49372 106 } 107 if e.Val(dwarf.AttrStmtList) == nil { 108 continue 109 } 110 lrdr, err := dw.LineReader(e) 111 must(err) 112 113 var le dwarf.LineEntry 114 115 for { 116 err := lrdr.Next(&le) 117 if err == io.EOF { 118 break 119 } 120 must(err) 121 fl := Line{le.File.Name, le.Line} 122 lines[fl] = lines[fl] || le.IsStmt 123 } 124 } 125 126 nonStmtLines := []Line{} 127 for line, isstmt := range lines { 128 if !isstmt { 129 nonStmtLines = append(nonStmtLines, line) 130 } 131 } 132 133 var m int 134 if runtime.GOARCH == "amd64" { 135 m = 1 // > 99% obtained on amd64, no backsliding 136 } else if runtime.GOARCH == "riscv64" { 137 m = 3 // XXX temporary update threshold to 97% for regabi 138 } else { 139 m = 2 // expect 98% elsewhere. 140 } 141 142 if len(nonStmtLines)*100 > m*len(lines) { 143 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)) 144 } 145 t.Logf("Saw %d out of %d lines without statement marks", len(nonStmtLines), len(lines)) 146 if testing.Verbose() { 147 sort.Slice(nonStmtLines, func(i, j int) bool { 148 if nonStmtLines[i].File != nonStmtLines[j].File { 149 return nonStmtLines[i].File < nonStmtLines[j].File 150 } 151 return nonStmtLines[i].Line < nonStmtLines[j].Line 152 }) 153 for _, l := range nonStmtLines { 154 t.Logf("%s:%d has no DWARF is_stmt mark\n", l.File, l.Line) 155 } 156 } 157 t.Logf("total=%d, nostmt=%d\n", len(lines), len(nonStmtLines)) 158} 159