Skip to content

Commit d0e2ab6

Browse files
committed
internal/core/dep,internal/core/export: fix stack overflow on recursive definitions
Break two independent infinite recursion cycles triggered by `cue def -e '#a'` on input with recursive pattern constraints like `#c: [string]: #c`. In dep.go, track vertices already visited by markInternalResolvers to prevent the cycle markInternalResolvers → markConjuncts → markExpr → markResolver → reportDependency → markInternalResolvers. In the exporter, track vertices currently being inlined by refExpr to prevent the cycle refExpr → expr → mergeValues → addExpr → adt → resolve → refExpr. Fixes #3476. Signed-off-by: Daniel Martí <[email protected]> Change-Id: Ib2139fc6555bdde6f2216118668d992adfe6babd Reviewed-on: https://cue.gerrithub.io/c/cue-lang/cue/+/1231449 Reviewed-by: Marcel van Lohuizen <[email protected]> Unity-Result: CUE porcuepine <[email protected]> TryBot-Result: CUEcueckoo <[email protected]>
1 parent 7fb912e commit d0e2ab6

File tree

3 files changed

+54
-8
lines changed

3 files changed

+54
-8
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Verify that cue def does not stack overflow on recursive definitions.
2+
# Issue #3476.
3+
4+
exec cue def -e '#a' in.cue
5+
cmp stdout stdout.golden
6+
7+
-- in.cue --
8+
#a: (#b & {x: _}).x
9+
#b: {
10+
x?: #c
11+
#c: [string]: #c
12+
}
13+
-- stdout.golden --
14+
15+
_#def
16+
_#def: {
17+
[string]: [string]: C.#x
18+
}
19+
20+
//cue:path: #c
21+
let C = {
22+
#x: {
23+
[string]: C.#x
24+
}
25+
}

internal/core/dep/dep.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -189,14 +189,15 @@ func Visit(cfg *Config, c *adt.OpContext, n *adt.Vertex, f VisitFunc) error {
189189
panic("nil context")
190190
}
191191
v := visitor{
192-
ctxt: c,
193-
fn: f,
194-
pkg: cfg.Pkg,
195-
recurse: cfg.Descend,
196-
all: cfg.Descend,
197-
top: true,
198-
cfgDynamic: cfg.Dynamic,
199-
resolved: map[refEntry]bool{},
192+
ctxt: c,
193+
fn: f,
194+
pkg: cfg.Pkg,
195+
recurse: cfg.Descend,
196+
all: cfg.Descend,
197+
top: true,
198+
cfgDynamic: cfg.Dynamic,
199+
resolved: map[refEntry]bool{},
200+
visitedInternal: map[*adt.Vertex]bool{},
200201
}
201202
return v.visitReusingVisitor(n, true)
202203
}
@@ -271,6 +272,10 @@ type visitor struct {
271272

272273
// resolved dedups resolving references to prevent exponential blowup.
273274
resolved map[refEntry]bool
275+
276+
// visitedInternal tracks vertices already processed by markInternalResolvers
277+
// to prevent infinite recursion on recursive definitions like [string]: #c.
278+
visitedInternal map[*adt.Vertex]bool
274279
}
275280

276281
type refEntry struct {
@@ -557,6 +562,11 @@ func (c *visitor) markConjuncts(v *adt.Vertex) {
557562
// proactive. For selectors and indices this means we need to evaluate their
558563
// objects to see exactly what the selector or index refers to.
559564
func (c *visitor) markInternalResolvers(env *adt.Environment, r adt.Resolver, v *adt.Vertex) {
565+
if c.visitedInternal[v] {
566+
return
567+
}
568+
c.visitedInternal[v] = true
569+
560570
saved := c.all // recursive traversal already done by this function.
561571

562572
// As lets have no path and we otherwise will not process them, we set

internal/core/export/self.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ type pivotter struct {
9191
refs []*refData
9292
refMap map[adt.Resolver]*refData
9393

94+
// inlining tracks vertices currently being inlined by refExpr to prevent
95+
// infinite recursion on recursive definitions like [string]: #c.
96+
inlining map[*adt.Vertex]bool
97+
9498
decls []ast.Decl
9599
}
96100

@@ -432,11 +436,18 @@ func (p *pivotter) refExpr(r adt.Resolver) ast.Expr {
432436
// Don't simplify for errors to make the position of the error clearer.
433437
case !n.IsConcrete() && p.x.inExpression > 0:
434438
// Don't simplify an expression that is known will fail.
439+
case p.inlining[n]:
440+
// Prevent infinite recursion on recursive definitions.
435441
case dst.usageCount() == 1 && p.x.inExpression == 0:
436442
// Used only once.
437443
fallthrough
438444
case n.IsConcrete() && len(n.Arcs) == 0:
439445
// Simple scalar value.
446+
if p.inlining == nil {
447+
p.inlining = map[*adt.Vertex]bool{}
448+
}
449+
p.inlining[n] = true
450+
defer delete(p.inlining, n)
440451
return p.x.expr(nil, n)
441452
}
442453

0 commit comments

Comments
 (0)