diff --git a/.golangci.reference.yml b/.golangci.reference.yml index 9a4fe43e8d68..515c043f88a8 100644 --- a/.golangci.reference.yml +++ b/.golangci.reference.yml @@ -2052,6 +2052,7 @@ linters: - predeclared - promlinter - reassign + - reuseconn - revive - rowserrcheck - scopelint @@ -2160,6 +2161,7 @@ linters: - predeclared - promlinter - reassign + - reuseconn - revive - rowserrcheck - scopelint diff --git a/go.mod b/go.mod index 30e0426a0457..bf3bb46330ab 100644 --- a/go.mod +++ b/go.mod @@ -187,3 +187,5 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect ) + +require github.com/atzoum/reuseconn v0.1.0 // indirect diff --git a/go.sum b/go.sum index 83be7c9bb78c..2cfe2bb78fbb 100644 --- a/go.sum +++ b/go.sum @@ -71,6 +71,8 @@ github.com/ashanbrown/forbidigo v1.3.0 h1:VkYIwb/xxdireGAdJNZoo24O4lmnEWkactplBl github.com/ashanbrown/forbidigo v1.3.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBFg8t0sG2FIxmI= github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= +github.com/atzoum/reuseconn v0.1.0 h1:Tif+KPmOzoK/ns3ZmGKBQaNwjgUrqC3mGfacPrFv47U= +github.com/atzoum/reuseconn v0.1.0/go.mod h1:qlcg3/NPvsttnjeaOp1LW31KWAih+MVN/G77s+lpmHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= diff --git a/pkg/golinters/reuseconn.go b/pkg/golinters/reuseconn.go new file mode 100644 index 000000000000..e1a61a79dcd9 --- /dev/null +++ b/pkg/golinters/reuseconn.go @@ -0,0 +1,17 @@ +package golinters + +import ( + "github.com/atzoum/reuseconn/reuseconn" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" +) + +func NewReuseconn() *goanalysis.Linter { + return goanalysis.NewLinter( + "reuseconn", + "checks whether HTTP response body is consumed and closed properly in a single function", + []*analysis.Analyzer{reuseconn.Analyzer}, + nil, + ).WithLoadMode(goanalysis.LoadModeWholeProgram) +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index ab8f1835bd41..6a270d070d38 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -715,6 +715,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { ConsiderSlow(). WithURL("https://github.com/mgechev/revive"), + linter.NewConfig(golinters.NewReuseconn()). + WithSince("v1.51.0"). + WithLoadForGoAnalysis(). + WithPresets(linter.PresetPerformance, linter.PresetBugs). + WithURL("https://github.com/atzoum/reuseconn"), + linter.NewConfig(golinters.NewRowsErrCheck(rowserrcheckCfg)). WithSince("v1.23.0"). WithLoadForGoAnalysis(). diff --git a/test/testdata/reuseconn.go b/test/testdata/reuseconn.go new file mode 100644 index 000000000000..ad3273f6b3c3 --- /dev/null +++ b/test/testdata/reuseconn.go @@ -0,0 +1,26 @@ +//golangcitest:args -Ereuseconn +package testdata + +import ( + "io" + "io/ioutil" + "net/http" +) + +func BodyNotDisposedInSingleFunction() { + resp, _ := http.Get("https://google.com") // want "response body must be disposed properly in a single function read to completion and closed" + _, _ = ioutil.ReadAll(resp.Body) + resp.Body.Close() +} + +func BodyDisposedInSingleFunction() { + resp, _ := http.Get("https://google.com") + disposeResponse(resp) +} + +func disposeResponse(resp *http.Response) { + if resp != nil && resp.Body != nil { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + } +}