Repository: go
Issue: proposal: annotation to generalize `lostcancel` for other kinds of cleanup function
Labels: Proposal,Analysis

### Proposal Details

The [context.WithCancel](https://pkg.go.dev/context#WithCancel) function returns a "cancel" function that must be called on all execution paths. It's easy to forget to do so, especially in early-return error paths, leading to a context leak. This pattern is quite common: the gopls codebase has many functions that return a `release` function that decrements a reference count [[example](https://cs.opensource.google/go/x/tools/+/master:gopls/internal/server/link.go;l=34;drc=6d109d1133da8b25c847b69458614b0ab44da0fb)], and we've had a number of bugs from failure to follow the proper discipline. Kubernetes has many of its own too. Searching for functions that return a value of type `func()`, often named something like `cleanup`, `release`, `stop`, `close`, or `shutdown`, it's easy to find more instances of this pattern.

Go vet currently has a [lostcancel](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/lostcancel) analyzer that reports problems of this sort, and it is not hard to generalize it to handle other functions besides `context.WithCancel`. (I implemented it over the weekend.) But the hardest part of the problem is reliably identifying which functions are cleanup functions, and which don't exactly follow the discipline [[example](https://cs.opensource.google/go/x/tools/+/master:gopls/internal/server/prompt.go;l=277;drc=6d109d1133da8b25c847b69458614b0ab44da0fb)]. The name is not a reliable clue, and many functions don't name their result variables.

The actual type of the function returned by WithCancel is [`type CancelFunc func()`](https://pkg.go.dev/context#CancelFunc), a named type. This suggests an approach to generalization: allow modules to define their own clean-up function types analogous to CancelFunc, and then register them with the analyzer using the annotation mechanism that we plan to design and develop this year.

Here's a sketch of what that might look like:

```go
package p

import "golang.org/x/tools/analysis/annotations/lostcancel"

type Resource struct { ... }

// Acquire (on success) returns a resource.
// The caller must call the release function when they are finished using the resource.
func Acquire() (*Resource, ReleaseFunc, error) { ... }

func ReleaseFunc func() 

func _() {
    lostcancel.Register(CancelFunc(nil))
}
```

This proposal is obviously subordinate to the annotations proposal, but one immediate question is: should the standard library provide a standard CancelFunc? Long time viewers may remember a similar discussion around a standard [NoCopy](https://github.com/golang/go/issues/8005) annotation, which ended in a "no" decision. But as we think about generalizing the patterns of vet checking beyond the standard library, we may want to revisit whether the standard library should provide a  lightweight package for declaring the most important annotations. This might have benefits in uniformity and boilerplate reduction, and would also allow the standard library to use the annotations, which (if they lived alongside the analyzers) would otherwise be unavailable to it.

```go
package annot // hypothetical new std annotations package

// A CleanupFunc is a function that must be called to clean up a resource.
//
// Here is the typical pattern:
//
//        resource, cleanup, err := p.Acquire()
//        if err != nil { return err }
//        defer cleanup()
//        ... use resource ... 
//
// Failure to call cleanup (or at least mention it) on all control paths not
// dominated by `err != nil` may be reported as an error by the `lostcancel` checker.
type CleanupFunc func()
```

Related:
- https://github.com/golang/go/issues/64543
- https://github.com/golang/go/issues/58340
- https://github.com/golang/go/issues/52445
- https://go.dev/cl/489835