summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/mgechev/revive/rule/unreachable-code.go
blob: c81e9e733b1e66165aa6788834a77c9a6f407874 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package rule

import (
	"go/ast"

	"github.com/mgechev/revive/lint"
)

// UnreachableCodeRule lints unreachable code.
type UnreachableCodeRule struct{}

// Apply applies the rule to given file.
func (r *UnreachableCodeRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
	var failures []lint.Failure
	onFailure := func(failure lint.Failure) {
		failures = append(failures, failure)
	}

	var branchingFunctions = map[string]map[string]bool{
		"os": map[string]bool{"Exit": true},
		"log": map[string]bool{
			"Fatal":   true,
			"Fatalf":  true,
			"Fatalln": true,
			"Panic":   true,
			"Panicf":  true,
			"Panicln": true,
		},
	}

	w := lintUnreachableCode{onFailure, branchingFunctions}
	ast.Walk(w, file.AST)
	return failures
}

// Name returns the rule name.
func (r *UnreachableCodeRule) Name() string {
	return "unreachable-code"
}

type lintUnreachableCode struct {
	onFailure          func(lint.Failure)
	branchingFunctions map[string]map[string]bool
}

func (w lintUnreachableCode) Visit(node ast.Node) ast.Visitor {
	blk, ok := node.(*ast.BlockStmt)
	if !ok {
		return w
	}

	if len(blk.List) < 2 {
		return w
	}
loop:
	for i, stmt := range blk.List[:len(blk.List)-1] {
		// println("iterating ", len(blk.List))
		next := blk.List[i+1]
		if _, ok := next.(*ast.LabeledStmt); ok {
			continue // skip if next statement is labeled
		}

		switch s := stmt.(type) {
		case *ast.ReturnStmt:
			w.onFailure(newUnreachableCodeFailure(s))
			break loop
		case *ast.BranchStmt:
			token := s.Tok.String()
			if token != "fallthrough" {
				w.onFailure(newUnreachableCodeFailure(s))
				break loop
			}
		case *ast.ExprStmt:
			ce, ok := s.X.(*ast.CallExpr)
			if !ok {
				continue
			}
			// it's a function call
			fc, ok := ce.Fun.(*ast.SelectorExpr)
			if !ok {
				continue
			}

			id, ok := fc.X.(*ast.Ident)

			if !ok {
				continue
			}
			fn := fc.Sel.Name
			pkg := id.Name
			if !w.branchingFunctions[pkg][fn] { // it isn't a call to a branching function
				continue
			}

			if _, ok := next.(*ast.ReturnStmt); ok { // return statement needed to satisfy function signature
				continue
			}

			w.onFailure(newUnreachableCodeFailure(s))
			break loop
		}
	}

	return w
}

func newUnreachableCodeFailure(node ast.Node) lint.Failure {
	return lint.Failure{
		Confidence: 1,
		Node:       node,
		Category:   "logic",
		Failure:    "unreachable code after this statement",
	}
}