1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package astutil
16
17 import (
18 "fmt"
19 "math/rand"
20 "strings"
21
22 "cuelang.org/go/cue/ast"
23 "cuelang.org/go/cue/errors"
24 "cuelang.org/go/cue/token"
25 )
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 func Sanitize(f *ast.File) error {
42 z := &sanitizer{
43 file: f,
44 rand: rand.New(rand.NewSource(808)),
45
46 names: map[string]bool{},
47 importMap: map[string]*ast.ImportSpec{},
48 referenced: map[ast.Node]bool{},
49 altMap: map[ast.Node]string{},
50 }
51
52
53 walk(&scope{
54 errFn: z.errf,
55 nameFn: z.addName,
56 identFn: z.markUsed,
57 }, f)
58 if z.errs != nil {
59 return z.errs
60 }
61
62
63 s := &scope{
64 file: f,
65 errFn: z.errf,
66 identFn: z.handleIdent,
67 index: make(map[string]entry),
68 }
69 z.fileScope = s
70 walk(s, f)
71 if z.errs != nil {
72 return z.errs
73 }
74
75 z.cleanImports()
76
77 return z.errs
78 }
79
80 type sanitizer struct {
81 file *ast.File
82 fileScope *scope
83
84 rand *rand.Rand
85
86
87 names map[string]bool
88 referenced map[ast.Node]bool
89
90
91
92
93 altMap map[ast.Node]string
94 importMap map[string]*ast.ImportSpec
95
96 errs errors.Error
97 }
98
99 func (z *sanitizer) errf(p token.Pos, msg string, args ...interface{}) {
100 z.errs = errors.Append(z.errs, errors.Newf(p, msg, args...))
101 }
102
103 func (z *sanitizer) addName(name string) {
104 z.names[name] = true
105 }
106
107 func (z *sanitizer) addRename(base string, n ast.Node) (alt string, new bool) {
108 if name, ok := z.altMap[n]; ok {
109 return name, false
110 }
111
112 name := z.uniqueName(base, false)
113 z.altMap[n] = name
114 return name, true
115 }
116
117 func (z *sanitizer) unshadow(parent ast.Node, base string, link ast.Node) string {
118 name, ok := z.altMap[link]
119 if !ok {
120 name = z.uniqueName(base, false)
121 z.altMap[link] = name
122
123
124
125 let := &ast.LetClause{
126 Ident: ast.NewIdent(name),
127 Expr: ast.NewIdent(base),
128 }
129
130 var decls *[]ast.Decl
131
132 switch x := parent.(type) {
133 case *ast.File:
134 decls = &x.Decls
135 case *ast.StructLit:
136 decls = &x.Elts
137 default:
138 panic(fmt.Sprintf("impossible scope type %T", parent))
139 }
140
141 i := 0
142 for ; i < len(*decls); i++ {
143 if (*decls)[i] == link {
144 break
145 }
146 if f, ok := (*decls)[i].(*ast.Field); ok && f.Label == link {
147 break
148 }
149 }
150
151 if i > 0 {
152 ast.SetRelPos(let, token.NewSection)
153 }
154
155 a := append((*decls)[:i:i], let)
156 *decls = append(a, (*decls)[i:]...)
157 }
158 return name
159 }
160
161 func (z *sanitizer) markUsed(s *scope, n *ast.Ident) bool {
162 if n.Node != nil {
163 return false
164 }
165 _, _, entry := s.lookup(n.String())
166 z.referenced[entry.link] = true
167 return true
168 }
169
170 func (z *sanitizer) cleanImports() {
171 z.file.VisitImports(func(d *ast.ImportDecl) {
172 k := 0
173 for _, s := range d.Specs {
174 if _, ok := z.referenced[s]; ok {
175 d.Specs[k] = s
176 k++
177 }
178 }
179 d.Specs = d.Specs[:k]
180 })
181 }
182
183 func (z *sanitizer) handleIdent(s *scope, n *ast.Ident) bool {
184 if n.Node == nil {
185 return true
186 }
187
188 _, _, node := s.lookup(n.Name)
189 if node.node == nil {
190 spec, ok := n.Node.(*ast.ImportSpec)
191 if !ok {
192
193
194 n.Node = nil
195 n.Scope = nil
196 return false
197 }
198
199 _ = z.addImport(spec)
200 info, _ := ParseImportSpec(spec)
201 z.fileScope.insert(info.Ident, spec, spec)
202 return true
203 }
204
205 if x, ok := n.Node.(*ast.ImportSpec); ok {
206 xi, _ := ParseImportSpec(x)
207
208 if y, ok := node.node.(*ast.ImportSpec); ok {
209 yi, _ := ParseImportSpec(y)
210 if xi.ID == yi.ID {
211 z.referenced[y] = true
212 n.Node = x
213 n.Scope = nil
214 return false
215 }
216 }
217
218
219
220
221
222
223 spec := z.importMap[xi.ID]
224 if spec == nil {
225 name := z.uniqueName(xi.Ident, false)
226 spec = z.addImport(&ast.ImportSpec{
227 Name: ast.NewIdent(name),
228 Path: x.Path,
229 })
230 z.importMap[xi.ID] = spec
231 z.fileScope.insert(name, spec, spec)
232 }
233
234 info, _ := ParseImportSpec(spec)
235
236 n.Name = info.Ident
237 n.Node = spec
238 n.Scope = nil
239 return false
240 }
241
242 if node.node == n.Node {
243 return true
244 }
245
246
247
248
249
250 parent, e, ok := s.resolveScope(n.Name, n.Node)
251 if !ok {
252
253
254
255
256
257
258
259 n.Name = z.unshadow(z.file, n.Name, n)
260 n.Node = nil
261 n.Scope = nil
262
263 return false
264 }
265
266 var name string
267
268 switch x := e.link.(type) {
269 case *ast.Field:
270 name, ok = z.altMap[x]
271 if ok {
272 break
273 }
274
275
276
277
278 switch y := x.Label.(type) {
279 case *ast.Alias:
280 name = z.unshadow(parent, y.Ident.Name, y)
281
282 case *ast.Ident:
283 var isNew bool
284 name, isNew = z.addRename(y.Name, x)
285 if isNew {
286 ident := ast.NewIdent(name)
287
288
289 CopyMeta(ident, y)
290 ast.SetRelPos(y, token.NoRelPos)
291 ast.SetComments(y, nil)
292 x.Label = &ast.Alias{Ident: ident, Expr: y}
293 }
294
295 default:
296
297 return false
298 }
299
300 case *ast.LetClause:
301 name = z.unshadow(parent, x.Ident.Name, x)
302
303 case *ast.Alias:
304 name = z.unshadow(parent, x.Ident.Name, x)
305
306 default:
307 panic(fmt.Sprintf("unexpected link type %T", e.link))
308 }
309
310
311
312 n.Name = name
313 n.Node = nil
314 n.Scope = nil
315
316 return true
317 }
318
319
320
321
322
323
324
325
326 func (z *sanitizer) uniqueName(base string, hidden bool) string {
327 if hidden && !strings.HasPrefix(base, "_") {
328 base = "_" + base
329 if !z.names[base] {
330 z.names[base] = true
331 return base
332 }
333 }
334
335 const mask = 0xff_ffff_ffff_ffff
336 const shift = 4
337 for n := int64(0x10); ; n = int64(mask&((n<<shift)-1)) + 1 {
338 num := z.rand.Intn(int(n))
339 name := fmt.Sprintf("%s_%01X", base, num)
340 if !z.names[name] {
341 z.names[name] = true
342 return name
343 }
344 }
345 }
346
347 func (z *sanitizer) addImport(spec *ast.ImportSpec) *ast.ImportSpec {
348 spec = insertImport(&z.file.Decls, spec)
349 z.referenced[spec] = true
350 return spec
351 }
352
View as plain text