github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/ast/inspector/inspector.go (about) 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 types argument, if non-empty, enables type-based filtering of 68 // events. The function f if is called only for nodes whose type 69 // matches an element of the types slice. 70 func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) { 71 // Because it avoids postorder calls to f, and the pruning 72 // check, Preorder is almost twice as fast as Nodes. The two 73 // features seem to contribute similar slowdowns (~1.4x each). 74 75 mask := maskOf(types) 76 for i := 0; i < len(in.events); { 77 ev := in.events[i] 78 if ev.index > i { 79 // push 80 if ev.typ&mask != 0 { 81 f(ev.node) 82 } 83 pop := ev.index 84 if in.events[pop].typ&mask == 0 { 85 // Subtrees do not contain types: skip them and pop. 86 i = pop + 1 87 continue 88 } 89 } 90 i++ 91 } 92 } 93 94 // Nodes visits the nodes of the files supplied to New in depth-first 95 // order. It calls f(n, true) for each node n before it visits n's 96 // children. If f returns true, Nodes invokes f recursively for each 97 // of the non-nil children of the node, followed by a call of 98 // f(n, false). 99 // 100 // The types argument, if non-empty, enables type-based filtering of 101 // events. The function f if is called only for nodes whose type 102 // matches an element of the types slice. 103 func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (proceed bool)) { 104 mask := maskOf(types) 105 for i := 0; i < len(in.events); { 106 ev := in.events[i] 107 if ev.index > i { 108 // push 109 pop := ev.index 110 if ev.typ&mask != 0 { 111 if !f(ev.node, true) { 112 i = pop + 1 // jump to corresponding pop + 1 113 continue 114 } 115 } 116 if in.events[pop].typ&mask == 0 { 117 // Subtrees do not contain types: skip them. 118 i = pop 119 continue 120 } 121 } else { 122 // pop 123 push := ev.index 124 if in.events[push].typ&mask != 0 { 125 f(ev.node, false) 126 } 127 } 128 i++ 129 } 130 } 131 132 // WithStack visits nodes in a similar manner to Nodes, but it 133 // supplies each call to f an additional argument, the current 134 // traversal stack. The stack's first element is the outermost node, 135 // an *ast.File; its last is the innermost, n. 136 func (in *Inspector) WithStack(types []ast.Node, f func(n ast.Node, push bool, stack []ast.Node) (proceed bool)) { 137 mask := maskOf(types) 138 var stack []ast.Node 139 for i := 0; i < len(in.events); { 140 ev := in.events[i] 141 if ev.index > i { 142 // push 143 pop := ev.index 144 stack = append(stack, ev.node) 145 if ev.typ&mask != 0 { 146 if !f(ev.node, true, stack) { 147 i = pop + 1 148 stack = stack[:len(stack)-1] 149 continue 150 } 151 } 152 if in.events[pop].typ&mask == 0 { 153 // Subtrees does not contain types: skip them. 154 i = pop 155 continue 156 } 157 } else { 158 // pop 159 push := ev.index 160 if in.events[push].typ&mask != 0 { 161 f(ev.node, false, stack) 162 } 163 stack = stack[:len(stack)-1] 164 } 165 i++ 166 } 167 } 168 169 // traverse builds the table of events representing a traversal. 170 func traverse(files []*ast.File) []event { 171 // Preallocate approximate number of events 172 // based on source file extent. 173 // This makes traverse faster by 4x (!). 174 var extent int 175 for _, f := range files { 176 extent += int(f.End() - f.Pos()) 177 } 178 // This estimate is based on the net/http package. 179 capacity := extent * 33 / 100 180 if capacity > 1e6 { 181 capacity = 1e6 // impose some reasonable maximum 182 } 183 events := make([]event, 0, capacity) 184 185 var stack []event 186 stack = append(stack, event{}) // include an extra event so file nodes have a parent 187 for _, f := range files { 188 ast.Inspect(f, func(n ast.Node) bool { 189 if n != nil { 190 // push 191 ev := event{ 192 node: n, 193 typ: 0, // temporarily used to accumulate type bits of subtree 194 index: len(events), // push event temporarily holds own index 195 } 196 stack = append(stack, ev) 197 events = append(events, ev) 198 } else { 199 // pop 200 top := len(stack) - 1 201 ev := stack[top] 202 typ := typeOf(ev.node) 203 push := ev.index 204 parent := top - 1 205 206 events[push].typ = typ // set type of push 207 stack[parent].typ |= typ | ev.typ // parent's typ contains push and pop's typs. 208 events[push].index = len(events) // make push refer to pop 209 210 stack = stack[:top] 211 events = append(events, ev) 212 } 213 return true 214 }) 215 } 216 217 return events 218 }