carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3import (
4 "bytes"
5 "go/ast"
6 "go/parser"
7 "go/printer"
8 "go/token"
9 "io/fs"
10 "path/filepath"
11 "strings"
12 "testing"
13)
14
15// String fields cannot encode the region vocabulary in their Go types. This
16// lint checks scalar comparisons, call arguments and map keys at recognizable
17// record/engine boundaries. Runtime witnesses cover local variable aliases.
18func plan13Expr(e ast.Node) string {
19 var b bytes.Buffer
20 _ = printer.Fprint(&b, token.NewFileSet(), e)
21 return b.String()
22}
23func plan13Placement(s string) bool {
24 if strings.Contains(s, "PlacementRegion(") {
25 return true
26 }
27 return s == "to" || s == "from" || s == "rec.Region" || s == "recRegion" || s == "mv.To" || s == "mv.From" || s == "m.To" || s == "m.From" || strings.HasSuffix(s, ".Move.To") || strings.HasSuffix(s, ".Move.From")
28}
29func plan13Engine(s string) bool {
30 if strings.Contains(s, ".Intent[") && (strings.HasSuffix(s, `["to"]`) || strings.HasSuffix(s, `["region"]`)) {
31 return true
32 }
33 if strings.HasSuffix(s, ".Region()") || strings.HasSuffix(s, ".Touched.Regions") || strings.HasSuffix(s, ".QuietAfter") {
34 return true
35 }
36 if strings.HasPrefix(s, `strings.TrimPrefix(`) && strings.HasSuffix(s, `.Owner, "region:")`) {
37 return plan13Engine(strings.TrimSuffix(strings.TrimPrefix(s, `strings.TrimPrefix(`), `.Owner, "region:")`) + ".Region")
38 }
39 for _, field := range []string{".Region", ".PredecessorRegion"} {
40 if strings.HasSuffix(s, field) {
41 switch strings.TrimSuffix(s, field) {
42 case "ob", "o", "a", "predecessor", "entry", "record", "box", "row", "rr.row":
43 return true
44 }
45 }
46 }
47 switch s {
48 case "actor.Region", "src.Region", "dst.Region", "b.Region", "engine.Region", "e.Region", "r.Holder.Region", "in.SourceRegion", "in.TargetRegion", "s.Region", "a.Region", "o.Region", "m.Region", "t.Region", "fence.Region", "verified.Region", "regions":
49 return true
50 }
51 return false
52}
53func plan13Operand(e ast.Expr, classify func(string) bool) bool {
54 if _, ok := e.(*ast.FuncLit); ok {
55 return false
56 }
57 if classify(plan13Expr(e)) {
58 return true
59 }
60 switch x := e.(type) {
61 case *ast.ParenExpr:
62 return plan13Operand(x.X, classify)
63 case *ast.CallExpr:
64 switch plan13Expr(x.Fun) {
65 case "park.SameRegion", "regionname.SameRegion", "SameRegion", "park.PlacementMatches", "PlacementMatches", "park.QuietUntil", "QuietUntil":
66 return false
67 }
68 for _, arg := range x.Args {
69 if plan13Operand(arg, classify) {
70 return true
71 }
72 }
73 }
74 return false
75}
76func plan13MixedRegion(x *ast.BinaryExpr) bool { return plan13MixedRegionWith(x, plan13Engine) }
77func plan13MixedRegionWith(x *ast.BinaryExpr, engine func(string) bool) bool {
78 if x.Op != token.EQL && x.Op != token.NEQ {
79 return false
80 }
81 return plan13Operand(x.X, plan13Placement) && plan13Operand(x.Y, engine) || plan13Operand(x.Y, plan13Placement) && plan13Operand(x.X, engine)
82}
83func plan13MixedRegionNode(n ast.Node) bool { return plan13MixedRegionNodeWith(n, plan13Engine) }
84func plan13MixedRegionNodeWith(n ast.Node, engine func(string) bool) bool {
85 switch x := n.(type) {
86 case *ast.BinaryExpr:
87 return plan13MixedRegionWith(x, engine)
88 case *ast.AssignStmt:
89 if len(x.Lhs) == len(x.Rhs) {
90 for i := range x.Lhs {
91 if plan13Operand(x.Lhs[i], engine) && plan13Operand(x.Rhs[i], plan13Placement) && !plan13EngineConversion(x.Rhs[i]) {
92 return true
93 }
94 }
95 }
96 case *ast.IndexExpr:
97 return plan13Operand(x.X, engine) && plan13Operand(x.Index, plan13Placement)
98 case *ast.CallExpr:
99 name := plan13Expr(x.Fun)
100 switch name {
101 case "park.SameRegion", "regionname.SameRegion", "SameRegion", "park.PlacementMatches", "PlacementMatches", "park.QuietUntil", "QuietUntil":
102 return false
103 }
104 placement, hasEngine := false, false
105 for _, arg := range x.Args {
106 placement = placement || plan13Operand(arg, plan13Placement)
107 hasEngine = hasEngine || plan13Operand(arg, engine)
108 }
109 return placement && hasEngine
110 }
111 return false
112}
113
114// Mapping a placement to an engine is valid at a store boundary, but cannot
115// replace alias-aware equality between engine identities.
116func plan13EngineConversion(e ast.Expr) bool {
117 if p, ok := e.(*ast.ParenExpr); ok {
118 return plan13EngineConversion(p.X)
119 }
120 c, ok := e.(*ast.CallExpr)
121 if !ok {
122 return false
123 }
124 switch plan13Expr(c.Fun) {
125 case "park.PlacementRegion", "regionname.PlacementRegion", "PlacementRegion":
126 return true
127 }
128 return false
129}
130
131// A scope maps scalar regions and records from engine-region collections.
132func plan13Collection(e ast.Expr) string {
133 s := plan13Expr(e)
134 if strings.HasSuffix(s, ".Touched.Regions") {
135 return "scalar"
136 }
137 if strings.HasSuffix(s, ".Admissions") || strings.HasSuffix(s, ".Obligations") {
138 return "record"
139 }
140 return ""
141}
142func plan13Scope(scope map[string]string) map[string]string {
143 next := map[string]string{}
144 for name, kind := range scope {
145 next[name] = kind
146 }
147 return next
148}
149
150type plan13Parent struct {
151 switchTag ast.Expr
152 recordType string
153}
154
155func plan13Inspect(root ast.Node, scope map[string]string, reject func(ast.Node), parents ...plan13Parent) {
156 var parent plan13Parent
157 if len(parents) > 0 {
158 parent = parents[0]
159 }
160 engine := func(s string) bool {
161 if kind, ok := scope[s]; ok {
162 return kind == "scalar"
163 }
164 for _, field := range []string{".Region", ".PredecessorRegion"} {
165 if strings.HasSuffix(s, field) {
166 if kind, ok := scope[strings.TrimSuffix(s, field)]; ok {
167 return kind == "record"
168 }
169 }
170 }
171 return plan13Engine(s)
172 }
173 ast.Inspect(root, func(n ast.Node) bool {
174 if n == nil {
175 return false
176 }
177 if plan13MixedRegionNodeWith(n, engine) {
178 reject(n)
179 }
180 switch x := n.(type) {
181 case *ast.RangeStmt:
182 plan13Inspect(x.X, scope, reject)
183 next := plan13Scope(scope)
184 if name, ok := x.Key.(*ast.Ident); ok {
185 next[name.Name] = ""
186 }
187 if name, ok := x.Value.(*ast.Ident); ok {
188 next[name.Name] = plan13Collection(x.X)
189 }
190 plan13Inspect(x.Body, next, reject)
191 return false
192 case *ast.CallExpr:
193 for _, arg := range x.Args {
194 if fn, ok := arg.(*ast.FuncLit); ok {
195 next := plan13Scope(scope)
196 for _, field := range fn.Type.Params.List {
197 for _, name := range field.Names {
198 next[name.Name] = ""
199 }
200 }
201 if len(x.Args) > 0 && len(fn.Type.Params.List) > 0 {
202 for _, name := range fn.Type.Params.List[0].Names {
203 next[name.Name] = plan13Collection(x.Args[0])
204 }
205 }
206 plan13Inspect(fn.Body, next, reject)
207 } else {
208 plan13Inspect(arg, scope, reject)
209 }
210 }
211 plan13Inspect(x.Fun, scope, reject)
212 return false
213 case *ast.FuncLit:
214 next := plan13Scope(scope)
215 for _, field := range x.Type.Params.List {
216 for _, name := range field.Names {
217 next[name.Name] = ""
218 }
219 }
220 plan13Inspect(x.Body, next, reject)
221 return false
222 case *ast.SwitchStmt:
223 if x.Init != nil {
224 plan13Inspect(x.Init, scope, reject)
225 }
226 if x.Tag != nil {
227 plan13Inspect(x.Tag, scope, reject)
228 }
229 for _, stmt := range x.Body.List {
230 plan13Inspect(stmt, scope, reject, plan13Parent{switchTag: x.Tag})
231 }
232 return false
233 case *ast.CaseClause:
234 for _, value := range x.List {
235 if parent.switchTag != nil && plan13MixedRegionWith(&ast.BinaryExpr{X: parent.switchTag, Op: token.EQL, Y: value}, engine) {
236 reject(x)
237 }
238 }
239 case *ast.CompositeLit:
240 typ := plan13Expr(x.Type)
241 for _, element := range x.Elts {
242 plan13Inspect(element, scope, reject, plan13Parent{recordType: typ[strings.LastIndex(typ, ".")+1:]})
243 }
244 return false
245 case *ast.KeyValueExpr:
246 switch parent.recordType {
247 case "Obligation", "Admission", "AdmissionRecord", "Selection":
248 if (plan13Expr(x.Key) == "Region" || plan13Expr(x.Key) == "PredecessorRegion") && plan13Operand(x.Value, plan13Placement) && !plan13EngineConversion(x.Value) {
249 reject(x)
250 }
251 }
252 }
253 return true
254 })
255}
256
257func TestPlan13NoDirectPlacementEngineComparison(t *testing.T) {
258 err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
259 if err != nil {
260 return err
261 }
262 if d.IsDir() {
263 if path != "." && strings.HasPrefix(d.Name(), ".") {
264 return filepath.SkipDir
265 }
266 return nil
267 }
268 if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
269 return nil
270 }
271 fset := token.NewFileSet()
272 f, err := parser.ParseFile(fset, path, nil, 0)
273 if err != nil {
274 return err
275 }
276 plan13Inspect(f, nil, func(n ast.Node) {
277 t.Errorf("%s: placement and engine regions need the shared rule: %s", fset.Position(n.Pos()), plan13Expr(n))
278 })
279 return nil
280 })
281 if err != nil {
282 t.Fatal(err)
283 }
284}
285
286func TestPlan13RegionComparisonLintWitness(t *testing.T) {
287 for _, expr := range []string{`rec.Region != src.Region`, `rec.Move.To == c.Op.Intent["to"]`, `in.SourceRegion == mv.From`, `park.PlacementRegion(rec.Region) != src.Region`, `rec.Move.To == s.Region`, `m.From == m.Region`, `rec.Region == fence.Region()`, `to == c.Op.Intent["to"]`, `canonical(rec.Region) == canonical(actor.Region)`} {
288 e, err := parser.ParseExpr(expr)
289 if err != nil {
290 t.Fatal(err)
291 }
292 if !plan13MixedRegion(e.(*ast.BinaryExpr)) {
293 t.Fatal("missed", expr)
294 }
295 }
296 for _, expr := range []string{`rec.Region != cfg.Region`, `rec.Move.To == rec.Region`, `receipt.Region != box.Region`, `canonical(rec.Region) != canonical(cfg.Region)`} {
297 e, err := parser.ParseExpr(expr)
298 if err != nil {
299 t.Fatal(err)
300 }
301 if plan13MixedRegion(e.(*ast.BinaryExpr)) {
302 t.Fatal("false positive", expr)
303 }
304 }
305}
306
307func TestPlan13RegionCallAndMapLintWitness(t *testing.T) {
308 for _, expr := range []string{
309 `containsString(c.Touched.Regions, rec.Move.To)`,
310 `slices.Contains(c.Touched.Regions, rec.Move.To)`,
311 `strings.EqualFold(rec.Region, actor.Region)`,
312 `same(rec.Move.From, verified.Region())`,
313 `same(canonical(rec.Move.From), canonical(verified.Region()))`,
314 `c.QuietAfter[rec.Move.To]`,
315 `c.QuietAfter[canonical(rec.Move.To)]`,
316 `regions[rec.Region]`,
317 `slices.Contains(c.Touched.Regions, park.PlacementRegion(rec.Move.To))`,
318 `c.QuietAfter[park.PlacementRegion(rec.Move.To)]`,
319 } {
320 e, err := parser.ParseExpr(expr)
321 if err != nil {
322 t.Fatal(err)
323 }
324 if !plan13MixedRegionNode(e) {
325 t.Fatal("missed", expr)
326 }
327 }
328 for _, expr := range []string{
329 `containsString(c.Touched.Boxes, box)`,
330 `strings.EqualFold(rec.Region, cfg.Region)`,
331 `park.PlacementMatches(home, rec.Region, actor.Region)`,
332 `park.SameRegion(home, park.PlacementRegion(rec.Region), actor.Region)`,
333 `park.QuietUntil(c.QuietAfter, home, park.PlacementRegion(rec.Move.To))`,
334 `fleet.targets[rec.Region]`,
335 } {
336 e, err := parser.ParseExpr(expr)
337 if err != nil {
338 t.Fatal(err)
339 }
340 if plan13MixedRegionNode(e) {
341 t.Fatal("false positive", expr)
342 }
343 }
344}
345
346func TestPlan13RegionFlowLintWitness(t *testing.T) {
347 for _, body := range []string{
348 `if park.PlacementRegion(rec.Region) == box.Region {}`,
349 `if park.PlacementRegion(rec.Region) != row.Region {}`,
350 `if park.PlacementRegion(rec.Region) == rr.row.Region {}`,
351 `if ob.Region == park.PlacementRegion(rec.Region) {}`,
352 `if predecessor.Region == park.PlacementRegion(rec.Region) {}`,
353 `if entry.PredecessorRegion == park.PlacementRegion(rec.Region) {}`,
354 `if strings.TrimPrefix(ob.Owner, "region:") == park.PlacementRegion(rec.Region) {}`,
355 `slices.ContainsFunc(c.Touched.Regions, func(r string) bool { return r == rec.Move.To })`,
356 `for _, r := range c.Touched.Regions { if r == park.PlacementRegion(rec.Move.To) {} }`,
357 `switch park.PlacementRegion(rec.Region) { case s.Region: }`,
358 `_ = hostclaim.Obligation{Region: rec.Move.To}`,
359 `s.Region = rec.Region`,
360 `if park.PlacementRegion(rec.Region) == record.Region {}`,
361 `for _, item := range c.Admissions { if item.PredecessorRegion == rec.Move.From {} }`,
362 `for _, item := range c.Obligations { if item.Region == rec.Region {} }`,
363 `slices.ContainsFunc(c.Admissions, func(item hostclaim.AdmissionRecord) bool { return item.Region == rec.Region })`,
364 `_ = hostclaim.Admission{Region: rec.Region}`,
365 `_ = hostbox.Selection{Region: rec.Region}`,
366 `s.Region, other = rec.Region, x`,
367 `f := func() { s.Region = rec.Region }; f()`,
368 } {
369 t.Run(body, func(t *testing.T) {
370 f, err := parser.ParseFile(token.NewFileSet(), "witness.go", "package p; func f(){"+body+"}", 0)
371 if err != nil {
372 t.Fatal(err)
373 }
374 found := false
375 plan13Inspect(f, nil, func(ast.Node) { found = true })
376 if !found {
377 t.Fatal("missed", body)
378 }
379 })
380 }
381}
382
383func TestPlan13RegionFlowLintAcceptsBoundaries(t *testing.T) {
384 for _, body := range []string{
385 `slices.ContainsFunc(c.Touched.Regions,func(r string) bool { return park.PlacementMatches(home, rec.Move.To,r) })`,
386 `for _,r:=range c.Touched.Regions { if park.SameRegion(home,r,park.PlacementRegion(rec.Region)) {} }`,
387 `for _,item:=range c.Admissions { if park.PlacementMatches(home,rec.Region,item.Region) {} }`,
388 `s.Region = park.PlacementRegion(rec.Region)`,
389 `_ = hostclaim.Obligation{Region: park.PlacementRegion(rec.Region)}`,
390 `_ = provision.Record{Region: rec.Region}`,
391 `switch rec.Region { case cfg.Region: }`,
392 `for _,r:=range c.Touched.Regions { use(r) }; if r == rec.Region {}`,
393 `slices.ContainsFunc(c.Touched.Regions,func(r string) bool { return true }); if r == rec.Region {}`,
394 `slices.ContainsFunc(c.Touched.Regions,func(r string) bool { return func(r string) bool { return r == rec.Region }("placement") })`,
395 } {
396 t.Run(body, func(t *testing.T) {
397 f, err := parser.ParseFile(token.NewFileSet(), "witness.go", "package p; func f(){"+body+"}", 0)
398 if err != nil {
399 t.Fatal(err)
400 }
401 plan13Inspect(f, nil, func(n ast.Node) { t.Errorf("false positive: %s", plan13Expr(n)) })
402 })
403 }
404}
405