github.com/songshiyun/revive@v1.1.5-0.20220323112655-f8433a19b3c5/rule/unconditional-recursion.go (about) 1 package rule 2 3 import ( 4 "go/ast" 5 6 "github.com/songshiyun/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: 65 rec = nil 66 case n.Recv.NumFields() < 1 || len(n.Recv.List[0].Names) < 1: 67 rec = &ast.Ident{Name: "_"} 68 default: 69 rec = n.Recv.List[0].Names[0] 70 } 71 72 w.currentFunc = &funcStatus{&funcDesc{rec, n.Name}, false} 73 case *ast.CallExpr: 74 var funcID *ast.Ident 75 var selector *ast.Ident 76 switch c := n.Fun.(type) { 77 case *ast.Ident: 78 selector = nil 79 funcID = c 80 case *ast.SelectorExpr: 81 var ok bool 82 selector, ok = c.X.(*ast.Ident) 83 if !ok { // a.b....Foo() 84 return nil 85 } 86 funcID = c.Sel 87 default: 88 return w 89 } 90 91 if w.currentFunc != nil && // not in a func body 92 !w.currentFunc.seenConditionalExit && // there is a conditional exit in the function 93 w.currentFunc.funcDesc.equal(&funcDesc{selector, funcID}) { 94 w.onFailure(lint.Failure{ 95 Category: "logic", 96 Confidence: 1, 97 Node: n, 98 Failure: "unconditional recursive call", 99 }) 100 } 101 case *ast.IfStmt: 102 w.updateFuncStatus(n.Body) 103 w.updateFuncStatus(n.Else) 104 return nil 105 case *ast.SelectStmt: 106 w.updateFuncStatus(n.Body) 107 return nil 108 case *ast.RangeStmt: 109 w.updateFuncStatus(n.Body) 110 return nil 111 case *ast.TypeSwitchStmt: 112 w.updateFuncStatus(n.Body) 113 return nil 114 case *ast.SwitchStmt: 115 w.updateFuncStatus(n.Body) 116 return nil 117 case *ast.GoStmt: 118 for _, a := range n.Call.Args { 119 ast.Walk(w, a) // check if arguments have a recursive call 120 } 121 return nil // recursive async call is not an issue 122 case *ast.ForStmt: 123 if n.Cond != nil { 124 return nil 125 } 126 // unconditional loop 127 return w 128 } 129 130 return w 131 } 132 133 func (w *lintUnconditionalRecursionRule) updateFuncStatus(node ast.Node) { 134 if node == nil || w.currentFunc == nil || w.currentFunc.seenConditionalExit { 135 return 136 } 137 138 w.currentFunc.seenConditionalExit = w.hasControlExit(node) 139 } 140 141 var exitFunctions = map[string]map[string]bool{ 142 "os": {"Exit": true}, 143 "syscall": {"Exit": true}, 144 "log": { 145 "Fatal": true, 146 "Fatalf": true, 147 "Fatalln": true, 148 "Panic": true, 149 "Panicf": true, 150 "Panicln": true, 151 }, 152 } 153 154 func (w *lintUnconditionalRecursionRule) hasControlExit(node ast.Node) bool { 155 // isExit returns true if the given node makes control exit the function 156 isExit := func(node ast.Node) bool { 157 switch n := node.(type) { 158 case *ast.ReturnStmt: 159 return true 160 case *ast.CallExpr: 161 if isIdent(n.Fun, "panic") { 162 return true 163 } 164 se, ok := n.Fun.(*ast.SelectorExpr) 165 if !ok { 166 return false 167 } 168 169 id, ok := se.X.(*ast.Ident) 170 if !ok { 171 return false 172 } 173 174 fn := se.Sel.Name 175 pkg := id.Name 176 if exitFunctions[pkg] != nil && exitFunctions[pkg][fn] { // it's a call to an exit function 177 return true 178 } 179 } 180 181 return false 182 } 183 184 return len(pick(node, isExit, nil)) != 0 185 }