Skip to content

evalv3: stackoverflow when using the function pattern #3731

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ydubreuil opened this issue Feb 6, 2025 · 4 comments
Closed

evalv3: stackoverflow when using the function pattern #3731

ydubreuil opened this issue Feb 6, 2025 · 4 comments
Assignees
Labels
evaluator evalv3 issues affecting only the evaluator version 3 panic

Comments

@ydubreuil
Copy link

ydubreuil commented Feb 6, 2025

What version of CUE are you using (cue version)?

$ cue version
cue version v0.12.0

go version go1.23.5
      -buildmode exe
       -compiler gc
  DefaultGODEBUG asynctimerchan=1,gotypesalias=0,httpservecontentkeepheaders=1,tls3des=1,tlskyber=0,x509keypairleaf=0,x509negativeserial=1
     CGO_ENABLED 1
          GOARCH amd64
            GOOS linux
         GOAMD64 v1
cue.lang.version v0.12.0

Does this issue reproduce with the latest stable release?

Yes

What did you do?

Ran the following test script:

# -- evalv2 --
env CUE_EXPERIMENT=evalv3=0
exec cue eval -e tree main.cue
cmp stdout stdout.golden

# -- evalv3 --
env CUE_EXPERIMENT=evalv3=1
exec cue eval -e tree main.cue
cmp stdout stdout.golden

-- main.cue --
#Workspace: {
	workspaceA?: {}
	workspaceB?: {}
}

#AccountConfig: {
	workspaces: #Workspace
	siblings?: [...string]
}

#AccountConfigSub1: {
	#AccountConfig
	workspaces: "workspaceA": {}
}

#AccountConfigSub2: {
	#AccountConfig
	workspaces: "workspaceB": {}
}

tree: env1: {
	"region1": {
		"env1-r1-account-sub1": #AccountConfigSub1
		"env1-r1-account-sub2-1": #AccountConfigSub2
	}
}

#lookupSiblings: {
	envtree: {...}
	out: [
		for region, v in envtree
		for account, config in v
		if config.workspaces."workspaceB" != _|_ { account },
	]
}

tree: ENVTREE=env1: [_]: [_]: #AccountConfig & {
	siblings: (#lookupSiblings & {envtree: ENVTREE}).out
}
-- stdout.golden --
env1: {
    region1: {
        "env1-r1-account-sub1": {
            workspaces: {
                workspaceA: {}
            }
            siblings: ["env1-r1-account-sub2-1"]
        }
        "env1-r1-account-sub2-1": {
            workspaces: {
                workspaceB: {}
            }
            siblings: ["env1-r1-account-sub2-1"]
        }
    }
}

What did you expect to see?

The same result as the one generated by the old evaluator:

Passing test.

What did you see instead?

A panic log from the Go runtime:

runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0xc07eb80320 stack=[0xc07eb80000, 0xc09eb80000]
fatal error: stack overflow

runtime stack:
runtime.throw({0x10388d0?, 0x7d9e78ff8d40?})
        /usr/lib/go/src/runtime/panic.go:1067 +0x48 fp=0x7d9e78ff8d00 sp=0x7d9e78ff8cd0 pc=0x4769e8
runtime.newstack()
        /usr/lib/go/src/runtime/stack.go:1117 +0x5bd fp=0x7d9e78ff8e40 sp=0x7d9e78ff8d00 pc=0x458f3d
runtime.morestack()
        /usr/lib/go/src/runtime/asm_amd64.s:621 +0x7a fp=0x7d9e78ff8e48 sp=0x7d9e78ff8e40 pc=0x47d5da

goroutine 1 gp=0xc0000061c0 m=10 mp=0xc000680008 [running]:
runtime.growslice(0x0?, 0x1?, 0x0?, 0x1?, 0xfb39a0?)
        /usr/lib/go/src/runtime/slice.go:177 +0x765 fp=0xc07eb80330 sp=0xc07eb80328 pc=0x479465
cuelang.org/go/internal/core/adt.(*Vertex).addConjunctUnchecked(0xc0f1ffda40, {0x0, {0x11e6e60, 0xc0f202a588}, {0x0, 0xc0f2030900, 0x0, 0x0, 0x0, 0x0, ...}})
        /home/ydubreuil/go/pkg/mod/cuelang.org/[email protected]/internal/core/adt/composite.go:1409 +0x4e fp=0xc07eb803b8 sp=0xc07eb80330 pc=0x6e856e
