Skip to content

Commit b69af3f

Browse files
authored
fix false positive with defer (#4)
1 parent ea780f8 commit b69af3f

File tree

2 files changed

+85
-41
lines changed

2 files changed

+85
-41
lines changed

testdata/src/a/a.go

+26
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ func bad() {
3333
logger2 = log.Error() // want "must be dispatched by Msg or Send method"
3434
}
3535
logger2.Str("foo", "bar")
36+
37+
// defer patterns
38+
defer log.Info() // want "must be dispatched by Msg or Send method"
39+
40+
logger3 := log.Error() // want "must be dispatched by Msg or Send method"
41+
defer logger3.Err(err).Str("foo", "bar").Int("foo", 1)
42+
43+
defer log.Info(). // want "must be dispatched by Msg or Send method"
44+
Str("foo", "bar").
45+
Dict("dict", zerolog.Dict().
46+
Str("bar", "baz").
47+
Int("n", 1),
48+
)
3649
}
3750

3851
func ok() {
@@ -68,4 +81,17 @@ func ok() {
6881
// dispatch variation
6982
log.Info().Msgf("")
7083
log.Info().MsgFunc(func() string { return "foo" })
84+
85+
// defer patterns
86+
defer log.Info().Msg("")
87+
88+
logger3 := log.Info()
89+
defer logger3.Msg("")
90+
91+
defer log.Info().
92+
Str("foo", "bar").
93+
Dict("dict", zerolog.Dict().
94+
Str("bar", "baz").
95+
Int("n", 1),
96+
).Send()
7197
}

zerologlint.go

+59-41
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package zerologlint
22

33
import (
4+
"go/token"
45
"strings"
56

67
"golang.org/x/tools/go/analysis"
@@ -20,54 +21,33 @@ var Analyzer = &analysis.Analyzer{
2021
},
2122
}
2223

24+
type posser interface {
25+
Pos() token.Pos
26+
}
27+
28+
// posser is an interface just to hold both ssa.Call and ssa.Defer in our set
29+
type callDefer interface {
30+
Common() *ssa.CallCommon
31+
Pos() token.Pos
32+
}
33+
2334
func run(pass *analysis.Pass) (interface{}, error) {
2435
srcFuncs := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs
2536

26-
// This map holds all the ssa block that is a zerolog.Event type instance
37+
// This set holds all the ssa block that is a zerolog.Event type instance
2738
// that should be dispatched.
2839
// Everytime the zerolog.Event is dispatched with Msg() or Send(),
29-
// deletes that block from this map.
40+
// deletes that block from this set.
3041
// At the end, check if the set is empty, or report the not dispatched block.
31-
set := make(map[ssa.Value]struct{})
42+
set := make(map[posser]struct{})
3243

3344
for _, sf := range srcFuncs {
3445
for _, b := range sf.Blocks {
3546
for _, instr := range b.Instrs {
3647
if c, ok := instr.(*ssa.Call); ok {
37-
v := c.Value()
38-
// check if it's in github.com/rs/zerolog/log since there's some
39-
// functions in github.com/rs/zerolog that returns zerolog.Event
40-
// which should not be included
41-
if isInLogPkg(v) {
42-
if isZerologEvent(v) {
43-
// check if this is a new instance of zerolog.Event like logger := log.Error()
44-
// which should be dispatched afterwards at some point
45-
if len(v.Call.Args) == 0 {
46-
set[v] = struct{}{}
47-
}
48-
continue
49-
}
50-
}
51-
52-
// if the call does not return zerolog.Event,
53-
// check if the base is zerolog.Event.
54-
// if so, check if the StaticCallee is Send() or Msg().
55-
// if so, remove the arg[0] from the set.
56-
for _, arg := range v.Call.Args {
57-
if isZerologEvent(arg) {
58-
if isDispatchMethod(v) {
59-
val := getRootSsaValue(arg)
60-
// if there's branch, remove both ways from the set
61-
if phi, ok := val.(*ssa.Phi); ok {
62-
for _, edge := range phi.Edges {
63-
delete(set, edge)
64-
}
65-
} else {
66-
delete(set, val)
67-
}
68-
}
69-
}
70-
}
48+
inspect(c, &set)
49+
} else if c, ok := instr.(*ssa.Defer); ok {
50+
inspect(c, &set)
7151
}
7252
}
7353
}
@@ -80,8 +60,46 @@ func run(pass *analysis.Pass) (interface{}, error) {
8060
return nil, nil
8161
}
8262

83-
func isInLogPkg(c *ssa.Call) bool {
84-
switch v := c.Call.Value.(type) {
63+
func inspect(cd callDefer, set *map[posser]struct{}) {
64+
c := cd.Common()
65+
66+
// check if it's in github.com/rs/zerolog/log since there's some
67+
// functions in github.com/rs/zerolog that returns zerolog.Event
68+
// which should not be included
69+
if isInLogPkg(*c) {
70+
if isZerologEvent(c.Value) {
71+
// check if this is a new instance of zerolog.Event like logger := log.Error()
72+
// which should be dispatched afterwards at some point
73+
if len(c.Args) == 0 {
74+
(*set)[cd] = struct{}{}
75+
}
76+
return
77+
}
78+
}
79+
80+
// if the call does not return zerolog.Event,
81+
// check if the base is zerolog.Event.
82+
// if so, check if the StaticCallee is Send() or Msg().
83+
// if so, remove the arg[0] from the set.
84+
for _, arg := range c.Args {
85+
if isZerologEvent(arg) {
86+
if isDispatchMethod(*c) {
87+
val := getRootSsaValue(arg)
88+
// if there's branch, remove both ways from the set
89+
if phi, ok := val.(*ssa.Phi); ok {
90+
for _, edge := range phi.Edges {
91+
delete(*set, edge)
92+
}
93+
} else {
94+
delete(*set, val)
95+
}
96+
}
97+
}
98+
}
99+
}
100+
101+
func isInLogPkg(c ssa.CallCommon) bool {
102+
switch v := c.Value.(type) {
85103
case ssa.Member:
86104
p := v.Package()
87105
if p == nil {
@@ -98,8 +116,8 @@ func isZerologEvent(v ssa.Value) bool {
98116
return strings.HasSuffix(ts, "github.com/rs/zerolog.Event")
99117
}
100118

101-
func isDispatchMethod(c *ssa.Call) bool {
102-
m := c.Common().StaticCallee().Name()
119+
func isDispatchMethod(c ssa.CallCommon) bool {
120+
m := c.StaticCallee().Name()
103121
if m == "Send" || m == "Msg" || m == "Msgf" || m == "MsgFunc" {
104122
return true
105123
}

0 commit comments

Comments
 (0)