Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions sjsonnet/src/sjsonnet/ByteRenderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -222,13 +222,13 @@ class ByteRenderer(out: OutputStream = new java.io.ByteArrayOutputStream(), inde
materializeDirectArr(xs, matDepth + 1, ctx)
else
// Fall back to generic visitor path for extremely deep nesting
Materializer.apply0(v, this)(evaluator)
Materializer.materializeStackless(v, this, ctx)(evaluator)
case 6 => // TAG_OBJ
val obj = v.asInstanceOf[Val.Obj]
if (matDepth < ctx.recursiveDepthLimit)
materializeDirectObj(obj, matDepth + 1, ctx)
else
Materializer.apply0(v, this)(evaluator)
Materializer.materializeStackless(v, this, ctx)(evaluator)
case 7 => // TAG_FUNC
val s = v.asInstanceOf[Val.Func]
Error.fail(
Expand Down
2 changes: 1 addition & 1 deletion sjsonnet/src/sjsonnet/Materializer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ abstract class Materializer {

// Iterative materialization for deep nesting. Used as a fallback when recursive depth exceeds
// the recursive depth limit. Uses an explicit ArrayDeque stack to avoid StackOverflowError.
private def materializeStackless[T](
private[sjsonnet] def materializeStackless[T](
v: Val,
visitor: Visitor[T, T],
ctx: Materializer.MaterializeContext)(implicit evaluator: EvalScope): T = {
Expand Down
31 changes: 31 additions & 0 deletions sjsonnet/test/src/sjsonnet/RendererTests.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package sjsonnet

import java.io.ByteArrayOutputStream
import utest._

object RendererTests extends TestSuite {
Expand Down Expand Up @@ -65,6 +66,36 @@ object RendererTests extends TestSuite {
ujson.transform(ujson.Num(1e15), new Renderer()).toString ==> "1000000000000000"
}

test("byteRendererFallbackPreservesCycleContext") {
val interpreter = new Interpreter(
Map(),
Map(),
DummyPath(),
Importer.empty,
parseCache = new DefaultParseCache,
settings = Settings.default.copy(materializeRecursiveDepthLimit = 2, maxMaterializeDepth = 2)
)
val value = interpreter.evaluate(
"""local o = {
| a: std.repeat("x", 10000),
| z: { b: o },
|};
|{ root: o }""".stripMargin,
DummyPath("(memory)")
) match {
case Right(v) => v
case Left(err) => throw new Exception(Error.formatError(err))
}
val out = new ByteArrayOutputStream
val e = interpreter.materialize(value, new ByteRenderer(out)) match {
case Left(err) => Error.formatError(err)
case Right(_) => throw new Exception("Expected recursive value materialization error")
}
assert(e.contains("Stackoverflow while materializing, possibly due to recursive value"))
// Losing the outer materialization context re-renders `o.a` before detecting the cycle.
assert(out.size() < 15000)
}

test("indentZero") {
// indent=0 should produce newlines but no spaces
ujson.transform(ujson.Arr(1, 2), new Renderer(indent = 0)).toString ==>
Expand Down
Loading