Skip to content

fix: self-referential thunks produce clean error instead of StackOverflowError#1020

Open
He-Pin wants to merge 1 commit into
databricks:masterfrom
He-Pin:fix/self-referential-thunk
Open

fix: self-referential thunks produce clean error instead of StackOverflowError#1020
He-Pin wants to merge 1 commit into
databricks:masterfrom
He-Pin:fix/self-referential-thunk

Conversation

@He-Pin

@He-Pin He-Pin commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • local x = x; x caused an uncontrolled java.lang.StackOverflowError with raw Java stack trace
  • C++ jsonnet, go-jsonnet, and jrsonnet all produce clean, controlled error messages
  • Added evaluating boolean flag to LazyExpr for cycle detection — same approach as jrsonnet

Cross-implementation comparison

Expression cpp-jsonnet 0.21.0 go-jsonnet 0.22.0 jrsonnet 0.5.0-pre99 sjsonnet (before) sjsonnet (after)
local x = x; x "max stack frames" "max stack frames" "infinite recursion" StackOverflowError 💥 "Infinite recursion detected" ✅
local f() = f(); f() "max stack frames" "max stack frames" "stack overflow" "Max stack frames" ✅ "Max stack frames" ✅
local x = 1+2; x 3 3 3 3 3

Root cause

visitValidIdLazyExpr.valuevisitExprvisitValidId forms an infinite loop with no stack depth check in the path. The checkStackDepth guard exists in function call and binary op visitors but was missing from the thunk evaluation path.

Test plan

  • ./mill sjsonnet.jvm[3.3.7].test — all suites green
  • New error test: error.self_referential_thunk.jsonnet

…flowError

Motivation:
`local x = x; x` caused an uncontrolled java.lang.StackOverflowError
with a raw Java stack trace. C++ jsonnet, go-jsonnet, and jrsonnet
all produce clean, controlled error messages for self-referential
thunks.

Modification:
- Val.scala: Add `evaluating` boolean flag to LazyExpr. When value
  is first requested, set the flag to true. If value is requested
  again while the flag is true, it's a cycle — throw a clean error:
  "Infinite recursion detected (self-referential thunk)".

Result:
Self-referential thunks now produce a clean sjsonnet.Error instead
of crashing the JVM.

Cross-implementation comparison:
| Expression | cpp-jsonnet | go-jsonnet | jrsonnet | sjsonnet (before) | sjsonnet (after) |
|---|---|---|---|---|---|
| local x = x; x | "max stack frames" | "max stack frames" | "infinite recursion" | StackOverflowError 💥 | "Infinite recursion detected" ✅ |
| local f() = f(); f() | "max stack frames" | "max stack frames" | "stack overflow" | "Max stack frames" ✅ | "Max stack frames" ✅ |
| local x = 1+2; x | 3 | 3 | 3 | 3 ✅ | 3 ✅ |
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant