Skip to content

Commit b010875

Browse files
committed
internal/core/adt: fix hang on cyclic pattern constraints
This issue triggered a cyclic evaluation in pattern constraints. It seems like there is still an issue as the evaluation of `x` in`and` may trigger a cyclic evaluation of `and`. However, `and` only does a shallow evaluation, and inserting `x`'s conjuncts into itself is identified as idempotent and thus further evaluation is skipped. The same mechanism does not work for pattern constraints (...T in this case), so we disable evaluation there. Note that v2 does not evaluate patterns either. It is not strictly necessary, but increases precision with subsumption and equality, which in turn improves efficiency of disambiguating disjunctions. Fixes #3326 Signed-off-by: Marcel van Lohuizen <[email protected]> Change-Id: I5ac7f5a19c72de64d95bbdb186e2393e6bc824a1 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1202604 Unity-Result: CUE porcuepine <[email protected]> Reviewed-by: Matthew Sackman <[email protected]> TryBot-Result: CUEcueckoo <[email protected]>
1 parent 3c40786 commit b010875

File tree

11 files changed

+220
-55
lines changed

11 files changed

+220
-55
lines changed

cue/testdata/cycle/evaluate.txtar

+42-25
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ letCycleOK.t2.a.X: structural cycle:
133133
./in.cue:23:11
134134
letCycleFail.t1.a.X: structural cycle:
135135
./in.cue:33:11
136-
disjunctionCycle.b: cannot use 1 (type int) as list in argument 1 to and:
136+
disjunctionCycle.b: cannot use 1 (type int) as type list:
137+
./in.cue:56:5
137138
./in.cue:56:9
138139
b: structural cycle:
139140
./in.cue:62:6
@@ -211,15 +212,18 @@ Result:
211212
disjunctionCycle: (_|_){
212213
// [eval]
213214
a: (_|_){
214-
// [eval] disjunctionCycle.b: cannot use 1 (type int) as list in argument 1 to and:
215+
// [eval] disjunctionCycle.b: cannot use 1 (type int) as type list:
216+
// ./in.cue:56:5
215217
// ./in.cue:56:9
216218
}
217219
b: (_|_){
218-
// [eval] disjunctionCycle.b: cannot use 1 (type int) as list in argument 1 to and:
220+
// [eval] disjunctionCycle.b: cannot use 1 (type int) as type list:
221+
// ./in.cue:56:5
219222
// ./in.cue:56:9
220223
}
221224
c: (_|_){
222-
// [eval] disjunctionCycle.b: cannot use 1 (type int) as list in argument 1 to and:
225+
// [eval] disjunctionCycle.b: cannot use 1 (type int) as type list:
226+
// ./in.cue:56:5
223227
// ./in.cue:56:9
224228
}
225229
}
@@ -349,7 +353,7 @@ Result:
349353
diff old new
350354
--- old
351355
+++ new
352-
@@ -1,47 +1,49 @@
356+
@@ -1,50 +1,50 @@
353357
Errors:
354358
-closeCycle.a: structural cycle
355359
-closeCycle.b.d: structural cycle
@@ -361,17 +365,20 @@ diff old new
361365
-letCycleFail.t1.a.c: structural cycle
362366
-structCycle.a: structural cycle
363367
-structCycle.b.d: structural cycle
364-
-disjunctionCycle.a: cannot use 1 (type int) as list in argument 1 to and:
368+
-disjunctionCycle.a: cannot use 1 (type int) as type list:
369+
- ./in.cue:56:5
365370
- ./in.cue:56:9
366371
+printCycle.a.X: structural cycle
367372
+structCycle.c: structural cycle
368373
+letCycleOK.t2.a.X: structural cycle:
369374
+ ./in.cue:23:11
370375
+letCycleFail.t1.a.X: structural cycle:
371376
+ ./in.cue:33:11
372-
disjunctionCycle.b: cannot use 1 (type int) as list in argument 1 to and:
377+
disjunctionCycle.b: cannot use 1 (type int) as type list:
378+
./in.cue:56:5
373379
./in.cue:56:9
374-
-disjunctionCycle.c: cannot use 1 (type int) as list in argument 1 to and:
380+
-disjunctionCycle.c: cannot use 1 (type int) as type list:
381+
- ./in.cue:56:5
375382
- ./in.cue:56:9
376383
b: structural cycle:
377384
./in.cue:62:6
@@ -424,7 +431,7 @@ diff old new
424431
}
425432
}
426433
}
427-
@@ -56,20 +58,16 @@
434+
@@ -59,20 +59,16 @@
428435
// [structural cycle] letCycleFail.t1.a.X: structural cycle
429436
}
430437
c: (_|_){
@@ -455,32 +462,36 @@ diff old new
455462
}
456463
x: (struct){
457464
y: (string){ "" }
458-
@@ -85,15 +83,15 @@
465+
@@ -88,17 +84,17 @@
459466
disjunctionCycle: (_|_){
460467
// [eval]
461468
a: (_|_){
462-
- // [eval] disjunctionCycle.a: cannot use 1 (type int) as list in argument 1 to and:
469+
- // [eval] disjunctionCycle.a: cannot use 1 (type int) as type list:
470+
- // ./in.cue:56:5
463471
- // ./in.cue:56:9
464472
- }
465473
- b: (_|_){
466-
- // [eval] disjunctionCycle.b: cannot use 1 (type int) as list in argument 1 to and:
474+
- // [eval] disjunctionCycle.b: cannot use 1 (type int) as type list:
475+
- // ./in.cue:56:5
467476
- // ./in.cue:56:9
468477
- }
469478
- c: (_|_){
470-
- // [eval] disjunctionCycle.c: cannot use 1 (type int) as list in argument 1 to and:
471-
+ // [eval] disjunctionCycle.b: cannot use 1 (type int) as list in argument 1 to and:
479+
- // [eval] disjunctionCycle.c: cannot use 1 (type int) as type list:
480+
+ // [eval] disjunctionCycle.b: cannot use 1 (type int) as type list:
481+
+ // ./in.cue:56:5
472482
+ // ./in.cue:56:9
473483
+ }
474484
+ b: (_|_){
475-
+ // [eval] disjunctionCycle.b: cannot use 1 (type int) as list in argument 1 to and:
485+
+ // [eval] disjunctionCycle.b: cannot use 1 (type int) as type list:
486+
+ // ./in.cue:56:5
476487
+ // ./in.cue:56:9
477488
+ }
478489
+ c: (_|_){
479-
+ // [eval] disjunctionCycle.b: cannot use 1 (type int) as list in argument 1 to and:
490+
+ // [eval] disjunctionCycle.b: cannot use 1 (type int) as type list:
491+
// ./in.cue:56:5
480492
// ./in.cue:56:9
481493
}
482-
}
483-
@@ -118,80 +116,79 @@
494+
@@ -124,80 +120,79 @@
484495
}
485496
b: (struct){
486497
}
@@ -611,7 +622,7 @@ diff old new
611622
}
612623
}
613624
closeFail: (_|_){
614-
@@ -201,21 +198,22 @@
625+
@@ -207,21 +202,22 @@
615626
}
616627
x: (_|_){
617628
// [eval]
@@ -653,11 +664,14 @@ closeFail.x.b: field not allowed:
653664
letCycleFail.t1.a.c: structural cycle
654665
structCycle.a: structural cycle
655666
structCycle.b.d: structural cycle
656-
disjunctionCycle.a: cannot use 1 (type int) as list in argument 1 to and:
667+
disjunctionCycle.a: cannot use 1 (type int) as type list:
668+
./in.cue:56:5
657669
./in.cue:56:9
658-
disjunctionCycle.b: cannot use 1 (type int) as list in argument 1 to and:
670+
disjunctionCycle.b: cannot use 1 (type int) as type list:
671+
./in.cue:56:5
659672
./in.cue:56:9
660-
disjunctionCycle.c: cannot use 1 (type int) as list in argument 1 to and:
673+
disjunctionCycle.c: cannot use 1 (type int) as type list:
674+
./in.cue:56:5
661675
./in.cue:56:9
662676
b: structural cycle:
663677
./in.cue:62:6
@@ -729,15 +743,18 @@ Result:
729743
disjunctionCycle: (_|_){
730744
// [eval]
731745
a: (_|_){
732-
// [eval] disjunctionCycle.a: cannot use 1 (type int) as list in argument 1 to and:
746+
// [eval] disjunctionCycle.a: cannot use 1 (type int) as type list:
747+
// ./in.cue:56:5
733748
// ./in.cue:56:9
734749
}
735750
b: (_|_){
736-
// [eval] disjunctionCycle.b: cannot use 1 (type int) as list in argument 1 to and:
751+
// [eval] disjunctionCycle.b: cannot use 1 (type int) as type list:
752+
// ./in.cue:56:5
737753
// ./in.cue:56:9
738754
}
739755
c: (_|_){
740-
// [eval] disjunctionCycle.c: cannot use 1 (type int) as list in argument 1 to and:
756+
// [eval] disjunctionCycle.c: cannot use 1 (type int) as type list:
757+
// ./in.cue:56:5
741758
// ./in.cue:56:9
742759
}
743760
}

cue/testdata/cycle/patterns.txtar

+69
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Excluded from V2, which no longer passes.
12
Lots of cycle-reference goodness.
23

34
-- in.cue --
@@ -9,6 +10,19 @@ c: a: int
910
a: b
1011
b: [string]: int
1112
c: a: int
13+
14+
-- issue3326.cue --
15+
issue3326: {
16+
// This issue triggered a cyclic evaluation in pattern constraints.
17+
//
18+
// It seems like there is still an issue as the evaluation of `x` in
19+
// `and` may trigger a cyclic evaluation of `and`. However, `and` only
20+
// does a shallow evaluation, and inserting `x`'s conjuncts into itself
21+
// is identified as idempotent and thus further evaluation is skipped.
22+
_self: x: [...and(x)]
23+
_self
24+
x: [1]
25+
}
1226
-- out/eval/stats --
1327
Leaks: 0
1428
Freed: 7
@@ -19,6 +33,47 @@ Retain: 2
1933
Unifications: 7
2034
Conjuncts: 59
2135
Disjuncts: 9
36+
-- out/evalalpha --
37+
(struct){
38+
a: (struct){
39+
a: (int){ int }
40+
}
41+
b: (struct){
42+
a: (int){ int }
43+
}
44+
c: (struct){
45+
a: (int){ int }
46+
}
47+
issue3326: (struct){
48+
_self: (struct){
49+
x: (list){
50+
}
51+
}
52+
x: (#list){
53+
0: (int){ 1 }
54+
}
55+
a: (int){ int }
56+
}
57+
}
58+
-- diff/-out/evalalpha<==>+out/eval --
59+
diff old new
60+
--- old
61+
+++ new
62+
@@ -8,4 +8,14 @@
63+
c: (struct){
64+
a: (int){ int }
65+
}
66+
+ issue3326: (struct){
67+
+ _self: (struct){
68+
+ x: (list){
69+
+ }
70+
+ }
71+
+ x: (#list){
72+
+ 0: (int){ 1 }
73+
+ }
74+
+ a: (int){ int }
75+
+ }
76+
}
2277
-- out/eval --
2378
(struct){
2479
a: (struct){
@@ -51,3 +106,17 @@ Disjuncts: 9
51106
a: int
52107
}
53108
}
109+
--- issue3326.cue
110+
{
111+
issue3326: {
112+
_self: {
113+
x: [
114+
...and(〈1;x〉),
115+
]
116+
}
117+
〈0;_self〉
118+
x: [
119+
1,
120+
]
121+
}
122+
}

