Skip to content

Commit f55280a

Browse files
xoofxCopilot
andcommitted
Enforce parser depth for array initializers
Co-authored-by: Copilot <[email protected]>
1 parent 760dc21 commit f55280a

File tree

2 files changed

+72
-39
lines changed

2 files changed

+72
-39
lines changed

src/Scriban.Tests/TestParser.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,31 @@ public void EnsureStackOverflowCanBeAvoidedForSelfReferentialObjectGraphs()
826826
Assert.Throws<ScriptRuntimeException>(() => template.Render(context));
827827
}
828828

829+
[Test]
830+
public void EnsureExpressionDepthLimitAppliesToNestedArrayInitializers()
831+
{
832+
var builder = new StringBuilder();
833+
for (var i = 0; i < 20; i++)
834+
{
835+
builder.Append('[');
836+
}
837+
838+
builder.Append('0');
839+
840+
for (var i = 0; i < 20; i++)
841+
{
842+
builder.Append(']');
843+
}
844+
845+
var template = Template.Parse($"{{{{ {builder} }}}}", parserOptions: new ParserOptions
846+
{
847+
ExpressionDepthLimit = 10
848+
});
849+
850+
Assert.True(template.HasErrors);
851+
StringAssert.Contains("The statement depth limit `10` was reached when parsing this statement", template.Messages[0].ToString());
852+
}
853+
829854
[TestCase(@"ab{{end}}c")] // no blocks
830855
[TestCase(@"a{{if true}}b{{end}}{{end}}c")] // one-level block
831856
[TestCase(@"a{{if true}}{{for i in 0..1}}b{{end}}{{end}}{{end}}c")] // two-level block (nested)
@@ -1227,4 +1252,4 @@ protected override void DefaultVisit(ScriptNode node)
12271252
}
12281253
}
12291254
}
1230-
}
1255+
}

src/Scriban/Parsing/Parser.Expressions.cs

Lines changed: 46 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -771,67 +771,75 @@ private bool IsCurrentPipeOrExpressionContinuation()
771771

772772
private ScriptExpression ParseArrayInitializer()
773773
{
774-
var scriptArray = Open<ScriptArrayInitializerExpression>();
774+
EnterExpression();
775+
try
776+
{
777+
var scriptArray = Open<ScriptArrayInitializerExpression>();
775778

776-
// Should happen before the NextToken to consume any EOL after
777-
_allowNewLineLevel++;
779+
// Should happen before the NextToken to consume any EOL after
780+
_allowNewLineLevel++;
778781

779-
// Parse [
780-
ExpectAndParseTokenTo(scriptArray.OpenBracketToken, TokenType.OpenBracket);
782+
// Parse [
783+
ExpectAndParseTokenTo(scriptArray.OpenBracketToken, TokenType.OpenBracket);
781784

782-
bool expectingEndOfInitializer = false;
783-
784-
// unit test: 120-array-initializer-accessor.txt
785-
while (true)
786-
{
787-
if (Current.Type == TokenType.CloseBracket)
788-
{
789-
break;
790-
}
785+
bool expectingEndOfInitializer = false;
791786

792-
if (!expectingEndOfInitializer)
787+
// unit test: 120-array-initializer-accessor.txt
788+
while (true)
793789
{
794-
// unit test: 120-array-initializer-error2.txt
795-
var expression = ExpectAndParseExpression(scriptArray);
796-
if (expression == null)
790+
if (Current.Type == TokenType.CloseBracket)
797791
{
798792
break;
799793
}
800-
scriptArray.Values.Add(expression);
801794

802-
if (Current.Type == TokenType.Comma)
795+
if (!expectingEndOfInitializer)
803796
{
804-
// Record trailing Commas
805-
if (_isKeepTrivia)
797+
// unit test: 120-array-initializer-error2.txt
798+
var expression = ExpectAndParseExpression(scriptArray);
799+
if (expression == null)
806800
{
807-
PushTokenToTrivia();
801+
break;
808802
}
803+
scriptArray.Values.Add(expression);
809804

810-
NextToken();
805+
if (Current.Type == TokenType.Comma)
806+
{
807+
// Record trailing Commas
808+
if (_isKeepTrivia)
809+
{
810+
PushTokenToTrivia();
811+
}
811812

812-
if (_isKeepTrivia)
813+
NextToken();
814+
815+
if (_isKeepTrivia)
816+
{
817+
FlushTriviasToLastTerminal();
818+
}
819+
}
820+
else
813821
{
814-
FlushTriviasToLastTerminal();
822+
expectingEndOfInitializer = true;
815823
}
816824
}
817825
else
818826
{
819-
expectingEndOfInitializer = true;
827+
break;
820828
}
821829
}
822-
else
823-
{
824-
break;
825-
}
826-
}
827830

828-
// Should happen before NextToken() to stop on the next EOF
829-
_allowNewLineLevel--;
831+
// Should happen before NextToken() to stop on the next EOF
832+
_allowNewLineLevel--;
830833

831-
// Parse ]
832-
// unit test: 120-array-initializer-error1.txt
833-
ExpectAndParseTokenTo(scriptArray.CloseBracketToken, TokenType.CloseBracket);
834-
return Close(scriptArray);
834+
// Parse ]
835+
// unit test: 120-array-initializer-error1.txt
836+
ExpectAndParseTokenTo(scriptArray.CloseBracketToken, TokenType.CloseBracket);
837+
return Close(scriptArray);
838+
}
839+
finally
840+
{
841+
LeaveExpression();
842+
}
835843
}
836844

837845
private ScriptExpression ParseObjectInitializer()

0 commit comments

Comments
 (0)