cuelang.org/go/internal/core/adt.(*Vertex).assignConjunct(0x4174cb?, 0x7d9e5244d048?, 0xc0f2030900, {0x0, {0x11e6e60, 0xc0f202a588}, {0x0, 0xc0f2030900, 0x0, 0x0, ...}}, ...)
        /home/ydubreuil/go/pkg/mod/cuelang.org/[email protected]/internal/core/adt/fields.go:404 +0x129 fp=0xc07eb804c0 sp=0xc07eb803b8 pc=0x72a009
cuelang.org/go/internal/core/adt.(*closeContext).getKeyedCC(0xc0f2030360, 0xc0023c0960, 0xc0f2030900, {0x8?, 0x0?, 0x68?, 0xc0f2029380?}, 0x0, 0x1)
        /home/ydubreuil/go/pkg/mod/cuelang.org/[email protected]/internal/core/adt/fields.go:427 +0x239 fp=0xc07eb805b0 sp=0xc07eb804c0 pc=0x72a3d9
cuelang.org/go/internal/core/adt.(*closeContext).assignConjunct(0x9?, 0x30?, 0x20?, {0x0, {0x11e6e60, 0xc0f202a570}, {0x0, 0x0, 0x0, 0x0, ...}}, ...)
        /home/ydubreuil/go/pkg/mod/cuelang.org/[email protected]/internal/core/adt/fields.go:478 +0x5a fp=0xc07eb806d0 sp=0xc07eb805b0 pc=0x72a6ba
cuelang.org/go/internal/core/adt.(*closeContext).getKeyedCC(0xc0f20306c0, 0xc0023c0960, 0xc0f2030900, {0x48?, 0x8?, 0xb8?, 0xc0f2029380?}, 0x0, 0x1)
        /home/ydubreuil/go/pkg/mod/cuelang.org/[email protected]/internal/core/adt/fields.go:427 +0x239 fp=0xc07eb807c0 sp=0xc07eb806d0 pc=0x72a3d9
cuelang.org/go/internal/core/adt.(*closeContext).assignConjunct(0x8?, 0x412465?, 0x120?, {0xc0f2032160, {0x11e6e88, 0xc000c17ce0}, {0x0, 0xc0f20306c0, 0x0, 0x0, ...}}, ...)
        /home/ydubreuil/go/pkg/mod/cuelang.org/[email protected]/internal/core/adt/fields.go:478 +0x5a fp=0xc07eb808e0 sp=0xc07eb807c0 pc=0x72a6ba
cuelang.org/go/internal/core/adt.(*closeContext).insertConjunct(0x100e360?, 0xc0023c0960, 0xc0f2030900, {0xc0f2032160, {0x11e6e88, 0xc000c17ce0}, {0x0, 0xc0f20306c0, 0x0, 0x0, ...}}, ...)
        /home/ydubreuil/go/pkg/mod/cuelang.org/[email protected]/internal/core/adt/fields.go:671 +0x65 fp=0xc07eb80988 sp=0xc07eb808e0 pc=0x72b245
cuelang.org/go/internal/core/adt.(*nodeContext).insertArcCC(0xc0f202ec08, 0x621, 0x0, {0xc0f2032160, {0x11e6e88, 0xc000c17ce0}, {0x0, 0xc0f20306c0, 0x0, 0x0, ...}}, ...)
        /home/ydubreuil/go/pkg/mod/cuelang.org/[email protected]/internal/core/adt/fields.go:755 +0x205 fp=0xc07eb80ad8 sp=0xc07eb80988 pc=0x72b6c5
cuelang.org/go/internal/core/adt.(*nodeContext).insertArc(...)
        /home/ydubreuil/go/pkg/mod/cuelang.org/[email protected]/internal/core/adt/fields.go:714
...

If instead of using the function pattern the code inside #lookupSiblings.out is put at the call site, then there's no stack overflow panic and the new evaluator correctly evaluates tree.

cc @gotwarlost

@ydubreuil ydubreuil added NeedsInvestigation Triage Requires triage/attention labels Feb 6, 2025
@mvdan mvdan self-assigned this Feb 6, 2025
@mvdan
Copy link
Member

mvdan commented Feb 7, 2025

Reduced a bit:

# With the old evaluator.
env CUE_EXPERIMENT=evalv3=0
exec cue export input.cue

# With the new evaluator.
env CUE_EXPERIMENT=evalv3=1
exec cue export input.cue

-- input.cue --
package p

#lookup: {
	input: {...}
	output: [
		for _, v in input
		if v.show != _|_ { v },
	]
}

root: {
	self: show: true
	others: (#lookup & {input: root}).output
}

As of 0e42b0b:

# With the old evaluator. (0.010s)
> env CUE_EXPERIMENT=evalv3=0
> exec cue export input.cue
[stdout]
{
    "root": {
        "self": {
            "show": true
        },
        "others": [
            {
                "show": true
            }
        ]
    }
}
# With the new evaluator. (6.962s)
> env CUE_EXPERIMENT=evalv3=1
> exec cue export input.cue
[stderr]
runtime: goroutine stack exceeds 1000000000-byte limit

@mvdan mvdan removed their assignment Feb 7, 2025
@mvdan mvdan added panic evaluator evalv3 issues affecting only the evaluator version 3 and removed NeedsInvestigation Triage Requires triage/attention labels Feb 7, 2025
@myitcv
Copy link
Member

myitcv commented Feb 15, 2025

Two further observations regarding the reduction in #3731 (comment):

@myitcv myitcv removed the tmp/eval label Feb 15, 2025
@mpvl
Copy link
Member

mpvl commented Feb 19, 2025

This turns out to be quite an interesting one. The problem can be reduced to something not using the function pattern:

X: {
    input: a: _
    output: input.a
}
root: {
    a: _a.output
    _a: X & {input: root}
}

@mpvl mpvl self-assigned this Feb 25, 2025
cueckoo pushed a commit that referenced this issue Feb 27, 2025
We want to do the same processing for Evaluator
as for a Resolver, if the former resolves to a Vertex.
We refactor the code in a separate CL to make the
diff smaller later.

Issue #3731
Issue #3634

Signed-off-by: Marcel van Lohuizen <[email protected]>
Change-Id: I33e300b9c873ebf10d4e40bbe8f507c74f3bc426
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1209384
TryBot-Result: CUEcueckoo <[email protected]>
Reviewed-by: Matthew Sackman <[email protected]>
cueckoo pushed a commit that referenced this issue Feb 27, 2025
With the structural cycle detection rework, this no
longer seems necessary. As it is safer to be
non-permissive, we do not set this flag. We leave
it in, however, to quickly be able to set it if an
issue bisects to this change.

Issue #3731
Issue #3634

Signed-off-by: Marcel van Lohuizen <[email protected]>
Change-Id: Ie411bf9eca647e675e5a74b12245175e7aa0909c
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1209385
Reviewed-by: Matthew Sackman <[email protected]>
TryBot-Result: CUEcueckoo <[email protected]>
cueckoo pushed a commit that referenced this issue Feb 27, 2025
By caching inline expressions, we can treat them
more like regular fields, which will, in turn,
allow simplifying the structural cycle handling.

Note that some of the dependency analysis results
have changed. In let2 the ones dropped are
for non-existent references, which seems fine.
For selfref.txtar, it seems like this should be
fixed, although V2 isn't grand either.

Issue #3731
Issue #3634

Signed-off-by: Marcel van Lohuizen <[email protected]>
Change-Id: I0e30f5dec08c8e120e5d24ad3115fce26f565fec
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1209386
Reviewed-by: Matthew Sackman <[email protected]>
Unity-Result: CUE porcuepine <[email protected]>
TryBot-Result: CUEcueckoo <[email protected]>
cueckoo pushed a commit that referenced this issue Feb 27, 2025
…sharing

In some cases, an ancestor node may be shared
in a child node. This bypasses the usual structure
sharing checks. This adds the check to catch this
edge case. We should probably find something more
principled down the line.

This problem was discovered as an issue when
debugging
Issue #3731

Signed-off-by: Marcel van Lohuizen <[email protected]>
Change-Id: Ib5349a113088f82dfcb0c982a9ff0957c8af545f
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1209390
Reviewed-by: Matthew Sackman <[email protected]>
TryBot-Result: CUEcueckoo <[email protected]>
Unity-Result: CUE porcuepine <[email protected]>
@myitcv
Copy link
Member

myitcv commented Mar 19, 2025

Confirming that as of c479844 (which is now in tip) this still passes as expected, but also that we do not need to set CUE_DEBUG=openinline=0 in order for it to pass. The test passes with:

  • CUE_DEBUG unset
  • CUE_DEBUG=openinline=0
  • CUE_DEBUG=openinline=1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
evaluator evalv3 issues affecting only the evaluator version 3 panic
Projects
None yet
Development

No branches or pull requests

4 participants