Source file src/cmd/vendor/golang.org/x/tools/go/ast/inspector/inspector.go
1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package inspector provides helper functions for traversal over the 6 // syntax trees of a package, including node filtering by type, and 7 // materialization of the traversal stack. 8 // 9 // During construction, the inspector does a complete traversal and 10 // builds a list of push/pop events and their node type. Subsequent 11 // method calls that request a traversal scan this list, rather than walk 12 // the AST, and perform type filtering using efficient bit sets. 13 // 14 // Experiments suggest the inspector's traversals are about 2.5x faster 15 // than ast.Inspect, but it may take around 5 traversals for this 16 // benefit to amortize the inspector's construction cost. 17 // If efficiency is the primary concern, do not use Inspector for 18 // one-off traversals. 19 package inspector 20 21 // There are four orthogonal features in a traversal: 22 // 1 type filtering 23 // 2 pruning 24 // 3 postorder calls to f 25 // 4 stack 26 // Rather than offer all of them in the API, 27 // only a few combinations are exposed: 28 // - Preorder is the fastest and has fewest features, 29 // but is the most commonly needed traversal. 30 // - Nodes and WithStack both provide pruning and postorder calls, 31 // even though few clients need it, because supporting two versions 32 // is not justified. 33 // More combinations could be supported by expressing them as 34 // wrappers around a more generic traversal, but this was measured 35 // and found to degrade performance significantly (30%). 36 37 import ( 38 "go/ast" 39 ) 40 41 // An Inspector provides methods for inspecting 42 // (traversing) the syntax trees of a package. 43 type Inspector struct { 44 events []event 45 } 46 47 // New returns an Inspector for the specified syntax trees. 48 func New(files []*ast.File) *Inspector { 49 return &Inspector{traverse(files)} 50 } 51 52 // An event represents a push or a pop 53 // of an ast.Node during a traversal. 54 type event struct { 55 node ast.Node 56 typ uint64 // typeOf(node) on push event, or union of typ strictly between push and pop events on pop events 57 index int // index of corresponding push or pop event 58 } 59 60 // TODO: Experiment with storing only the second word of event.node (unsafe.Pointer). 61 // Type can be recovered from the sole bit in typ. 62 63 // Preorder visits all the nodes of the files supplied to New in 64 // depth-first order. It calls f(n) for each node n before it visits 65 // n's children. 66 // 67 // The complete traversal sequence is determined by ast.Inspect. 68 // The types argument, if non-empty, enables type-based filtering of 69 // events. The function f is called only for nodes whose type 70 // matches an element of the types slice. 71 func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) { 72 // Because it avoids postorder calls to f, and the pruning 73 // check, Preorder is almost twice as fast as Nodes. The two 74 // features seem to contribute similar slowdowns (~1.4x each). 75 76 // This function is equivalent to the PreorderSeq call below, 77 // but to avoid the additional dynamic call (which adds 13-35% 78 // to the benchmarks), we expand it out. 79 // 80 // in.PreorderSeq(types...)(func(n ast.Node) bool { 81 // f(n) 82 // return true 83 // }) 84 85 mask := maskOf(types) 86 for i := 0; i < len(in.events); { 87 ev := in.events[i] 88 if ev.index > i { 89 // push 90 if ev.typ&mask != 0 { 91 f(ev.node) 92 } 93 pop := ev.index 94 if in.events[pop].typ&mask == 0 { 95 // Subtrees do not contain types: skip them and pop. 96 i = pop + 1 97 continue 98 } 99 } 100 i++ 101 } 102 } 103 104 // Nodes visits the nodes of the files supplied to New in depth-first 105 // order. It calls f(n, true) for each node n before it visits n's 106 // children. If f returns true, Nodes invokes f recursively for each 107 // of the non-nil children of the node, followed by a call of 108 // f(n, false). 109 // 110 // The complete traversal sequence is determined by ast.Inspect. 111 // The types argument, if non-empty, enables type-based filtering of 112 // events. The function f if is called only for nodes whose type 113 // matches an element of the types slice. 114 func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (proceed bool)) { 115 mask := maskOf(types) 116 for i := 0; i < len(in.events); { 117 ev := in.events[i] 118 if ev.index > i { 119 // push 120 pop := ev.index 121 if ev.typ&mask != 0 { 122 if !f(ev.node, true) { 123 i = pop + 1 // jump to corresponding pop + 1 124 continue 125 } 126 } 127 if in.events[pop].typ&mask == 0 { 128 // Subtrees do not contain types: skip them. 129 i = pop 130 continue 131 } 132 } else { 133 // pop 134 push := ev.index 135 if in.events[push].typ&mask != 0 { 136 f(ev.node, false) 137 } 138 } 139 i++ 140 } 141 } 142 143 // WithStack visits nodes in a similar manner to Nodes, but it 144 // supplies each call to f an additional argument, the current 145 // traversal stack. The stack's first element is the outermost node, 146 // an *ast.File; its last is the innermost, n. 147 func (in *Inspector) WithStack(types []ast.Node, f func(n ast.Node, push bool, stack []ast.Node) (proceed bool)) { 148 mask := maskOf(types) 149 var stack []ast.Node 150 for i := 0; i < len(in.events); { 151 ev := in.events[i] 152 if ev.index > i { 153 // push 154 pop := ev.index 155 stack = append(stack, ev.node) 156 if ev.typ&mask != 0 { 157 if !f(ev.node, true, stack) { 158 i = pop + 1 159 stack = stack[:len(stack)-1] 160 continue 161 } 162 } 163 if in.events[pop].typ&mask == 0 { 164 // Subtrees does not contain types: skip them. 165 i = pop 166 continue 167 } 168 } else { 169 // pop 170 push := ev.index 171 if in.events[push].typ&mask != 0 { 172 f(ev.node, false, stack) 173 } 174 stack = stack[:len(stack)-1] 175 } 176 i++ 177 } 178 } 179 180 // traverse builds the table of events representing a traversal. 181 func traverse(files []*ast.File) []event { 182 // Preallocate approximate number of events 183 // based on source file extent of the declarations. 184 // (We use End-Pos not FileStart-FileEnd to neglect 185 // the effect of long doc comments.) 186 // This makes traverse faster by 4x (!). 187 var extent int 188 for _, f := range files { 189 extent += int(f.End() - f.Pos()) 190 } 191 // This estimate is based on the net/http package. 192 capacity := extent * 33 / 100 193 if capacity > 1e6 { 194 capacity = 1e6 // impose some reasonable maximum 195 } 196 events := make([]event, 0, capacity) 197 198 var stack []event 199 stack = append(stack, event{}) // include an extra event so file nodes have a parent 200 for _, f := range files { 201 ast.Inspect(f, func(n ast.Node) bool { 202 if n != nil { 203 // push 204 ev := event{ 205 node: n, 206 typ: 0, // temporarily used to accumulate type bits of subtree 207 index: len(events), // push event temporarily holds own index 208 } 209 stack = append(stack, ev) 210 events = append(events, ev) 211 } else { 212 // pop 213 top := len(stack) - 1 214 ev := stack[top] 215 typ := typeOf(ev.node) 216 push := ev.index 217 parent := top - 1 218 219 events[push].typ = typ // set type of push 220 stack[parent].typ |= typ | ev.typ // parent's typ contains push and pop's typs. 221 events[push].index = len(events) // make push refer to pop 222 223 stack = stack[:top] 224 events = append(events, ev) 225 } 226 return true 227 }) 228 } 229 230 return events 231 } 232