internal/core/adt/composite.go

+4
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ type Vertex struct {
206206
// Used for cycle detection.
207207
IsDynamic bool
208208

209+
// IsPatternConstraint indicates that this Vertex is an entry in
210+
// Vertex.PatternConstraints.
211+
IsPatternConstraint bool
212+
209213
// nonRooted indicates that this Vertex originates within the context of
210214
// a dynamic, or inlined, Vertex (e.g. `{out: ...}.out``). Note that,
211215
// through reappropriation, this Vertex may become rooted down the line.

internal/core/adt/constraints.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,10 @@ func (n *nodeContext) insertConstraint(pattern Value, c Conjunct) bool {
9393
}
9494

9595
if constraint == nil {
96-
constraint = &Vertex{}
96+
constraint = &Vertex{
97+
// See "Self-referencing patterns" in cycle.go
98+
IsPatternConstraint: true,
99+
}
97100
pcs.Pairs = append(pcs.Pairs, PatternConstraint{
98101
Pattern: pattern,
99102
Constraint: constraint,

internal/core/adt/context.go

+36-11
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,20 @@ func (c *OpContext) Evaluate(env *Environment, x Expr) (result Value, complete b
656656
return val, true
657657
}
658658

659+
// EvaluateKeepState does an evaluate, but leaves any errors an cycle info
660+
// within the context.
661+
func (c *OpContext) EvaluateKeepState(x Expr) (result Value) {
662+
src := c.src
663+
c.src = x.Source()
664+
665+
result, ci := c.evalStateCI(x, final(partial, concreteKnown))
666+
667+
c.src = src
668+
c.ci = ci
669+
670+
return result
671+
}
672+
659673
func (c *OpContext) evaluateRec(v Conjunct, state combinedFlags) Value {
660674
x := v.Expr()
661675
s := c.PushConjunct(v)
@@ -687,10 +701,18 @@ func (c *OpContext) value(x Expr, state combinedFlags) (result Value) {
687701
}
688702

689703
func (c *OpContext) evalState(v Expr, state combinedFlags) (result Value) {
704+
result, _ = c.evalStateCI(v, state)
705+
return result
706+
}
707+
708+
func (c *OpContext) evalStateCI(v Expr, state combinedFlags) (result Value, ci CloseInfo) {
690709
savedSrc := c.src
691710
c.src = v.Source()
692711
err := c.errs
693712
c.errs = nil
713+
// Save the old CloseInfo and restore after evaluate to avoid detecting
714+
// spurious cycles.
715+
saved := c.ci
694716

695717
defer func() {
696718
c.errs = CombineErrors(c.src, c.errs, err)
@@ -721,23 +743,29 @@ func (c *OpContext) evalState(v Expr, state combinedFlags) (result Value) {
721743
result = c.errs
722744
}
723745
c.src = savedSrc
746+
747+
// TODO(evalv3): this c.ci should be passed to the caller who may need
748+
// it to continue cycle detection for partially evaluated values.
749+
// Either this or we must prove that this is covered by structural cycle
750+
// detection.
751+
c.ci = saved
724752
}()
725753

726754
switch x := v.(type) {
727755
case Value:
728-
return x
756+
return x, c.ci
729757

730758
case Evaluator:
731759
v := x.evaluate(c, state)
732-
return v
760+
return v, c.ci
733761

734762
case Resolver:
735763
arc := x.resolve(c, state)
736764
if c.HasErr() {
737-
return nil
765+
return nil, c.ci
738766
}
739767
if arc == nil {
740-
return nil
768+
return nil, c.ci
741769
}
742770
// TODO(deref): what is the right level of dereferencing here?
743771
// DerefValue seems to work too.
@@ -748,9 +776,6 @@ func (c *OpContext) evalState(v Expr, state combinedFlags) (result Value) {
748776
// TODO: is this indirect necessary?
749777
// arc = arc.Indirect()
750778

751-
// Save the old CloseInfo and restore after evaluate to avoid detecting
752-
// spurious cycles.
753-
saved := c.ci
754779
n := arc.state
755780
if c.isDevVersion() {
756781
n = arc.getState(c)
@@ -787,7 +812,7 @@ func (c *OpContext) evalState(v Expr, state combinedFlags) (result Value) {
787812
err := c.Newf("cycle with field %v", x)
788813
b := &Bottom{Code: CycleError, Err: err}
789814
s.setBaseValue(b)
790-
return b
815+
return b, c.ci
791816
// TODO: use this instead, as is usual for incomplete errors,
792817
// and also move this block one scope up to also apply to
793818
// defined arcs. In both cases, though, doing so results in
@@ -796,13 +821,13 @@ func (c *OpContext) evalState(v Expr, state combinedFlags) (result Value) {
796821
// return nil
797822
}
798823
c.undefinedFieldError(v, IncompleteError)
799-
return nil
824+
return nil, c.ci
800825
}
801826
}
802827
}
803828
v := c.evaluate(arc, x, state)
804-
c.ci = saved
805-
return v
829+
830+
return v, c.ci
806831

807832
default:
808833
// This can only happen, really, if v == nil, which is not allowed.

0 commit comments

Comments
 (0)