Skip to content

Commit f684d27

Browse files
feat: introduce maximum parser recursion depth (#483)
Resolves issue #473. This PR implements a configurable maximum parser recursion depth limit to prevent stack overflow from deeply nested Jsonnet structures, following the approach from [google/jsonnet#1230](google/jsonnet#1230) **Changes** - Added maxParserRecursionDepth: Int = 1000 parameter to Settings class - Added --max-parser-recursion-depth CLI argument to allow runtime configuration - Updated Settings flow: CLI → Config → Settings → Interpreter → CachedResolver → Parser **Before:** - Parsing extremely deeply nested structures (e.g., [[[...1000+ levels...]]]) could cause native stack overflow and crash the interpreter. **After:** - Parser gracefully throws java.lang.Exception: Parsing exceeded maximum recursion depth of 1000 instead of crashing. - Users can now configure the limit: `sjsonnet --max-parser-recursion-depth 2000 deeply_nested.jsonnet` - Allows tuning for specific use cases while maintaining safe defaults
1 parent 8e13eb6 commit f684d27

File tree

6 files changed

+454
-315
lines changed

6 files changed

+454
-315
lines changed

sjsonnet/src-jvm-native/sjsonnet/Config.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ final case class Config(
139139
doc = """If set, reverses the import order of specified jpaths (so that the rightmost wins)"""
140140
)
141141
reverseJpathsPriority: Flag = Flag(),
142+
@arg(
143+
name = "max-parser-recursion-depth",
144+
doc =
145+
"Set maximum parser recursion depth to prevent stack overflow from deeply nested structures"
146+
)
147+
maxParserRecursionDepth: Int = 1000,
142148
@arg(
143149
doc = "The jsonnet file you wish to evaluate",
144150
positional = true

sjsonnet/src-jvm-native/sjsonnet/SjsonnetMainBase.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,8 @@ object SjsonnetMainBase {
285285
settings = new Settings(
286286
preserveOrder = config.preserveOrder.value,
287287
strict = config.strict.value,
288-
throwErrorForInvalidSets = config.throwErrorForInvalidSets.value
288+
throwErrorForInvalidSets = config.throwErrorForInvalidSets.value,
289+
maxParserRecursionDepth = config.maxParserRecursionDepth
289290
),
290291
storePos = (position: Position) => if (config.yamlDebug.value) currentPos = position else (),
291292
logger = warnLogger,

sjsonnet/src/sjsonnet/Importer.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ class CachedResolver(
205205
internedStaticFieldSets: mutable.HashMap[
206206
Val.StaticObjectFieldSet,
207207
java.util.LinkedHashMap[String, java.lang.Boolean]
208-
])
208+
],
209+
settings: Settings = Settings.default)
209210
extends CachedImporter(parentImporter) {
210211

211212
def parse(path: Path, content: ResolvedFile)(implicit
@@ -214,7 +215,7 @@ class CachedResolver(
214215
(path, content.contentHash()), {
215216
val parsed = fastparse.parse(
216217
content.getParserInput(),
217-
new Parser(path, internedStrings, internedStaticFieldSets).document(_)
218+
new Parser(path, internedStrings, internedStaticFieldSets, settings).document(_)
218219
) match {
219220
case f @ Parsed.Failure(_, _, _) =>
220221
val traced = f.trace()

sjsonnet/src/sjsonnet/Interpreter.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ class Interpreter(
6363
importer,
6464
parseCache,
6565
internedStrings,
66-
internedStaticFieldSets
66+
internedStaticFieldSets,
67+
settings
6768
) {
6869
override def process(expr: Expr, fs: FileScope): Either[Error, (Expr, FileScope)] = {
6970
handleException(

0 commit comments

Comments
 (0)