github.com/Johnny2210/revive@v1.0.8-0.20210625134200-febf37ccd0f5/rule/unconditional-recursion.go (about) 1 package rule 2 3 import ( 4 "go/ast" 5 6 "github.com/mgechev/revive/lint" 7 ) 8 9 // UnconditionalRecursionRule lints given else constructs. 10 type UnconditionalRecursionRule struct{} 11 12 // Apply applies the rule to given file. 13 func (r *UnconditionalRecursionRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { 14 var failures []lint.Failure 15 16 onFailure := func(failure lint.Failure) { 17 failures = append(failures, failure) 18 } 19 20 w := lintUnconditionalRecursionRule{onFailure: onFailure} 21 ast.Walk(w, file.AST) 22 return failures 23 } 24 25 // Name returns the rule name. 26 func (r *UnconditionalRecursionRule) Name() string { 27 return "unconditional-recursion" 28 } 29 30 type funcDesc struct { 31 reciverID *ast.Ident 32 id *ast.Ident 33 } 34 35 func (fd *funcDesc) equal(other *funcDesc) bool { 36 receiversAreEqual := (fd.reciverID == nil && other.reciverID == nil) || fd.reciverID != nil && other.reciverID != nil && fd.reciverID.Name == other.reciverID.Name 37 idsAreEqual := (fd.id == nil && other.id == nil) || fd.id.Name == other.id.Name 38 39 return receiversAreEqual && idsAreEqual 40 } 41 42 type funcStatus struct { 43 funcDesc *funcDesc 44 seenConditionalExit bool 45 } 46 47 type lintUnconditionalRecursionRule struct { 48 onFailure func(lint.Failure) 49 currentFunc *funcStatus 50 } 51 52 // Visit will traverse the file AST. 53 // The rule is based in the following algorithm: inside each function body we search for calls to the function itself. 54 // We do not search inside conditional control structures (if, for, switch, ...) because any recursive call inside them is conditioned 55 // We do search inside conditional control structures are statements that will take the control out of the function (return, exit, panic) 56 // If we find conditional control exits, it means the function is NOT unconditionally-recursive 57 // If we find a recursive call before finding any conditional exit, a failure is generated 58 // In resume: if we found a recursive call control-dependant from the entry point of the function then we raise a failure. 59 func (w lintUnconditionalRecursionRule) Visit(node ast.Node) ast.Visitor { 60 switch n := node.(type) { 61 case *ast.FuncDecl: 62 var rec *ast.Ident 63 switch { 64 case n.Recv == nil || n.Recv.NumFields() < 1 || len(n.Recv.List[0].Names) < 1: 65 rec = nil 66 default: 67 rec = n.Recv.List[0].Names[0] 68 } 69 70 w.currentFunc = &funcStatus{&funcDesc{rec, n.Name}, false} 71 case *ast.CallExpr: 72 var funcID *ast.Ident 73 var selector *ast.Ident 74 switch c := n.Fun.(type) { 75 case *ast.Ident: 76 selector = nil 77 funcID = c 78 case *ast.SelectorExpr: 79 var ok bool 80 selector, ok = c.X.(*ast.Ident) 81 if !ok { // a.b....Foo() 82 return nil 83 } 84 funcID = c.Sel 85 default: 86 return w 87 } 88 89 if w.currentFunc != nil && // not in a func body 90 !w.currentFunc.seenConditionalExit && // there is a conditional exit in the function 91 w.currentFunc.funcDesc.equal(&funcDesc{selector, funcID}) { 92 w.onFailure(lint.Failure{ 93 Category: "logic", 94 Confidence: 1, 95 Node: n, 96 Failure: "unconditional recursive call", 97 }) 98 } 99 case *ast.IfStmt: 100 w.updateFuncStatus(n.Body) 101 w.updateFuncStatus(n.Else) 102 return nil 103 case *ast.SelectStmt: 104 w.updateFuncStatus(n.Body) 105 return nil 106 case *ast.RangeStmt: 107 w.updateFuncStatus(n.Body) 108 return nil 109 case *ast.TypeSwitchStmt: 110 w.updateFuncStatus(n.Body) 111 return nil 112 case *ast.SwitchStmt: 113 w.updateFuncStatus(n.Body) 114 return nil 115 case *ast.GoStmt: 116 for _, a := range n.Call.Args { 117 ast.Walk(w, a) // check if arguments have a recursive call 118 } 119 return nil // recursive async call is not an issue 120 case *ast.ForStmt: 121 if n.Cond != nil { 122 return nil 123 } 124 // unconditional loop 125 return w 126 } 127 128 return w 129 } 130 131 func (w *lintUnconditionalRecursionRule) updateFuncStatus(node ast.Node) { 132 if node == nil || w.currentFunc == nil || w.currentFunc.seenConditionalExit { 133 return 134 } 135 136 w.currentFunc.seenConditionalExit = w.hasControlExit(node) 137 } 138 139 var exitFunctions = map[string]map[string]bool{ 140 "os": {"Exit": true}, 141 "syscall": {"Exit": true}, 142 "log": { 143 "Fatal": true, 144 "Fatalf": true, 145 "Fatalln": true, 146 "Panic": true, 147 "Panicf": true, 148 "Panicln": true, 149 }, 150 } 151 152 func (w *lintUnconditionalRecursionRule) hasControlExit(node ast.Node) bool { 153 // isExit returns true if the given node makes control exit the function 154 isExit := func(node ast.Node) bool { 155 switch n := node.(type) { 156 case *ast.ReturnStmt: 157 return true 158 case *ast.CallExpr: 159 if isIdent(n.Fun, "panic") { 160 return true 161 } 162 se, ok := n.Fun.(*ast.SelectorExpr) 163 if !ok { 164 return false 165 } 166 167 id, ok := se.X.(*ast.Ident) 168 if !ok { 169 return false 170 } 171 172 fn := se.Sel.Name 173 pkg := id.Name 174 if exitFunctions[pkg] != nil && exitFunctions[pkg][fn] { // it's a call to an exit function 175 return true 176 } 177 } 178 179 return false 180 } 181 182 return len(pick(node, isExit, nil)) != 0 183 }