blob: 0a8e88f26ca118efa1e331505e4c10c3cc3e7426 [file]
// Copyright 2022 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 base
import (
"cmd/internal/notsha256"
"fmt"
"io"
"os"
"strings"
"sync"
)
const GOSSAHASH = "GOSSAHASH"
type writeSyncer interface {
io.Writer
Sync() error
}
type HashDebug struct {
mu sync.Mutex
// what file (if any) receives the yes/no logging?
// default is os.Stdout
logfile writeSyncer
}
var hd HashDebug
// DebugHashMatch reports whether environment variable GOSSAHASH
//
// 1. is empty (this is a special more-quickly implemented case of 3)
//
// 2. is "y" or "Y"
//
// 3. is a suffix of the sha1 hash of name
//
// 4. OR
// if evname(i) is a suffix of the sha1 hash of name
// where evname(i)=fmt.Sprintf("GOSSAHASH%d", i),
// for 0<=i<n such that for all i evname(i) != "" and evname(n) == ""
//
// That is, as long as they're not empty, try GOSSAHASH, GOSSAHASH0, GOSSAHASH1, etc,
// but quit trying at the first empty environment variable substitution.
//
// Otherwise it returns false.
// Clause 4 is not really intended for human use.
//
// Unless GOSSAHASH is empty, when DebugHashMatch returns true the message
//
// "%s triggered %s\n", evname, name
//
// is printed on the file named in environment variable GSHS_LOGFILE,
// or standard out if that is empty.
//
// Typical use:
//
// 1. you make a change to the compiler, say, adding a new phase
//
// 2. it is broken in some mystifying way, for example, make.bash builds a broken
// compiler that almost works, but crashes compiling a test in run.bash.
//
// 3. add this guard to the code, which by default leaves it broken, but
// does not run the broken new code if GOSSAHASH is non-empty and non-matching:
//
// if !base.DebugHashMatch(ir.PkgFuncName(fn)) {
// return nil // early exit, do nothing
// }
//
// 4. rebuild w/o the bad code, GOSSAHASH=n ./all.bash to verify that you
// put theguard in the right place with the right sense of the test.
//
// 5. use github.com/dr2chase/gossahash to search for the error:
//
// go install github.com/dr2chase/gossahash@latest
//
// gossahash -- <the thing that fails>
//
// for example: GOMAXPROCS=1 gossahash -- ./all.bash
//
// 6. gossahash should return a single function whose miscompilation
// causes the problem, and you can focus on that.
func DebugHashMatch(pkgAndName string) bool {
return hd.DebugHashMatch(pkgAndName)
}
func (d *HashDebug) DebugHashMatch(pkgAndName string) bool {
evname := GOSSAHASH
evhash := os.Getenv(evname)
hstr := ""
switch evhash {
case "":
return true // default behavior with no EV is "on"
case "n", "N":
return false
}
// Check the hash of the name against a partial input hash.
// We use this feature to do a binary search to
// find a function that is incorrectly compiled.
for _, b := range notsha256.Sum256([]byte(pkgAndName)) {
hstr += fmt.Sprintf("%08b", b)
}
if evhash == "y" || evhash == "Y" || strings.HasSuffix(hstr, evhash) {
d.logDebugHashMatch(evname, pkgAndName, hstr)
return true
}
// Iteratively try additional hashes to allow tests for multi-point
// failure.
for i := 0; true; i++ {
ev := fmt.Sprintf("%s%d", evname, i)
evv := os.Getenv(ev)
if evv == "" {
break
}
if strings.HasSuffix(hstr, evv) {
d.logDebugHashMatch(ev, pkgAndName, hstr)
return true
}
}
return false
}
func (d *HashDebug) logDebugHashMatch(evname, name, hstr string) {
d.mu.Lock()
defer d.mu.Unlock()
file := d.logfile
if file == nil {
if tmpfile := os.Getenv("GSHS_LOGFILE"); tmpfile != "" {
var err error
file, err = os.OpenFile(tmpfile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
Fatalf("could not open hash-testing logfile %s", tmpfile)
return
}
}
if file == nil {
file = os.Stdout
}
d.logfile = file
}
if len(hstr) > 24 {
hstr = hstr[len(hstr)-24:]
}
// External tools depend on this string
fmt.Fprintf(file, "%s triggered %s %s\n", evname, name, hstr)
file.Sync()
}