Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions passes/bodyclose/bodyclose.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ func (r *runner) isopen(b *ssa.BasicBlock, i int) bool {
return false
}

if len(*call.Referrers()) == 0 {
return true
if r.isHeadCall(call) {
return false
}

if instr, ok := b.Instrs[i].(*ssa.Call); ok {
Expand All @@ -196,6 +196,10 @@ func (r *runner) isopen(b *ssa.BasicBlock, i int) bool {
}
}

if len(*call.Referrers()) == 0 {
return true
}

cRefs := *call.Referrers()
for _, cRef := range cRefs {
val, ok := r.getResVal(cRef)
Expand Down Expand Up @@ -338,6 +342,46 @@ func (r *runner) getReqCall(instr ssa.Instruction) (*ssa.Call, bool) {
return call, true
}

func (r *runner) isHeadCall(call *ssa.Call) bool {
if callee := call.Call.StaticCallee(); callee != nil {
if callee.Name() != "Head" {
return false
}
if callee.Pkg == nil || callee.Pkg.Pkg == nil {
return false
}
if analysisutil.RemoveVendor(callee.Pkg.Pkg.Path()) != nethttpPath {
return false
}
recv := callee.Signature.Recv()
if recv == nil {
return true
}
typ := recv.Type()
if ptr, ok := typ.(*types.Pointer); ok {
typ = ptr.Elem()
}
return isNamedType(typ, nethttpPath, "Client")
}

if call.Call.IsInvoke() {
method := call.Call.Method
if method != nil && method.Name() == "Head" {
sig, ok := method.Type().(*types.Signature)
if ok && sig.Params().Len() >= 1 && sig.Results().Len() == 2 {
res0 := sig.Results().At(0).Type().String()
res1 := sig.Results().At(1).Type().String()
if res0 == r.resTyp.String() && res1 == "error" {
return true
}
}
}
}

return false
}


func (r *runner) getResVal(instr ssa.Instruction) (ssa.Value, bool) {
switch instr := instr.(type) {
case *ssa.FieldAddr:
Expand Down
82 changes: 82 additions & 0 deletions passes/bodyclose/testdata/src/a/issue78.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package a

import (
"fmt"
"net/http"
)

type HeadClient interface {
Head(url string) (*http.Response, error)
}

func issue78() {
// Call http.Head directly without assigning return values
http.Head("http://example.com") // OK

// Call http.Head ignoring return values
_, _ = http.Head("http://example.com") // OK

// Call http.Head and inspect response without closing body
resp, err := http.Head("http://example.com") // OK
if err != nil {
return
}
fmt.Println(resp.StatusCode)

// Call http.Head and close body explicitly
respWithClose, err := http.Head("http://example.com") // OK
if err != nil {
return
}
defer respWithClose.Body.Close()

// Call client.Head on *http.Client without closing body
client := &http.Client{}
respClient, err := client.Head("http://example.com") // OK
if err != nil {
return
}
fmt.Println(respClient.Status)

// Call client.Head directly without assigning
client.Head("http://example.com") // OK

// Call client.Head on http.Client value without closing body
var valClient http.Client
respValClient, err := valClient.Head("http://example.com") // OK
if err != nil {
return
}
fmt.Println(respValClient.Header)

// Call http.DefaultClient.Head without closing body
respDefault, err := http.DefaultClient.Head("http://example.com") // OK
if err != nil {
return
}
fmt.Println(respDefault.StatusCode)
}

func issue78Interface(c HeadClient) {
resp, err := c.Head("http://example.com") // OK
if err != nil {
return
}
fmt.Println(resp.StatusCode)
}

func issue78RegressionGet() {
resp, err := http.Get("http://example.com") // want "response body must be closed"
if err != nil {
return
}
fmt.Println(resp.StatusCode)
}

func issue78RegressionClientGet(client *http.Client) {
resp, err := client.Get("http://example.com") // want "response body must be closed"
if err != nil {
return
}
fmt.Println(resp.StatusCode)
}
9 changes: 9 additions & 0 deletions passes/bodyclose/testdata/src/consumption/consumption.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,15 @@ func responseHandledDirectiveSkipsConsumption() {
_, _ = handledResponse()
}

func headRequestSkipsConsumption() {
resp, err := http.Head("http://example.com") // OK
if err != nil {
return
}
_ = resp.StatusCode
}


// closeBeforeConsume is commented out because current implementation
// doesn't detect execution order (documented limitation)
//
Expand Down