Skip to content

Commit 1cd2d94

Browse files
committed
Move root -> passes/mutexunlock
1 parent c11bf13 commit 1cd2d94

File tree

18 files changed

+1387
-2
lines changed

18 files changed

+1387
-2
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.DS_Store
2-
unlockcheck
3-
mutexunlock
2+
./unlockcheck
3+
./mutexunlock
44
_playground
55

66
# Binaries for programs and plugins

passes/mutexunlock/analyzer.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package mutexunlock
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"go/ast"
7+
"os"
8+
"regexp"
9+
"strings"
10+
11+
"github.com/Qs-F/mutexunlock/internal/ctrlflow"
12+
"golang.org/x/tools/go/analysis"
13+
"golang.org/x/tools/go/analysis/passes/inspect"
14+
"golang.org/x/tools/go/ast/inspector"
15+
)
16+
17+
var Analyzer = &analysis.Analyzer{
18+
Name: "mutexunlock",
19+
Doc: "If lcoked mutex is not unlocked before exitting from function or locked block, then report and fix when called with -fix option.",
20+
Run: run,
21+
Requires: []*analysis.Analyzer{
22+
inspect.Analyzer,
23+
ctrlflow.Analyzer,
24+
},
25+
}
26+
27+
func run(pass *analysis.Pass) (interface{}, error) {
28+
cfgs, ok := pass.ResultOf[ctrlflow.Analyzer].(*ctrlflow.CFGs)
29+
if !ok {
30+
return nil, errors.New("ctrlflow Analyzer is not ready")
31+
}
32+
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
33+
filter := []ast.Node{
34+
(*ast.File)(nil),
35+
(*ast.FuncDecl)(nil),
36+
(*ast.FuncLit)(nil),
37+
}
38+
n := 0
39+
bn := 0
40+
sn := 0
41+
inspect.Nodes(filter, func(node ast.Node, push bool) bool {
42+
if !push {
43+
return false
44+
}
45+
46+
switch node := node.(type) {
47+
case *ast.File:
48+
f := pass.Fset.File(node.Pos())
49+
return !(strings.HasSuffix(f.Name(), "_test.go") || generated(node))
50+
case *ast.FuncDecl:
51+
n++
52+
b, s := declCheck(pass, cfgs, node)
53+
bn += b
54+
sn += s
55+
case *ast.FuncLit:
56+
n++
57+
b, s := litCheck(pass, cfgs, node)
58+
bn += b
59+
sn += s
60+
}
61+
return false
62+
})
63+
64+
vlevel := os.Getenv("VERBOSE_LEVEL")
65+
if vlevel == "0" || vlevel == "1" || vlevel == "2" {
66+
fmt.Println("================")
67+
fmt.Println("pass", "\t", pass)
68+
fmt.Println("\tN funcs", "\t", n)
69+
fmt.Println("\tN blocks", "\t", bn)
70+
fmt.Println("\tN succs", "\t", sn)
71+
}
72+
73+
return nil, nil
74+
}
75+
76+
// Belows are copied from https://github.com/ichiban/prodinspect/blob/master/inspector.go
77+
78+
// https://github.com/golang/go/issues/13560#issuecomment-288457920
79+
var pattern = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`)
80+
81+
func generated(f *ast.File) bool {
82+
for _, c := range f.Comments {
83+
for _, l := range c.List {
84+
if pattern.MatchString(l.Text) {
85+
return true
86+
}
87+
}
88+
}
89+
return false
90+
}

passes/mutexunlock/lockstate.go

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package mutexunlock
2+
3+
import (
4+
"errors"
5+
"go/ast"
6+
"go/token"
7+
8+
"github.com/Qs-F/mutexunlock/internal/cfg"
9+
"github.com/google/go-cmp/cmp"
10+
"github.com/google/go-cmp/cmp/cmpopts"
11+
)
12+
13+
var (
14+
ErrDoublyLocked = errors.New("Lock after Lock")
15+
ErrInvalidOp = errors.New("Invalid Operation to mutex object")
16+
ErrLockLocked = errors.New("Attempt to Lock the Locked mutex object")
17+
ErrLockRLocked = errors.New("Attempt to Lock the RLocked mutex object")
18+
ErrRLockLocked = errors.New("Attempt to RLock the Locked mutex object")
19+
ErrRLockRLocked = errors.New("Attempt to RLock the RLocked mutex object")
20+
ErrUnlockUnlocked = errors.New("Attempt to Unlock the Unlocked mutex object")
21+
ErrUnlockRLocked = errors.New("Attempt to Unlock the RLocked mutex object")
22+
ErrRUnlockLocked = errors.New("Attempt to RUnlock the Locked mutex object")
23+
ErrRUnlockRUnlocked = errors.New("Attempt to RUnlock the RUnlocked mutex object")
24+
)
25+
26+
type MuState struct {
27+
Op MutexOp
28+
29+
node ast.Expr
30+
block *cfg.Block
31+
locked bool
32+
rlocked bool
33+
err error
34+
}
35+
36+
func (mu *MuState) Error() error {
37+
return mu.err
38+
}
39+
40+
func (mu *MuState) Locked() bool {
41+
return mu.locked
42+
}
43+
44+
func (mu *MuState) RLocked() bool {
45+
return mu.rlocked
46+
}
47+
48+
type MuStates []*MuState
49+
50+
func (ms *MuStates) Len() int {
51+
if ms == nil {
52+
return 0
53+
}
54+
return len(*ms)
55+
}
56+
57+
func (ms *MuStates) Peek() *MuState {
58+
if ms.Len() < 1 {
59+
return &MuState{
60+
Op: MutexOpInvalid,
61+
}
62+
}
63+
return (*ms)[ms.Len()-1]
64+
}
65+
66+
func (ms *MuStates) Push(state *MuState) {
67+
prev := ms.Peek()
68+
var e error
69+
locked := prev.Locked()
70+
rlocked := prev.RLocked()
71+
72+
switch state.Op {
73+
case MutexOpLock:
74+
// Both rlocked and locked never be true at the same time
75+
if prev.Locked() {
76+
e = ErrLockLocked
77+
break
78+
}
79+
if prev.RLocked() {
80+
e = ErrLockRLocked
81+
break
82+
}
83+
locked = true
84+
case MutexOpRLock:
85+
// Both rlocked and locked never be true at the same time
86+
if prev.Locked() {
87+
e = ErrRLockLocked
88+
break
89+
}
90+
if prev.RLocked() {
91+
e = ErrRLockRLocked
92+
break
93+
}
94+
rlocked = true
95+
case MutexOpUnlock:
96+
if !prev.Locked() {
97+
e = ErrUnlockUnlocked
98+
break
99+
}
100+
if prev.RLocked() {
101+
e = ErrUnlockRLocked
102+
break
103+
}
104+
locked = false
105+
case MutexOpRUnlock:
106+
if !prev.RLocked() {
107+
e = ErrRUnlockRUnlocked
108+
break
109+
}
110+
if prev.Locked() {
111+
e = ErrRUnlockLocked
112+
break
113+
}
114+
rlocked = false
115+
}
116+
117+
state.locked = locked
118+
state.rlocked = rlocked
119+
state.err = e
120+
121+
*ms = append(*ms, state)
122+
}
123+
124+
type LockState struct {
125+
ms map[ast.Expr]*MuStates
126+
}
127+
128+
func NewLockState() *LockState {
129+
return &LockState{
130+
ms: make(map[ast.Expr]*MuStates),
131+
}
132+
}
133+
134+
func (ls *LockState) Init(node ast.Expr) {
135+
ls.ms[node] = &MuStates{}
136+
}
137+
138+
func (ls *LockState) Push(mu *MuState) {
139+
if mu == nil {
140+
return
141+
}
142+
key := mu.node
143+
for k := range ls.ms {
144+
if cmp.Equal(k, mu.node, cmpopts.IgnoreTypes(token.Pos(0))) {
145+
key = k
146+
break
147+
}
148+
}
149+
150+
if _, ok := ls.ms[key]; !ok {
151+
ls.Init(key)
152+
}
153+
ls.ms[key].Push(mu)
154+
}
155+
156+
func (ls *LockState) Update(block *cfg.Block, node ast.Expr, op MutexOp) {
157+
ls.Push(&MuState{
158+
Op: op,
159+
block: block,
160+
node: node,
161+
})
162+
}
163+
164+
func (ls *LockState) Map() map[ast.Expr]*MuStates {
165+
return ls.ms
166+
}
167+
168+
func (ls *LockState) Get(key ast.Expr) (*MuStates, bool) {
169+
node := key
170+
for k := range ls.ms {
171+
if cmp.Equal(k, key, cmpopts.IgnoreTypes(token.Pos(0))) {
172+
node = k
173+
break
174+
}
175+
}
176+
ret, ok := ls.ms[node]
177+
return ret, ok
178+
}
179+
180+
func (ls *LockState) Copy() *LockState {
181+
if ls == nil {
182+
return nil
183+
}
184+
ret := NewLockState()
185+
for k, v := range ls.ms {
186+
ret.Init(k)
187+
*ret.ms[k] = append(MuStates{}, *v...)
188+
}
189+
return ret
190+
}

0 commit comments

Comments
 (0)