Skip to content

Commit 85be888

Browse files
devoxeladonovan
authored andcommitted
go/analysis/passes/defers: add analyser for defer mistake
This is adding an analysis pass to catch defer statements where people intend to invoke a defer arguments when the defer is ran; not when it is first invoked. In order to achieve this, the current analyasis implementation first uses the inspect.Preorder tool to look for defer nodes. It then walks the defer node expression tree. This solution means that we don't catch function literals, and maybe it's slightly unoptimized because it doesn't use the Inspect fast node filtering once we find the defer nodes. Updates golang/go#60048. Change-Id: I50ec60c7fc4a5ced858f42cb8db8e9ea37a7038f Reviewed-on: https://go-review.googlesource.com/c/tools/+/499875 TryBot-Bypass: Alan Donovan <[email protected]> Reviewed-by: Alan Donovan <[email protected]> Reviewed-by: Robert Findley <[email protected]> Auto-Submit: Alan Donovan <[email protected]> gopls-CI: kokoro <[email protected]> Run-TryBot: Alan Donovan <[email protected]>
1 parent c43232f commit 85be888

File tree

5 files changed

+174
-0
lines changed

5 files changed

+174
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// The defers command runs the defers analyzer.
6+
package main
7+
8+
import (
9+
"golang.org/x/tools/go/analysis/passes/defers"
10+
"golang.org/x/tools/go/analysis/singlechecker"
11+
)
12+
13+
func main() { singlechecker.Main(defers.Analyzer) }

go/analysis/passes/defers/defer.go

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package defers
6+
7+
import (
8+
_ "embed"
9+
"go/ast"
10+
"go/types"
11+
12+
"golang.org/x/tools/go/analysis"
13+
"golang.org/x/tools/go/analysis/passes/inspect"
14+
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
15+
"golang.org/x/tools/go/ast/inspector"
16+
"golang.org/x/tools/go/types/typeutil"
17+
)
18+
19+
//go:embed doc.go
20+
var doc string
21+
22+
// Analyzer is the defer analyzer.
23+
var Analyzer = &analysis.Analyzer{
24+
Name: "defer",
25+
Requires: []*analysis.Analyzer{inspect.Analyzer},
26+
Doc: analysisutil.MustExtractDoc(doc, "defer"),
27+
Run: run,
28+
}
29+
30+
func run(pass *analysis.Pass) (interface{}, error) {
31+
if !analysisutil.Imports(pass.Pkg, "time") {
32+
return nil, nil
33+
}
34+
35+
checkDeferCall := func(node ast.Node) bool {
36+
switch v := node.(type) {
37+
case *ast.CallExpr:
38+
fn, ok := typeutil.Callee(pass.TypesInfo, v).(*types.Func)
39+
if ok && fn.Name() == "Since" && fn.Pkg().Path() == "time" {
40+
pass.Reportf(v.Pos(), "call to time.Since is not deferred")
41+
}
42+
case *ast.FuncLit:
43+
return false // prune
44+
}
45+
return true
46+
}
47+
48+
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
49+
50+
nodeFilter := []ast.Node{
51+
(*ast.DeferStmt)(nil),
52+
}
53+
54+
inspect.Preorder(nodeFilter, func(n ast.Node) {
55+
d := n.(*ast.DeferStmt)
56+
ast.Inspect(d.Call, checkDeferCall)
57+
})
58+
59+
return nil, nil
60+
}
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package defers_test
6+
7+
import (
8+
"testing"
9+
10+
"golang.org/x/tools/go/analysis/analysistest"
11+
"golang.org/x/tools/go/analysis/passes/defers"
12+
)
13+
14+
func Test(t *testing.T) {
15+
testdata := analysistest.TestData()
16+
analysistest.Run(t, testdata, defers.Analyzer, "a")
17+
}

go/analysis/passes/defers/doc.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package defers defines an Analyzer that checks for common mistakes in defer
6+
// statements.
7+
//
8+
// # Analyzer defer
9+
//
10+
// defer: report common mistakes in defer statements
11+
//
12+
// The defer analyzer reports a diagnostic when a defer statement would
13+
// result in a non-deferred call to time.Since, as experience has shown
14+
// that this is nearly always a mistake.
15+
//
16+
// For example:
17+
//
18+
// start := time.Now()
19+
// ...
20+
// defer recordLatency(time.Since(start)) // error: call to time.Since is not deferred
21+
//
22+
// The correct code is:
23+
//
24+
// defer func() { recordLatency(time.Since(start)) }()`
25+
package defers
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package a
6+
7+
import (
8+
"fmt"
9+
"time"
10+
)
11+
12+
func Since() (t time.Duration) {
13+
return
14+
}
15+
16+
func x(time.Duration) {}
17+
func x2(float64) {}
18+
19+
func good() {
20+
// The following are OK because func is not evaluated in defer invocation.
21+
now := time.Now()
22+
defer func() {
23+
fmt.Println(time.Since(now)) // OK because time.Since is not evaluated in defer
24+
}()
25+
evalBefore := time.Since(now)
26+
defer fmt.Println(evalBefore)
27+
do := func(f func()) {}
28+
defer do(func() { time.Since(now) })
29+
defer fmt.Println(Since()) // OK because Since function is not in module time
30+
31+
}
32+
33+
type y struct{}
34+
35+
func (y) A(float64) {}
36+
func (*y) B(float64) {}
37+
func (y) C(time.Duration) {}
38+
func (*y) D(time.Duration) {}
39+
40+
func bad() {
41+
var zero time.Time
42+
now := time.Now()
43+
defer time.Since(zero) // want "call to time.Since is not deferred"
44+
defer time.Since(now) // want "call to time.Since is not deferred"
45+
defer fmt.Println(time.Since(now)) // want "call to time.Since is not deferred"
46+
defer fmt.Println(time.Since(time.Now())) // want "call to time.Since is not deferred"
47+
defer x(time.Since(now)) // want "call to time.Since is not deferred"
48+
defer x2(time.Since(now).Seconds()) // want "call to time.Since is not deferred"
49+
defer y{}.A(time.Since(now).Seconds()) // want "call to time.Since is not deferred"
50+
defer (&y{}).B(time.Since(now).Seconds()) // want "call to time.Since is not deferred"
51+
defer y{}.C(time.Since(now)) // want "call to time.Since is not deferred"
52+
defer (&y{}).D(time.Since(now)) // want "call to time.Since is not deferred"
53+
}
54+
55+
func ugly() {
56+
// The following is ok even though time.Since is evaluated. We don't
57+
// walk into function literals or check what function definitions are doing.
58+
defer x((func() time.Duration { return time.Since(time.Now()) })())
59+
}

0 commit comments

Comments
 (0)