bodyclose icon indicating copy to clipboard operation
bodyclose copied to clipboard

Vet regression case from update

Open benw10-1 opened this issue 1 year ago • 4 comments

Seeing stuff that didn't fail on ed6a65f now failing on current commit.

Example: integrations/testbodyclose/test.go

package sandbox

import (
	"io"
	"net/http"
)

func Foo(req *http.Request) error {
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	if res.StatusCode == 200 {
		body, err := io.ReadAll(res.Body)
		if err != nil {
			return err
		}
		_ = body

		return nil
	}

	return nil
}

Passes on ed6a65f, but fails with:

integrations/testbodyclose/test.go:10:35: response body must be closed

on master commit.

Can provide more examples similar to this if necessary, but would need to dig them up so didn't include in initial.

benw10-1 avatar Oct 17 '24 20:10 benw10-1

Other variants:

if
package sandbox

import (
	"io"
	"net/http"
)

func Foo(req *http.Request) error {
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}

	defer res.Body.Close()

	if res.StatusCode == 200 {
		return nil
	}

	io.ReadAll(res.Body)

	return nil
}
for
package sandbox

import (
	"io"
	"net/http"
)

func Foo(req *http.Request) error {
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}

	defer res.Body.Close()

	for range res.StatusCode {
		return nil
	}

	io.ReadAll(res.Body)

	return nil
}
switch
package sandbox

import (
	"io"
	"net/http"
)

func Foo(req *http.Request) error {
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}

	defer res.Body.Close()

	switch res.StatusCode {
	case http.StatusOK:
		return nil
	}

	io.ReadAll(res.Body)

	return nil
}

ldez avatar Oct 17 '24 21:10 ldez

The problem is related to the way the graph is analyzed: the algorithm is always wrong if there is at least one if/for/switch/etc. and a defer at the top level.

ldez avatar Oct 17 '24 21:10 ldez

Seems like it needs to be updated such that it doesn't fail if the parent branch explicitly calls the Close method instead of checking block by block without considering these cases.

I tested the below cases as well and got fails:

Defer Close with "named" block

integrations/testbodyclose/test.go

package sandbox

import (
	"net/http"
)

func Foo(req *http.Request) error {
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		// even though in practice this wouldn't be done, was checking to see if it was this block causing issue
		res.Body.Close()
		return err
	}
	defer res.Body.Close()

	{
		_ = res.Body
	}

	return nil
}

Fails with:

integrations/testbodyclose/test.go:8:35: response body must be closed
Explicit Close with "named" block

integrations/testbodyclose/test.go

package sandbox

import (
	"net/http"
)

func Foo(req *http.Request) error {
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		// even though in practice this wouldn't be done, was checking to see if it was this block causing issue
		res.Body.Close()
		return err
	}

	{
		_ = res.Body
	}

	err = res.Body.Close()
	if err != nil {
		return err
	}

	return nil
}

Fails with:

integrations/testbodyclose/test.go:8:35: response body must be closed

benw10-1 avatar Oct 17 '24 21:10 benw10-1

Offtopic: a tip to collapse examples inside Markdown.

<details><summary>foo</summary>

bar

```go
code
```

</details>

Result:

foo

bar

code

ldez avatar Oct 17 '24 21:10 ldez