Skip to content

Commit 1e1b740

Browse files
authored
Check all branches for end in defer (#31)
* Check all branches for end in defer * Refactor the check for end calls in defer
1 parent 4612a14 commit 1e1b740

File tree

2 files changed

+57
-7
lines changed

2 files changed

+57
-7
lines changed

spancheck.go

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ const (
2323
spanOpenCensus // from go.opencensus.io/trace
2424
)
2525

26+
const (
27+
selNameEnd = "End"
28+
selNameSetStatus = "SetStatus"
29+
selNameRecordError = "RecordError"
30+
)
31+
2632
// SpanTypes is a list of all span types by name.
2733
var SpanTypes = map[string]spanType{
2834
"opentelemetry": spanOpenTelemetry,
@@ -183,23 +189,23 @@ func runFunc(pass *analysis.Pass, node ast.Node, config *Config) {
183189
for _, sv := range spanVars {
184190
if config.endCheckEnabled {
185191
// Check if there's no End to the span.
186-
if ret := getMissingSpanCalls(pass, g, sv, "End", func(_ *analysis.Pass, ret *ast.ReturnStmt) *ast.ReturnStmt { return ret }, nil, config.startSpanMatchers); ret != nil {
192+
if ret := getMissingSpanCalls(pass, g, sv, selNameEnd, func(_ *analysis.Pass, ret *ast.ReturnStmt) *ast.ReturnStmt { return ret }, nil, config.startSpanMatchers); ret != nil {
187193
pass.ReportRangef(sv.stmt, "%s.End is not called on all paths, possible memory leak", sv.vr.Name())
188194
pass.ReportRangef(ret, "return can be reached without calling %s.End", sv.vr.Name())
189195
}
190196
}
191197

192198
if config.setStatusEnabled {
193199
// Check if there's no SetStatus to the span setting an error.
194-
if ret := getMissingSpanCalls(pass, g, sv, "SetStatus", getErrorReturn, config.ignoreChecksSignatures, config.startSpanMatchers); ret != nil {
200+
if ret := getMissingSpanCalls(pass, g, sv, selNameSetStatus, getErrorReturn, config.ignoreChecksSignatures, config.startSpanMatchers); ret != nil {
195201
pass.ReportRangef(sv.stmt, "%s.SetStatus is not called on all paths", sv.vr.Name())
196202
pass.ReportRangef(ret, "return can be reached without calling %s.SetStatus", sv.vr.Name())
197203
}
198204
}
199205

200206
if config.recordErrorEnabled && sv.spanType == spanOpenTelemetry { // RecordError only exists in OpenTelemetry
201207
// Check if there's no RecordError to the span setting an error.
202-
if ret := getMissingSpanCalls(pass, g, sv, "RecordError", getErrorReturn, config.ignoreChecksSignatures, config.startSpanMatchers); ret != nil {
208+
if ret := getMissingSpanCalls(pass, g, sv, selNameRecordError, getErrorReturn, config.ignoreChecksSignatures, config.startSpanMatchers); ret != nil {
203209
pass.ReportRangef(sv.stmt, "%s.RecordError is not called on all paths", sv.vr.Name())
204210
pass.ReportRangef(ret, "return can be reached without calling %s.RecordError", sv.vr.Name())
205211
}
@@ -216,7 +222,7 @@ func isSpanStart(info *types.Info, n ast.Node, startSpanMatchers []spanStartMatc
216222

217223
fnSig := info.ObjectOf(sel.Sel).String()
218224

219-
// Check if the function is a span start function
225+
// Check if the function is a span start function.
220226
for _, matcher := range startSpanMatchers {
221227
if matcher.signature.MatchString(fnSig) {
222228
return matcher.spanType, true
@@ -399,6 +405,26 @@ func usesCall(
399405
}
400406

401407
if g := cfgs.FuncLit(f); g != nil && len(g.Blocks) > 0 {
408+
if selName == selNameEnd {
409+
// Check if all returning blocks call end.
410+
for _, b := range g.Blocks {
411+
if b.Return() != nil && !usesCall(
412+
pass,
413+
b.Nodes,
414+
sv,
415+
selName,
416+
ignoreCheckSig,
417+
startSpanMatchers,
418+
depth+1,
419+
) {
420+
return false
421+
}
422+
}
423+
424+
found = true
425+
return false
426+
}
427+
402428
for _, b := range g.Blocks {
403429
if usesCall(
404430
pass,

testdata/enableall/enable_all.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import (
55
"errors"
66
"fmt"
77

8-
"github.com/jjti/go-spancheck/testdata/enableall/util"
98
"go.opencensus.io/trace"
109
"go.opentelemetry.io/otel"
1110
"go.opentelemetry.io/otel/codes"
11+
12+
"github.com/jjti/go-spancheck/testdata/enableall/util"
1213
)
1314

1415
type testError struct{}
@@ -266,15 +267,15 @@ func _() (err error) {
266267
}
267268

268269
func _() (err error) {
269-
_, span := otel.Tracer("foo").Start(context.Background(), "bar") // want "span.SetStatus is not called on all paths"
270+
_, span := otel.Tracer("foo").Start(context.Background(), "bar") // want "span.SetStatus is not called on all paths" "span.End is not called on all paths, possible memory leak"
270271
defer func() {
271272
if true {
272273
span.End()
273274
}
274275
span.RecordError(err)
275276
}()
276277

277-
return errors.New("test") // want "return can be reached without calling span.SetStatus"
278+
return errors.New("test") // want "return can be reached without calling span.SetStatus" "return can be reached without calling span.End"
278279
}
279280

280281
func _() (err error) {
@@ -287,3 +288,26 @@ func _() (err error) {
287288

288289
return errors.New("test")
289290
}
291+
292+
func _() (err error) {
293+
_, span := otel.Tracer("foo").Start(context.Background(), "bar") // want "span.End is not called on all paths, possible memory leak"
294+
defer func() {
295+
if err != nil {
296+
span.RecordError(err)
297+
span.SetStatus(codes.Error, "test")
298+
span.End()
299+
}
300+
}()
301+
302+
return errors.New("test") // want "return can be reached without calling span.End"
303+
}
304+
305+
func _() (err error) {
306+
_, span := otel.Tracer("foo").Start(context.Background(), "bar") // want "span.End is not called on all paths, possible memory leak"
307+
defer func() {
308+
span.RecordError(err)
309+
span.SetStatus(codes.Error, "test")
310+
}()
311+
312+
return errors.New("test") // want "return can be reached without calling span.End"
313+
}

0 commit comments

Comments
 (0)