import (
"bytes"
- "cmd/internal/notsha256"
"cmd/internal/obj"
"cmd/internal/src"
"fmt"
"sync"
)
-type writeSyncer interface {
- io.Writer
- Sync() error
-}
-
type hashAndMask struct {
// a hash h matches if (h^hash)&mask == 0
hash uint64
name string // base name of the flag/variable.
// what file (if any) receives the yes/no logging?
// default is os.Stdout
- logfile writeSyncer
+ logfile io.Writer
posTmp []src.Pos
bytesTmp bytes.Buffer
matches []hashAndMask // A hash matches if one of these matches.
var FmaHash *HashDebug // for debugging fused-multiply-add floating point changes
var LoopVarHash *HashDebug // for debugging shared/private loop variable changes
-// DebugHashMatch reports whether debug variable Gossahash
+// DebugHashMatchPkgFunc reports whether debug variable Gossahash
//
// 1. is empty (returns true; this is a special more-quickly implemented case of 4 below)
//
//
// Otherwise it returns false.
//
-// Unless Flags.Gossahash is empty, when DebugHashMatch returns true the message
+// Unless Flags.Gossahash is empty, when DebugHashMatchPkgFunc returns true the message
//
// "%s triggered %s\n", varname, pkgAndName
//
//
// 6. gossahash should return a single function whose miscompilation
// causes the problem, and you can focus on that.
-func DebugHashMatch(pkgAndName string) bool {
- return hashDebug.DebugHashMatch(pkgAndName)
+func DebugHashMatchPkgFunc(pkg, fn string) bool {
+ return hashDebug.MatchPkgFunc(pkg, fn)
}
func DebugHashMatchPos(pos src.XPos) bool {
- return hashDebug.DebugHashMatchPos(pos)
+ return hashDebug.MatchPos(pos)
}
// HasDebugHash returns true if Flags.Gossahash is non-empty, which
return hashDebug != nil
}
+// TODO: Delete when we switch to bisect-only.
func toHashAndMask(s, varname string) hashAndMask {
l := len(s)
if l > 64 {
// NewHashDebug returns a new hash-debug tester for the
// environment variable ev. If ev is not set, it returns
// nil, allowing a lightweight check for normal-case behavior.
-func NewHashDebug(ev, s string, file writeSyncer) *HashDebug {
+func NewHashDebug(ev, s string, file io.Writer) *HashDebug {
if s == "" {
return nil
}
hd.bisect = m
return hd
}
+
+ // TODO: Delete remainder of function when we switch to bisect-only.
ss := strings.Split(s, "/")
// first remove any leading exclusions; these are preceded with "-"
i := 0
}
-func hashOf(pkgAndName string, param uint64) uint64 {
- return hashOfBytes([]byte(pkgAndName), param)
-}
-
-func hashOfBytes(sbytes []byte, param uint64) uint64 {
- hbytes := notsha256.Sum256(sbytes)
- hash := uint64(hbytes[7])<<56 + uint64(hbytes[6])<<48 +
- uint64(hbytes[5])<<40 + uint64(hbytes[4])<<32 +
- uint64(hbytes[3])<<24 + uint64(hbytes[2])<<16 +
- uint64(hbytes[1])<<8 + uint64(hbytes[0])
-
- if param != 0 {
- // Because param is probably a line number, probably near zero,
- // hash it up a little bit, but even so only the lower-order bits
- // likely matter because search focuses on those.
- p0 := param + uint64(hbytes[9]) + uint64(hbytes[10])<<8 +
- uint64(hbytes[11])<<16 + uint64(hbytes[12])<<24
-
- p1 := param + uint64(hbytes[13]) + uint64(hbytes[14])<<8 +
- uint64(hbytes[15])<<16 + uint64(hbytes[16])<<24
-
- param += p0 * p1
- param ^= param>>17 ^ param<<47
- }
-
- return hash ^ param
-}
-
-// DebugHashMatch returns true if either the variable used to create d is
-// unset, or if its value is y, or if it is a suffix of the base-two
-// representation of the hash of pkgAndName. If the variable is not nil,
-// then a true result is accompanied by stylized output to d.logfile, which
-// is used for automated bug search.
-func (d *HashDebug) DebugHashMatch(pkgAndName string) bool {
- return d.DebugHashMatchParam(pkgAndName, 0)
-}
-
+// TODO: Delete when we switch to bisect-only.
func (d *HashDebug) excluded(hash uint64) bool {
for _, m := range d.excludes {
if (m.hash^hash)&m.mask == 0 {
return false
}
+// TODO: Delete when we switch to bisect-only.
func hashString(hash uint64) string {
hstr := ""
if hash == 0 {
hstr = string('0'+byte(hash&1)) + hstr
}
}
+ if len(hstr) > 24 {
+ hstr = hstr[len(hstr)-24:]
+ }
return hstr
}
+// TODO: Delete when we switch to bisect-only.
func (d *HashDebug) match(hash uint64) *hashAndMask {
for i, m := range d.matches {
if (m.hash^hash)&m.mask == 0 {
return nil
}
-// DebugHashMatchParam returns true if either the variable used to create d is
+// MatchPkgFunc returns true if either the variable used to create d is
// unset, or if its value is y, or if it is a suffix of the base-two
-// representation of the hash of pkgAndName and param. If the variable is not
-// nil, then a true result is accompanied by stylized output to d.logfile,
-// which is used for automated bug search.
-func (d *HashDebug) DebugHashMatchParam(pkgAndName string, param uint64) bool {
+// representation of the hash of pkg and fn. If the variable is not nil,
+// then a true result is accompanied by stylized output to d.logfile, which
+// is used for automated bug search.
+func (d *HashDebug) MatchPkgFunc(pkg, fn string) bool {
if d == nil {
return true
}
+ // Written this way to make inlining likely.
+ return d.matchPkgFunc(pkg, fn)
+}
- hash := hashOf(pkgAndName, param)
+func (d *HashDebug) matchPkgFunc(pkg, fn string) bool {
+ hash := bisect.Hash(pkg, fn)
if d.bisect != nil {
if d.bisect.ShouldPrint(hash) {
- d.logDebugHashMatch(d.name, pkgAndName, hash, param)
+ d.log(d.name, hash, pkg+"."+fn)
}
return d.bisect.ShouldEnable(hash)
}
+
+ // TODO: Delete rest of function body when we switch to bisect-only.
if m := d.match(hash); m != nil {
- d.logDebugHashMatch(m.name, pkgAndName, hash, param)
+ d.log(m.name, hash, pkg+"."+fn)
return true
}
return false
}
-// DebugHashMatchPos is similar to DebugHashMatchParam, but for hash computation
+// MatchPos is similar to MatchPkgFunc, but for hash computation
// it uses the source position including all inlining information instead of
-// package name and path. The mutex locking is more frequent and more granular.
+// package name and path.
// Note that the default answer for no environment variable (d == nil)
// is "yes", do the thing.
-func (d *HashDebug) DebugHashMatchPos(pos src.XPos) bool {
+func (d *HashDebug) MatchPos(pos src.XPos) bool {
if d == nil {
return true
}
// Written this way to make inlining likely.
- return d.debugHashMatchPos(Ctxt, pos)
+ return d.matchPos(Ctxt, pos)
}
-func (d *HashDebug) debugHashMatchPos(ctxt *obj.Link, pos src.XPos) bool {
- // TODO: When we remove the old d.match code, we can use
- // d.bisect.Hash instead of the locked buffer, and we can
- // use d.bisect.MarkerOnly to decide whether to format a string.
- d.mu.Lock()
- defer d.mu.Unlock()
+func (d *HashDebug) matchPos(ctxt *obj.Link, pos src.XPos) bool {
+ hash := d.hashPos(ctxt, pos)
- b := d.bytesForPos(ctxt, pos)
- hash := hashOfBytes(b, 0)
if d.bisect != nil {
if d.bisect.ShouldPrint(hash) {
- d.logDebugHashMatchLocked(d.name, string(b), hash, 0)
+ d.log(d.name, hash, d.fmtPos(ctxt, pos))
}
return d.bisect.ShouldEnable(hash)
}
+ // TODO: Delete rest of function body when we switch to bisect-only.
+
// Return false for explicitly excluded hashes
if d.excluded(hash) {
return false
}
if m := d.match(hash); m != nil {
- d.logDebugHashMatchLocked(m.name, string(b), hash, 0)
+ d.log(m.name, hash, d.fmtPos(ctxt, pos))
return true
}
return false
}
-// bytesForPos renders a position, including inlining, into d.bytesTmp
-// and returns the byte array. d.mu must be locked.
-func (d *HashDebug) bytesForPos(ctxt *obj.Link, pos src.XPos) []byte {
- b := &d.bytesTmp
- b.Reset()
- format := func(p src.Pos) {
- f := p.Filename()
- if d.fileSuffixOnly {
- f = filepath.Base(f)
- }
- fmt.Fprintf(b, "%s:%d:%d", f, p.Line(), p.Col())
+// short returns the form of file name to use for d.
+// The default is the full path, but fileSuffixOnly selects
+// just the final path element.
+func (d *HashDebug) short(name string) string {
+ if d.fileSuffixOnly {
+ return filepath.Base(name)
}
+ return name
+}
+
+// hashPos returns a hash of the position pos, including its entire inline stack.
+// If d.inlineSuffixOnly is true, hashPos only considers the innermost (leaf) position on the inline stack.
+func (d *HashDebug) hashPos(ctxt *obj.Link, pos src.XPos) uint64 {
if d.inlineSuffixOnly {
- format(ctxt.InnermostPos(pos))
- } else {
- ctxt.AllPos(pos, func(p src.Pos) {
- if b.Len() > 0 {
- b.WriteByte(';')
- }
- format(p)
- })
+ p := ctxt.InnermostPos(pos)
+ return bisect.Hash(d.short(p.Filename()), p.Line(), p.Col())
+ }
+ h := bisect.Hash()
+ ctxt.AllPos(pos, func(p src.Pos) {
+ h = bisect.Hash(h, d.short(p.Filename()), p.Line(), p.Col())
+ })
+ return h
+}
+
+// fmtPos returns a textual formatting of the position pos, including its entire inline stack.
+// If d.inlineSuffixOnly is true, fmtPos only considers the innermost (leaf) position on the inline stack.
+func (d *HashDebug) fmtPos(ctxt *obj.Link, pos src.XPos) string {
+ format := func(p src.Pos) string {
+ return fmt.Sprintf("%s:%d:%d", d.short(p.Filename()), p.Line(), p.Col())
+ }
+ if d.inlineSuffixOnly {
+ return format(ctxt.InnermostPos(pos))
}
- return b.Bytes()
+ var stk []string
+ ctxt.AllPos(pos, func(p src.Pos) {
+ stk = append(stk, format(p))
+ })
+ return strings.Join(stk, "; ")
}
-func (d *HashDebug) logDebugHashMatch(varname, name string, hash, param uint64) {
+// log prints a match with the given hash and textual formatting.
+// TODO: Delete varname parameter when we switch to bisect-only.
+func (d *HashDebug) log(varname string, hash uint64, text string) {
d.mu.Lock()
defer d.mu.Unlock()
- d.logDebugHashMatchLocked(varname, name, hash, param)
-}
-func (d *HashDebug) logDebugHashMatchLocked(varname, name string, hash, param uint64) {
file := d.logfile
if file == nil {
if tmpfile := os.Getenv("GSHS_LOGFILE"); tmpfile != "" {
}
d.logfile = file
}
- hstr := hashString(hash)
- if len(hstr) > 24 {
- hstr = hstr[len(hstr)-24:]
- }
- // External tools depend on this string
- if param == 0 {
- fmt.Fprintf(file, "%s triggered %s %s\n", varname, name, hstr)
- } else {
- fmt.Fprintf(file, "%s triggered %s:%d %s\n", varname, name, param, hstr)
- }
- // Print new bisect version too.
- if param == 0 {
- fmt.Fprintf(file, "%s %s\n", name, bisect.Marker(hash))
- } else {
- fmt.Fprintf(file, "%s:%d %s\n", name, param, bisect.Marker(hash))
- }
- file.Sync()
+
+ // Bisect output.
+ fmt.Fprintf(file, "%s %s\n", text, bisect.Marker(hash))
+
+ // Gossahash output.
+ // TODO: Delete rest of function when we switch to bisect-only.
+ fmt.Fprintf(file, "%s triggered %s %s\n", varname, text, hashString(hash))
}
import (
"bytes"
+ "internal/bisect"
"strings"
"testing"
)
func TestHashDebugGossahashY(t *testing.T) {
- hd := NewHashDebug("GOSSAHASH", "y", new(bufferWithSync))
+ hd := NewHashDebug("GOSSAHASH", "y", new(bytes.Buffer))
if hd == nil {
t.Errorf("NewHashDebug should not return nil for GOSSASHASH=y")
}
- if !hd.DebugHashMatch("anything") {
+ if !hd.MatchPkgFunc("anything", "anyfunc") {
t.Errorf("NewHashDebug should return yes for everything for GOSSASHASH=y")
}
}
func TestHashDebugGossahashN(t *testing.T) {
- hd := NewHashDebug("GOSSAHASH", "n", new(bufferWithSync))
+ hd := NewHashDebug("GOSSAHASH", "n", new(bytes.Buffer))
if hd == nil {
t.Errorf("NewHashDebug should not return nil for GOSSASHASH=n")
}
- if hd.DebugHashMatch("anything") {
+ if hd.MatchPkgFunc("anything", "anyfunc") {
t.Errorf("NewHashDebug should return no for everything for GOSSASHASH=n")
}
}
}
func TestHash(t *testing.T) {
- h0 := hashOf("bar", 0)
- h1 := hashOf("bar", 1)
- t.Logf(`These values are used in other tests: hashOf("bar,0)"=0x%x, hashOf("bar,1)"=0x%x`, h0, h1)
+ h0 := bisect.Hash("bar", "0")
+ h1 := bisect.Hash("bar", "1")
+ t.Logf(`These values are used in other tests: Hash("bar", "0")=%#64b, Hash("bar", "1")=%#64b`, h0, h1)
if h0 == h1 {
t.Errorf("Hashes 0x%x and 0x%x should differ", h0, h1)
}
}
func TestHashMatch(t *testing.T) {
- ws := new(bufferWithSync)
- hd := NewHashDebug("GOSSAHASH", "0011", ws)
- check := hd.DebugHashMatch("bar")
- msg := ws.String()
+ b := new(bytes.Buffer)
+ hd := NewHashDebug("GOSSAHASH", "1110", b)
+ check := hd.MatchPkgFunc("bar", "0")
+ msg := b.String()
t.Logf("message was '%s'", msg)
if !check {
- t.Errorf("GOSSAHASH=0011 should have matched for 'bar'")
+ t.Errorf("GOSSAHASH=1110 should have matched for 'bar', '0'")
}
- wantPrefix(t, msg, "GOSSAHASH triggered bar ")
- wantContains(t, msg, "\nbar [bisect-match ")
-}
-
-func TestHashMatchParam(t *testing.T) {
- ws := new(bufferWithSync)
- hd := NewHashDebug("GOSSAHASH", "1010", ws)
- check := hd.DebugHashMatchParam("bar", 1)
- msg := ws.String()
- t.Logf("message was '%s'", msg)
- if !check {
- t.Errorf("GOSSAHASH=1010 should have matched for 'bar', 1")
- }
- wantPrefix(t, msg, "GOSSAHASH triggered bar:1 ")
- wantContains(t, msg, "\nbar:1 [bisect-match ")
+ wantPrefix(t, msg, "bar.0 [bisect-match ")
+ wantContains(t, msg, "\nGOSSAHASH triggered bar.0 ")
}
func TestYMatch(t *testing.T) {
- ws := new(bufferWithSync)
- hd := NewHashDebug("GOSSAHASH", "y", ws)
- check := hd.DebugHashMatch("bar")
- msg := ws.String()
+ b := new(bytes.Buffer)
+ hd := NewHashDebug("GOSSAHASH", "y", b)
+ check := hd.MatchPkgFunc("bar", "0")
+ msg := b.String()
t.Logf("message was '%s'", msg)
if !check {
- t.Errorf("GOSSAHASH=y should have matched for 'bar'")
+ t.Errorf("GOSSAHASH=y should have matched for 'bar', '0'")
}
- wantPrefix(t, msg, "GOSSAHASH triggered bar 110101000010000100000011")
- wantContains(t, msg, "\nbar [bisect-match ")
+ wantPrefix(t, msg, "bar.0 [bisect-match ")
+ wantContains(t, msg, "\nGOSSAHASH triggered bar.0 010100100011100101011110")
}
func TestNMatch(t *testing.T) {
- ws := new(bufferWithSync)
- hd := NewHashDebug("GOSSAHASH", "n", ws)
- check := hd.DebugHashMatch("bar")
- msg := ws.String()
+ b := new(bytes.Buffer)
+ hd := NewHashDebug("GOSSAHASH", "n", b)
+ check := hd.MatchPkgFunc("bar", "0")
+ msg := b.String()
t.Logf("message was '%s'", msg)
if check {
- t.Errorf("GOSSAHASH=n should NOT have matched for 'bar'")
+ t.Errorf("GOSSAHASH=n should NOT have matched for 'bar', '0'")
}
- wantPrefix(t, msg, "GOSSAHASH triggered bar 110101000010000100000011")
- wantContains(t, msg, "\nbar [bisect-match ")
+ wantPrefix(t, msg, "bar.0 [bisect-match ")
+ wantContains(t, msg, "\nGOSSAHASH triggered bar.0 010100100011100101011110")
}
func TestHashNoMatch(t *testing.T) {
- ws := new(bufferWithSync)
- hd := NewHashDebug("GOSSAHASH", "001100", ws)
- check := hd.DebugHashMatch("bar")
- msg := ws.String()
+ b := new(bytes.Buffer)
+ hd := NewHashDebug("GOSSAHASH", "01110", b)
+ check := hd.MatchPkgFunc("bar", "0")
+ msg := b.String()
t.Logf("message was '%s'", msg)
if check {
- t.Errorf("GOSSAHASH=001100 should NOT have matched for 'bar'")
+ t.Errorf("GOSSAHASH=001100 should NOT have matched for 'bar', '0'")
}
if msg != "" {
t.Errorf("Message should have been empty, instead %s", msg)
}
func TestHashSecondMatch(t *testing.T) {
- ws := new(bufferWithSync)
- hd := NewHashDebug("GOSSAHASH", "001100/0011", ws)
+ b := new(bytes.Buffer)
+ hd := NewHashDebug("GOSSAHASH", "01110/11110", b)
- check := hd.DebugHashMatch("bar")
- msg := ws.String()
+ check := hd.MatchPkgFunc("bar", "0")
+ msg := b.String()
t.Logf("message was '%s'", msg)
if !check {
- t.Errorf("GOSSAHASH=001100, GOSSAHASH0=0011 should have matched for 'bar'")
+ t.Errorf("GOSSAHASH=001100, GOSSAHASH0=0011 should have matched for 'bar', '0'")
}
- wantPrefix(t, msg, "GOSSAHASH0 triggered bar")
-}
-
-type bufferWithSync struct {
- b bytes.Buffer
-}
-
-func (ws *bufferWithSync) Sync() error {
- return nil
-}
-
-func (ws *bufferWithSync) Write(p []byte) (n int, err error) {
- return (&ws.b).Write(p)
-}
-
-func (ws *bufferWithSync) String() string {
- return strings.TrimSpace((&ws.b).String())
+ wantContains(t, msg, "\nGOSSAHASH0 triggered bar")
}
func wantPrefix(t *testing.T, got, want string) {
+ t.Helper()
if !strings.HasPrefix(got, want) {
t.Errorf("want prefix %q, got:\n%s", want, got)
}
}
func wantContains(t *testing.T, got, want string) {
+ t.Helper()
if !strings.Contains(got, want) {
t.Errorf("want contains %q, got:\n%s", want, got)
}