1 | // Copyright 2017 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 pointer |
6 | |
7 | import ( |
8 | "errors" |
9 | "fmt" |
10 | "go/ast" |
11 | "go/parser" |
12 | "go/token" |
13 | "go/types" |
14 | "strconv" |
15 | ) |
16 | |
17 | // An extendedQuery represents a sequence of destructuring operations |
18 | // applied to an ssa.Value (denoted by "x"). |
19 | type extendedQuery struct { |
20 | ops []interface{} |
21 | ptr *Pointer |
22 | } |
23 | |
24 | // indexValue returns the value of an integer literal used as an |
25 | // index. |
26 | func indexValue(expr ast.Expr) (int, error) { |
27 | lit, ok := expr.(*ast.BasicLit) |
28 | if !ok { |
29 | return 0, fmt.Errorf("non-integer index (%T)", expr) |
30 | } |
31 | if lit.Kind != token.INT { |
32 | return 0, fmt.Errorf("non-integer index %s", lit.Value) |
33 | } |
34 | return strconv.Atoi(lit.Value) |
35 | } |
36 | |
37 | // parseExtendedQuery parses and validates a destructuring Go |
38 | // expression and returns the sequence of destructuring operations. |
39 | // See parseDestructuringExpr for details. |
40 | func parseExtendedQuery(typ types.Type, query string) ([]interface{}, types.Type, error) { |
41 | expr, err := parser.ParseExpr(query) |
42 | if err != nil { |
43 | return nil, nil, err |
44 | } |
45 | ops, typ, err := destructuringOps(typ, expr) |
46 | if err != nil { |
47 | return nil, nil, err |
48 | } |
49 | if len(ops) == 0 { |
50 | return nil, nil, errors.New("invalid query: must not be empty") |
51 | } |
52 | if ops[0] != "x" { |
53 | return nil, nil, fmt.Errorf("invalid query: query operand must be named x") |
54 | } |
55 | if !CanPoint(typ) { |
56 | return nil, nil, fmt.Errorf("query does not describe a pointer-like value: %s", typ) |
57 | } |
58 | return ops, typ, nil |
59 | } |
60 | |
61 | // destructuringOps parses a Go expression consisting only of an |
62 | // identifier "x", field selections, indexing, channel receives, load |
63 | // operations and parens---for example: "<-(*x[i])[key]"--- and |
64 | // returns the sequence of destructuring operations on x. |
65 | func destructuringOps(typ types.Type, expr ast.Expr) ([]interface{}, types.Type, error) { |
66 | switch expr := expr.(type) { |
67 | case *ast.SelectorExpr: |
68 | out, typ, err := destructuringOps(typ, expr.X) |
69 | if err != nil { |
70 | return nil, nil, err |
71 | } |
72 | |
73 | var structT *types.Struct |
74 | switch typ := typ.Underlying().(type) { |
75 | case *types.Pointer: |
76 | var ok bool |
77 | structT, ok = typ.Elem().Underlying().(*types.Struct) |
78 | if !ok { |
79 | return nil, nil, fmt.Errorf("cannot access field %s of pointer to type %s", expr.Sel.Name, typ.Elem()) |
80 | } |
81 | |
82 | out = append(out, "load") |
83 | case *types.Struct: |
84 | structT = typ |
85 | default: |
86 | return nil, nil, fmt.Errorf("cannot access field %s of type %s", expr.Sel.Name, typ) |
87 | } |
88 | |
89 | for i := 0; i < structT.NumFields(); i++ { |
90 | field := structT.Field(i) |
91 | if field.Name() == expr.Sel.Name { |
92 | out = append(out, "field", i) |
93 | return out, field.Type().Underlying(), nil |
94 | } |
95 | } |
96 | // TODO(dh): supporting embedding would need something like |
97 | // types.LookupFieldOrMethod, but without taking package |
98 | // boundaries into account, because we may want to access |
99 | // unexported fields. If we were only interested in one level |
100 | // of unexported name, we could determine the appropriate |
101 | // package and run LookupFieldOrMethod with that. However, a |
102 | // single query may want to cross multiple package boundaries, |
103 | // and at this point it's not really worth the complexity. |
104 | return nil, nil, fmt.Errorf("no field %s in %s (embedded fields must be resolved manually)", expr.Sel.Name, structT) |
105 | case *ast.Ident: |
106 | return []interface{}{expr.Name}, typ, nil |
107 | case *ast.BasicLit: |
108 | return []interface{}{expr.Value}, nil, nil |
109 | case *ast.IndexExpr: |
110 | out, typ, err := destructuringOps(typ, expr.X) |
111 | if err != nil { |
112 | return nil, nil, err |
113 | } |
114 | switch typ := typ.Underlying().(type) { |
115 | case *types.Array: |
116 | out = append(out, "arrayelem") |
117 | return out, typ.Elem().Underlying(), nil |
118 | case *types.Slice: |
119 | out = append(out, "sliceelem") |
120 | return out, typ.Elem().Underlying(), nil |
121 | case *types.Map: |
122 | out = append(out, "mapelem") |
123 | return out, typ.Elem().Underlying(), nil |
124 | case *types.Tuple: |
125 | out = append(out, "index") |
126 | idx, err := indexValue(expr.Index) |
127 | if err != nil { |
128 | return nil, nil, err |
129 | } |
130 | out = append(out, idx) |
131 | if idx >= typ.Len() || idx < 0 { |
132 | return nil, nil, fmt.Errorf("tuple index %d out of bounds", idx) |
133 | } |
134 | return out, typ.At(idx).Type().Underlying(), nil |
135 | default: |
136 | return nil, nil, fmt.Errorf("cannot index type %s", typ) |
137 | } |
138 | |
139 | case *ast.UnaryExpr: |
140 | if expr.Op != token.ARROW { |
141 | return nil, nil, fmt.Errorf("unsupported unary operator %s", expr.Op) |
142 | } |
143 | out, typ, err := destructuringOps(typ, expr.X) |
144 | if err != nil { |
145 | return nil, nil, err |
146 | } |
147 | ch, ok := typ.(*types.Chan) |
148 | if !ok { |
149 | return nil, nil, fmt.Errorf("cannot receive from value of type %s", typ) |
150 | } |
151 | out = append(out, "recv") |
152 | return out, ch.Elem().Underlying(), err |
153 | case *ast.ParenExpr: |
154 | return destructuringOps(typ, expr.X) |
155 | case *ast.StarExpr: |
156 | out, typ, err := destructuringOps(typ, expr.X) |
157 | if err != nil { |
158 | return nil, nil, err |
159 | } |
160 | ptr, ok := typ.(*types.Pointer) |
161 | if !ok { |
162 | return nil, nil, fmt.Errorf("cannot dereference type %s", typ) |
163 | } |
164 | out = append(out, "load") |
165 | return out, ptr.Elem().Underlying(), err |
166 | default: |
167 | return nil, nil, fmt.Errorf("unsupported expression %T", expr) |
168 | } |
169 | } |
170 | |
171 | func (a *analysis) evalExtendedQuery(t types.Type, id nodeid, ops []interface{}) (types.Type, nodeid) { |
172 | pid := id |
173 | // TODO(dh): we're allocating intermediary nodes each time |
174 | // evalExtendedQuery is called. We should probably only generate |
175 | // them once per (v, ops) pair. |
176 | for i := 1; i < len(ops); i++ { |
177 | var nid nodeid |
178 | switch ops[i] { |
179 | case "recv": |
180 | t = t.(*types.Chan).Elem().Underlying() |
181 | nid = a.addNodes(t, "query.extended") |
182 | a.load(nid, pid, 0, a.sizeof(t)) |
183 | case "field": |
184 | i++ // fetch field index |
185 | tt := t.(*types.Struct) |
186 | idx := ops[i].(int) |
187 | offset := a.offsetOf(t, idx) |
188 | t = tt.Field(idx).Type().Underlying() |
189 | nid = a.addNodes(t, "query.extended") |
190 | a.copy(nid, pid+nodeid(offset), a.sizeof(t)) |
191 | case "arrayelem": |
192 | t = t.(*types.Array).Elem().Underlying() |
193 | nid = a.addNodes(t, "query.extended") |
194 | a.copy(nid, 1+pid, a.sizeof(t)) |
195 | case "sliceelem": |
196 | t = t.(*types.Slice).Elem().Underlying() |
197 | nid = a.addNodes(t, "query.extended") |
198 | a.load(nid, pid, 1, a.sizeof(t)) |
199 | case "mapelem": |
200 | tt := t.(*types.Map) |
201 | t = tt.Elem() |
202 | ksize := a.sizeof(tt.Key()) |
203 | vsize := a.sizeof(tt.Elem()) |
204 | nid = a.addNodes(t, "query.extended") |
205 | a.load(nid, pid, ksize, vsize) |
206 | case "index": |
207 | i++ // fetch index |
208 | tt := t.(*types.Tuple) |
209 | idx := ops[i].(int) |
210 | t = tt.At(idx).Type().Underlying() |
211 | nid = a.addNodes(t, "query.extended") |
212 | a.copy(nid, pid+nodeid(idx), a.sizeof(t)) |
213 | case "load": |
214 | t = t.(*types.Pointer).Elem().Underlying() |
215 | nid = a.addNodes(t, "query.extended") |
216 | a.load(nid, pid, 0, a.sizeof(t)) |
217 | default: |
218 | // shouldn't happen |
219 | panic(fmt.Sprintf("unknown op %q", ops[i])) |
220 | } |
221 | pid = nid |
222 | } |
223 | |
224 | return t, pid |
225 | } |
226 |
Members