From 3021b1a80944a168a2cc0b5abf81400962d7354c Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Fri, 1 Apr 2022 14:45:00 +0300 Subject: [PATCH] Fix ElseIfStatement double-evaluation in awaited context --- Fluid.Tests/IfStatementTests.cs | 137 ++++++++++++++++++++++---------- Fluid/Ast/IfStatement.cs | 14 ++-- 2 files changed, 104 insertions(+), 47 deletions(-) diff --git a/Fluid.Tests/IfStatementTests.cs b/Fluid.Tests/IfStatementTests.cs index fba4f04f..817b5da3 100644 --- a/Fluid.Tests/IfStatementTests.cs +++ b/Fluid.Tests/IfStatementTests.cs @@ -4,26 +4,30 @@ using System.Threading.Tasks; using Fluid.Ast; using Fluid.Values; -using Microsoft.Extensions.Primitives; using Xunit; namespace Fluid.Tests { public class IfStatementTests { - private Expression TRUE = new LiteralExpression(BooleanValue.True); - private Expression FALSE = new LiteralExpression(BooleanValue.False); - - private List TEXT(string text) + private static List TEXT(string text) { return new List { new TextSpanStatement(text) }; } - [Fact] - public async Task IfCanProcessWhenTrue() + private static Expression BooleanExpression(bool value, bool async) + { + var boolean = BooleanValue.Create(value); + return async ? new AwaitedExpression(boolean) : new LiteralExpression(boolean); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task IfCanProcessWhenTrue(bool async) { var e = new IfStatement( - TRUE, + BooleanExpression(true, async), new List { new TextSpanStatement("x") } ); @@ -33,11 +37,13 @@ public async Task IfCanProcessWhenTrue() Assert.Equal("x", sw.ToString()); } - [Fact] - public async Task IfDoesntProcessWhenFalse() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task IfDoesntProcessWhenFalse(bool async) { var e = new IfStatement( - FALSE, + BooleanExpression(false, async), new List { new TextSpanStatement("x") } ); @@ -47,11 +53,13 @@ public async Task IfDoesntProcessWhenFalse() Assert.Equal("", sw.ToString()); } - [Fact] - public async Task IfDoesntProcessElseWhenTrue() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task IfDoesntProcessElseWhenTrue(bool async) { var e = new IfStatement( - TRUE, + BooleanExpression(true, async), new List { new TextSpanStatement("x") }, @@ -65,11 +73,13 @@ public async Task IfDoesntProcessElseWhenTrue() Assert.Equal("x", sw.ToString()); } - [Fact] - public async Task IfProcessElseWhenFalse() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task IfProcessElseWhenFalse(bool async) { var e = new IfStatement( - FALSE, + BooleanExpression(false, async), new List { new TextSpanStatement("x") }, @@ -84,14 +94,16 @@ public async Task IfProcessElseWhenFalse() Assert.Equal("y", sw.ToString()); } - [Fact] - public async Task IfProcessElseWhenNoOther() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task IfProcessElseWhenNoOther(bool async) { var e = new IfStatement( - FALSE, + BooleanExpression(false, async), TEXT("a"), new ElseStatement(TEXT("b")), - new List { new ElseIfStatement(FALSE, TEXT("c")) } + new List { new ElseIfStatement(BooleanExpression(false, async), TEXT("c")) } ); var sw = new StringWriter(); @@ -100,14 +112,16 @@ public async Task IfProcessElseWhenNoOther() Assert.Equal("b", sw.ToString()); } - [Fact] - public async Task IfProcessElseIf() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task IfProcessElseIf(bool async) { var e = new IfStatement( - FALSE, + BooleanExpression(false, async), TEXT("a"), new ElseStatement(TEXT("b")), - new List { new ElseIfStatement(TRUE, TEXT("c")) } + new List { new ElseIfStatement(BooleanExpression(true, async), TEXT("c")) } ); var sw = new StringWriter(); @@ -116,16 +130,18 @@ public async Task IfProcessElseIf() Assert.Equal("c", sw.ToString()); } - [Fact] - public async Task IfProcessMultipleElseIf() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task IfProcessMultipleElseIf(bool async) { var e = new IfStatement( - FALSE, + BooleanExpression(false, async), TEXT("a"), new ElseStatement(TEXT("b")), new List { - new ElseIfStatement(FALSE, TEXT("c")), - new ElseIfStatement(TRUE, TEXT("d"))} + new ElseIfStatement(BooleanExpression(false, async), TEXT("c")), + new ElseIfStatement(BooleanExpression(true, async), TEXT("d"))} ); var sw = new StringWriter(); @@ -134,16 +150,18 @@ public async Task IfProcessMultipleElseIf() Assert.Equal("d", sw.ToString()); } - [Fact] - public async Task IfProcessFirstElseIf() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task IfProcessFirstElseIf(bool async) { var e = new IfStatement( - FALSE, + BooleanExpression(false, async), TEXT("a"), new ElseStatement(TEXT("b")), new List { - new ElseIfStatement(TRUE, TEXT("c")), - new ElseIfStatement(TRUE, TEXT("d"))} + new ElseIfStatement(BooleanExpression(true, async), TEXT("c")), + new ElseIfStatement(BooleanExpression(true, async), TEXT("d"))} ); var sw = new StringWriter(); @@ -152,16 +170,18 @@ public async Task IfProcessFirstElseIf() Assert.Equal("c", sw.ToString()); } - [Fact] - public async Task IfProcessNoMatchElseIf() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task IfProcessNoMatchElseIf(bool async) { var e = new IfStatement( - FALSE, + BooleanExpression(false, async: false), TEXT("a"), null, new List { - new ElseIfStatement(FALSE, TEXT("c")), - new ElseIfStatement(FALSE, TEXT("d"))} + new ElseIfStatement(BooleanExpression(false, async), TEXT("c")), + new ElseIfStatement(BooleanExpression(false, async), TEXT("d"))} ); var sw = new StringWriter(); @@ -169,5 +189,42 @@ public async Task IfProcessNoMatchElseIf() Assert.Equal("", sw.ToString()); } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task IfProcessAwaitedAndElse(bool async) + { + var e = new IfStatement( + BooleanExpression(false, async: false), + TEXT("a"), + new ElseStatement(TEXT("c")), + new List + { + new ElseIfStatement(BooleanExpression(false, async), TEXT("b")) + } + ); + + var sw = new StringWriter(); + await e.WriteToAsync(sw, HtmlEncoder.Default, new TemplateContext()); + + Assert.Equal("c", sw.ToString()); + } + } + + sealed class AwaitedExpression : Expression + { + private readonly FluidValue _result; + + public AwaitedExpression(FluidValue result) + { + _result = result; + } + + public override async ValueTask EvaluateAsync(TemplateContext context) + { + await Task.Delay(10); + return _result; + } } } diff --git a/Fluid/Ast/IfStatement.cs b/Fluid/Ast/IfStatement.cs index e07af439..e081294b 100644 --- a/Fluid/Ast/IfStatement.cs +++ b/Fluid/Ast/IfStatement.cs @@ -65,8 +65,7 @@ public override ValueTask WriteToAsync(TextWriter writer, TextEncode var elseIfConditionTask = elseIf.Condition.EvaluateAsync(context); if (!elseIfConditionTask.IsCompletedSuccessfully) { - var writeTask = elseIf.WriteToAsync(writer, encoder, context); - return AwaitedElseBranch(elseIfConditionTask, writeTask, writer, encoder, context, i + 1); + return AwaitedElseBranch(elseIf, elseIfConditionTask, elseIfTask: null, writer, encoder, context, i + 1); } if (elseIfConditionTask.Result.ToBooleanValue()) @@ -74,7 +73,7 @@ public override ValueTask WriteToAsync(TextWriter writer, TextEncode var writeTask = elseIf.WriteToAsync(writer, encoder, context); if (!writeTask.IsCompletedSuccessfully) { - return AwaitedElseBranch(elseIfConditionTask, writeTask, writer, encoder, context, i + 1); + return AwaitedElseBranch(elseIf, elseIfConditionTask, writeTask, writer, encoder, context, i + 1); } return new ValueTask(writeTask.Result); @@ -139,15 +138,16 @@ private async ValueTask Awaited( } else { - await AwaitedElseBranch(new ValueTask(BooleanValue.False), new ValueTask(), writer, encoder, context, startIndex: 0); + await AwaitedElseBranch(null, new ValueTask(BooleanValue.False), new ValueTask(), writer, encoder, context, startIndex: 0); } return Completion.Normal; } private async ValueTask AwaitedElseBranch( + ElseIfStatement elseIf, ValueTask conditionTask, - ValueTask elseIfTask, + ValueTask? elseIfTask, TextWriter writer, TextEncoder encoder, TemplateContext context, @@ -156,12 +156,12 @@ private async ValueTask AwaitedElseBranch( bool condition = (await conditionTask).ToBooleanValue(); if (condition) { - await elseIfTask; + return await (elseIfTask ?? elseIf.WriteToAsync(writer, encoder, context)); } for (var i = startIndex; i < _elseIfStatements.Count; i++) { - var elseIf = _elseIfStatements[i]; + elseIf = _elseIfStatements[i]; if ((await elseIf.Condition.EvaluateAsync(context)).ToBooleanValue()) { return await elseIf.WriteToAsync(writer, encoder, context);