cmd/cue: stack overflow importing json schema using -l
What version of CUE are you using (cue version)?
$ cue version
cue version v0.13.0-alpha.1.0.20250321151659-72567b93ca63+dirty
go version go1.24.0
-buildmode exe
-compiler gc
DefaultGODEBUG gotestjsonbuildtext=1,multipathtcp=0,randseednop=0,rsa1024min=0,tlsmlkem=0,x509rsacrt=0,x509usepolicies=0
CGO_ENABLED 1
GOARCH arm64
GOOS linux
GOARM64 v8.0
vcs git
vcs.revision 72567b93ca6309a865fd45a3ffe14f9957134a25
vcs.time 2025-03-21T15:16:59Z
vcs.modified true
cue.lang.version v0.13.0
Does this issue reproduce with the latest release?
Yes
What did you do?
# Get the JSON Schema
exec curl -sLO https://gist.githubusercontent.com/infogulch/1578d100b8e8c041b50ee6bca78ae13a/raw/3b034f7f51084fc912d85152a42efaf294dd3517/caddy_schema.json
# top-level
exec cue import -f caddy_schema.json
# placed at #schema
exec cue import -f -l '#schema:' caddy_schema.json
What did you expect to see?
Passing test, with similar length of time taken for both cue import variants.
What did you see instead?
Stack overflow with the -l variant:
# Get the JSON Schema (0.228s)
# top-level (0.210s)
# placed at #schema (29.331s)
> exec cue import -f -l '#schema:' caddy_schema.json
[stderr]
runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0x4185f80510 stack=[0x4185f80000, 0x41a5f80000]
fatal error: stack overflow
runtime stack:
runtime.throw({0xbb1f4e?, 0x0?})
/home/myitcv/gos/src/runtime/panic.go:1096 +0x38 fp=0xffff5e7ce6e0 sp=0xffff5e7ce6b0 pc=0x89ca8
runtime.newstack()
/home/myitcv/gos/src/runtime/stack.go:1107 +0x45c fp=0xffff5e7ce820 sp=0xffff5e7ce6e0 pc=0x6e28c
runtime.morestack()
/home/myitcv/gos/src/runtime/asm_arm64.s:342 +0x70 fp=0xffff5e7ce820 sp=0xffff5e7ce820 pc=0x8f9b0
goroutine 1 gp=0x40000021c0 m=11 mp=0x4000501808 [running]:
cuelang.org/go/internal/core/adt.(*nodeContext).doDisjunct(0x42e51c5108, {0x423814ee60, {0xd65b70, 0x4002bb41c0}, {0x0, 0x4fb19, 0x0, 0x0, 0x0, 0x0, ...}}, ...)
/home/myitcv/dev/cuelang/cue/internal/core/adt/disjunct2.go:470 +0x5fc fp=0x4185f80510 sp=0x4185f80510 pc=0x2efb4c
cuelang.org/go/internal/core/adt.(*nodeContext).crossProduct(0x42e51c5108, {0x4185f80788?, 0x1272dd8?, 0x13bc460?}, {0x4185f807d8, 0x1, 0x1272dd8?}, 0x42537ef180, 0x4, 0x84c9f)
/home/myitcv/dev/cuelang/cue/internal/core/adt/disjunct2.go:423 +0x388 fp=0x4185f806f0 sp=0x4185f80510 pc=0x2ef108
cuelang.org/go/internal/core/adt.(*nodeContext).processDisjunctions(0x42e51c5108)
/home/myitcv/dev/cuelang/cue/internal/core/adt/disjunct2.go:333 +0x41c fp=0x4185f80950 sp=0x4185f806f0 pc=0x2ee5ec
cuelang.org/go/internal/core/adt.processDisjunctions(0x4002a1a800?, 0x42539cb7a0, 0x12?)
/home/myitcv/dev/cuelang/cue/internal/core/adt/tasks.go:215 +0x24 fp=0x4185f80990 sp=0x4185f80950 pc=0x319024
cuelang.org/go/internal/core/adt.runTask(0x42539cb7a0, 0x4)
/home/myitcv/dev/cuelang/cue/internal/core/adt/sched.go:706 +0x458 fp=0x4185f80c10 sp=0x4185f80990 pc=0x315398
cuelang.org/go/internal/core/adt.(*scheduler).process(0x42e51c52f8, 0x7f7f, 0x4)
/home/myitcv/dev/cuelang/cue/internal/core/adt/sched.go:404 +0x140 fp=0x4185f80ca0 sp=0x4185f80c10 pc=0x314870
cuelang.org/go/internal/core/adt.(*Vertex).unify(0x4253a34780, 0x4002a1a800, 0x7fff, 0x4, 0x1)
/home/myitcv/dev/cuelang/cue/internal/core/adt/unify.go:225 +0x3f0 fp=0x4185f80ef0 sp=0x4185f80ca0 pc=0x31d490
cuelang.org/go/internal/core/adt.(*OpContext).unify(0xa0?, 0xb901a0?, 0x2d50f01?)
/home/myitcv/dev/cuelang/cue/internal/core/adt/eval.go:160 +0x178 fp=0x4185f81190 sp=0x4185f80ef0 pc=0x2f4b48
I have reduced this to a self-contained and relatively small testscript, which reproduces the panic as of 4cbb1800466b0202d6468b1ad5a4836ebb4c7d72, and shows that it's an evalv3 regression:
env CUE_EXPERIMENT=evalv3=0
exec cue import -f schema.json
cmp schema.cue schema.cue-one
exec cue import -f -l '#schema:' schema.json
cmp schema.cue schema.cue-two
env CUE_EXPERIMENT=evalv3=1
exec cue import -f schema.json
cmp schema.cue schema.cue-one
# This currently panics after exhausting the goroutine stack.
exec cue import -f -l '#schema:' schema.json
cmp schema.cue schema.cue-two
-- schema.json --
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"definitions": {
"recursive": {
"type": "array",
"items": {
"properties": {
"source": {
"type": "string"
}
},
"if": {
"properties": {
"source": {
"const": "multi"
}
}
},
"then": {
"$ref": "#/definitions/recursive"
}
}
}
},
"properties": {
}
}
-- schema.cue-one --
@jsonschema(schema="https://json-schema.org/draft/2020-12/schema")
#recursive: [...matchIf(null | bool | number | string | [...] | {
source?: "multi"
...
}, #recursive, _) & (null | bool | number | string | [...] | {
source?: string
...
})]
...
-- schema.cue-two --
#schema: {
@jsonschema(schema="https://json-schema.org/draft/2020-12/schema")
#recursive: [...matchIf(null | bool | number | string | [...] | {
source?: "multi"
...
}, #recursive, _) & (null | bool | number | string | [...] | {
source?: string
...
})]
...
}
> env CUE_EXPERIMENT=evalv3=0
> exec cue import -f schema.json
> cmp schema.cue schema.cue-one
> exec cue import -f -l '#schema:' schema.json
> cmp schema.cue schema.cue-two
> env CUE_EXPERIMENT=evalv3=1
> exec cue import -f schema.json
> cmp schema.cue schema.cue-one
> exec cue import -f -l '#schema:' schema.json
[stderr]
runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0xc079990368 stack=[0xc079990000, 0xc099990000]
fatal error: stack overflow
FYI edited the original post as it conflated -l '#schema' with -l '#schema:'.
Reduced it further to a cue def evalv3 regression:
# evalv2
env CUE_EXPERIMENT=evalv3=0
exec cue def
# evalv3
env CUE_EXPERIMENT=evalv3=1
exec cue def
-- input.cue --
package p
#recursive: [...matchIf({
source?: "multi"
}, #recursive, _) & {
source?: string
}]
Worth noting that the original cue import command with the original input also fails on evalv2, but at this point we're not fixing any more evalv2 bugs.
This appears resolved as of v0.15.0; I bisected the fix to ab40e7f72cd12dfc06f62032ca7e8d6eb4ae7adc. I'll close this with a regression test.