@@ -26,6 +26,7 @@ const (
26
26
Doc = "bodyclose checks whether HTTP response body is closed successfully"
27
27
28
28
nethttpPath = "net/http"
29
+ closeMethod = "Close"
29
30
)
30
31
31
32
type runner struct {
@@ -70,7 +71,7 @@ func (r *runner) run(pass *analysis.Pass) (interface{}, error) {
70
71
bodyItrf := bodyNamed .Underlying ().(* types.Interface )
71
72
for i := 0 ; i < bodyItrf .NumMethods (); i ++ {
72
73
bmthd := bodyItrf .Method (i )
73
- if bmthd .Id () == "Close" {
74
+ if bmthd .Id () == closeMethod {
74
75
r .closeMthd = bmthd
75
76
}
76
77
}
@@ -82,6 +83,17 @@ func (r *runner) run(pass *analysis.Pass) (interface{}, error) {
82
83
continue
83
84
}
84
85
86
+ // skip if the function is just referenced
87
+ var isreffunc bool
88
+ for i := 0 ; i < f .Signature .Results ().Len (); i ++ {
89
+ if f .Signature .Results ().At (i ).Type ().String () == r .resTyp .String () {
90
+ isreffunc = true
91
+ }
92
+ }
93
+ if isreffunc {
94
+ continue
95
+ }
96
+
85
97
for _ , b := range f .Blocks {
86
98
for i := range b .Instrs {
87
99
pos := b .Instrs [i ].Pos ()
@@ -100,6 +112,7 @@ func (r *runner) isopen(b *ssa.BasicBlock, i int) bool {
100
112
if ! ok {
101
113
return false
102
114
}
115
+
103
116
if len (* call .Referrers ()) == 0 {
104
117
return true
105
118
}
@@ -126,15 +139,11 @@ func (r *runner) isopen(b *ssa.BasicBlock, i int) bool {
126
139
f := c .Fn .(* ssa.Function )
127
140
if r .noImportedNetHTTP (f ) {
128
141
// skip this
129
- continue
142
+ return false
130
143
}
131
144
called := r .isClosureCalled (c )
132
145
133
- for _ , b := range f .Blocks {
134
- for i := range b .Instrs {
135
- return r .isopen (b , i ) || ! called
136
- }
137
- }
146
+ return r .calledInFunc (f , called )
138
147
}
139
148
140
149
}
@@ -152,6 +161,7 @@ func (r *runner) isopen(b *ssa.BasicBlock, i int) bool {
152
161
}
153
162
154
163
bRefs := * resRef .Referrers ()
164
+
155
165
for _ , bRef := range bRefs {
156
166
bOp , ok := r .getBodyOp (bRef )
157
167
if ! ok {
@@ -186,14 +196,17 @@ func (r *runner) getReqCall(instr ssa.Instruction) (*ssa.Call, bool) {
186
196
}
187
197
188
198
func (r * runner ) getResVal (instr ssa.Instruction ) (ssa.Value , bool ) {
189
- val , ok := instr .(ssa.Value )
190
- if ! ok {
191
- return nil , false
192
- }
193
- if val .Type ().String () != r .resTyp .String () {
194
- return nil , false
199
+ switch instr := instr .(type ) {
200
+ case * ssa.FieldAddr :
201
+ if instr .X .Type ().String () == r .resTyp .String () {
202
+ return instr .X .(ssa.Value ), true
203
+ }
204
+ case ssa.Value :
205
+ if instr .Type ().String () == r .resTyp .String () {
206
+ return instr , true
207
+ }
195
208
}
196
- return val , true
209
+ return nil , false
197
210
}
198
211
199
212
func (r * runner ) getBodyOp (instr ssa.Instruction ) (* ssa.UnOp , bool ) {
@@ -217,6 +230,26 @@ func (r *runner) isCloseCall(ccall ssa.Instruction) bool {
217
230
if ccall .Call .Method .Name () == r .closeMthd .Name () {
218
231
return true
219
232
}
233
+ case * ssa.ChangeInterface :
234
+ if ccall .Type ().String () == "io.Closer" {
235
+ closeMtd := ccall .Type ().Underlying ().(* types.Interface ).Method (0 )
236
+ crs := * ccall .Referrers ()
237
+ for _ , cs := range crs {
238
+ if cs , ok := cs .(* ssa.Defer ); ok {
239
+ if val , ok := cs .Common ().Value .(* ssa.Function ); ok {
240
+ for _ , b := range val .Blocks {
241
+ for _ , instr := range b .Instrs {
242
+ if c , ok := instr .(* ssa.Call ); ok {
243
+ if c .Call .Method == closeMtd {
244
+ return true
245
+ }
246
+ }
247
+ }
248
+ }
249
+ }
250
+ }
251
+ }
252
+ }
220
253
}
221
254
return false
222
255
}
@@ -227,7 +260,8 @@ func (r *runner) isClosureCalled(c *ssa.MakeClosure) bool {
227
260
return false
228
261
}
229
262
for _ , ref := range refs {
230
- if _ , ok := ref .(* ssa.Call ); ok {
263
+ switch ref .(type ) {
264
+ case * ssa.Call , * ssa.Defer :
231
265
return true
232
266
}
233
267
}
@@ -265,3 +299,54 @@ func (r *runner) noImportedNetHTTP(f *ssa.Function) (ret bool) {
265
299
266
300
return true
267
301
}
302
+
303
+ func (r * runner ) calledInFunc (f * ssa.Function , called bool ) bool {
304
+ for _ , b := range f .Blocks {
305
+ for i , instr := range b .Instrs {
306
+ switch instr := instr .(type ) {
307
+ case * ssa.UnOp :
308
+ refs := * instr .Referrers ()
309
+ if len (refs ) == 0 {
310
+ return true
311
+ }
312
+ for _ , r := range refs {
313
+ if v , ok := r .(ssa.Value ); ok {
314
+ if ptr , ok := v .Type ().(* types.Pointer ); ! ok || ! isNamedType (ptr .Elem (), "io" , "ReadCloser" ) {
315
+ return true
316
+ }
317
+ vrefs := * v .Referrers ()
318
+ for _ , vref := range vrefs {
319
+ if vref , ok := vref .(* ssa.UnOp ); ok {
320
+ vrefs := * vref .Referrers ()
321
+ if len (vrefs ) == 0 {
322
+ return true
323
+ }
324
+ for _ , vref := range vrefs {
325
+ if c , ok := vref .(* ssa.Call ); ok {
326
+ if c .Call .Method .Name () == closeMethod {
327
+ return ! called
328
+ }
329
+ }
330
+ }
331
+ }
332
+ }
333
+ }
334
+
335
+ }
336
+ default :
337
+ return r .isopen (b , i ) || ! called
338
+ }
339
+ }
340
+ }
341
+ return false
342
+ }
343
+
344
+ // isNamedType reports whether t is the named type path.name.
345
+ func isNamedType (t types.Type , path , name string ) bool {
346
+ n , ok := t .(* types.Named )
347
+ if ! ok {
348
+ return false
349
+ }
350
+ obj := n .Obj ()
351
+ return obj .Name () == name && obj .Pkg () != nil && obj .Pkg ().Path () == path
352
+ }
0 commit comments