Comment by its-malbolge-ti
Hey HN! For the past few months, I've been working really hard on a static analysis tool called gohawk, and have successfully used it to find bugs in Caddy / Docker / k8s (links in the README). It's meant to catch resource leaks and concurrency errors in Go code, such as the following example: func fetch(url string) ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status: %s", resp.Status) // resp isn't closed on all paths
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
I built this tool after realizing that Staticcheck and other linters don't really cover this ground yet. It works by building a symbolic heap model of the program under analysis, so that it can track object references with a high degree of accuracy. (I took this idea from Meta's Infer tool, which is developed exclusively for C++/Objective-C/Java, and generalized it to fit Go.) For example, it's able to tell in this case that r1 and r2 are the same. type fileBox struct {
slots [2]*os.File
}
func example(path string, replacement *os.File) error {
r1, err := os.Open(path)
if err != nil {
return err
}
original := fileBox{slots: [2]*os.File{replacement, r1}}
copied := original
alias := &original
alias.slots[1] = replacement
window := copied.slots[1:]
r2 := window[0]
return r2.Close()
}
It's also capable of detecting certain types of goroutine leaks / lock inversions. For example, the following code gets flagged: func runWithTimeout(work func() error) error {
done := make(chan error)
go func() { done <- work() }()
select {
case err := <-done:
return err
case <-time.After(time.Second):
return errors.New("operation timed out")
}
}
HOW THIS WAS BUILT / TRAINEDI'll be up front with y'all and say I used AI a lot to help me in the process of writing gohawk. However, I will stand by the fact that it's not just AI slop -- I'm a clean code fanatic, and the pipeline has been deliberately engineered.
I really wanted the user experience to be buttery smooth when downloading and using this, so I had an AI agent continuously download hundreds of repos from the Internet, run gohawk on them, and report back if there were any false positives. (Yes, I know, slightly overkill :^) ) It's at a point where I think false positives on new code should be fairly rare.
OTHER FEATURES
- Really rich diagnostics (I took inspo from rustc -- the full lineage of the error gets reported, including error spans) - Documentation website (https://gohawk.dev) -- Contains detailed overview of all the supported analyzers. The diagnostic messages link out to the website - Custom analysis runner -- I found `go vet` to be really slow because it would spend a lot of time serializing and deserializing facts across packages, so I rolled my own runner to replace it. - Performant -- From a cold cache, it's able to analyze Docker in around 13s, and the whole of Kubernetes around 76s.
Building this out was a really awesome learning experience for me, and I'm planning to make a blog post about it soon -- stay tuned. Also hoping to get it integrated directly into GolangCI but gotta ask the maintainer first, so fingers crossed.
I genuinely hope you guys can download and enjoy the effort I put into this project. Feedback and contributions are welcome, and I'm planning to set up a Discord or IRC channel in the near future.