1 pointby its-malbolge-tian hour ago1 comment
  • its-malbolge-tian hour ago
    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 / TRAINED

    I'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.