Skip to content

Commit ba472fc

Browse files
Avoid stack overflow in stack overflow prevention code - fixes #1047
1 parent 781de29 commit ba472fc

3 files changed

Lines changed: 48 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1010

1111
### VB -> C#
1212

13+
* Avoid stack overflow in stack overflow prevention code [#1047](https://github.com/icsharpcode/CodeConverter/issues/1047)
14+
* Convert optional DateTime parameters [#1056](https://github.com/icsharpcode/CodeConverter/issues/1056)
1315
* Convert optional parameters before ref parameters using attributes to avoid compile error [#1057](https://github.com/icsharpcode/CodeConverter/issues/1057)
1416
* Remove square brackets when escaping labels and identifiers [#1043](https://github.com/icsharpcode/CodeConverter/issues/1043) and [#1044](https://github.com/icsharpcode/CodeConverter/issues/1044)
1517
* Exit Property now returns value assigned to return variable [#1051](https://github.com/icsharpcode/CodeConverter/issues/1051)

CodeConverter/CSharp/ExpressionNodeVisitor.cs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Data;
33
using System.Globalization;
44
using System.Linq.Expressions;
5+
using System.Runtime.CompilerServices;
56
using System.Xml.Linq;
67
using ICSharpCode.CodeConverter.CSharp.Replacements;
78
using ICSharpCode.CodeConverter.Util.FromRoslyn;
@@ -845,30 +846,35 @@ private CSharpSyntaxNode ConvertAddressOf(VBSyntax.UnaryExpressionSyntax node, E
845846
return expr;
846847
}
847848

848-
public override async Task<CSharpSyntaxNode> VisitBinaryExpression(VBasic.Syntax.BinaryExpressionSyntax node)
849+
public override async Task<CSharpSyntaxNode> VisitBinaryExpression(VBasic.Syntax.BinaryExpressionSyntax entryNode)
849850
{
850-
// Manually walk tree bottom up to avoid stack overflow for deeply nested binary expressions like 3 + 4 + 5 + ...
851+
// Walk down the syntax tree for deeply nested binary expressions to avoid stack overflow
852+
// e.g. 3 + 4 + 5 + ...
851853
// Test "DeeplyNestedBinaryExpressionShouldNotStackOverflowAsync()" skipped because it's too slow
852-
while (node.Left is VBSyntax.BinaryExpressionSyntax l) {
853-
node = l;
854-
}
855854

856855
ExpressionSyntax csLhs = null;
857-
for (; node is not null; node = BinaryParentWithLhs(node)) {
858-
csLhs = (ExpressionSyntax) await ConvertBinaryExpressionAsync(node, csLhs);
856+
int levelsToConvert = 0;
857+
VBSyntax.BinaryExpressionSyntax currentNode = entryNode;
858+
859+
// Walk down the nested levels to count them
860+
for (var nextNode = entryNode; nextNode != null; currentNode = nextNode, nextNode = currentNode.Left as VBSyntax.BinaryExpressionSyntax, levelsToConvert++) {
861+
// Don't go beyond a rewritten operator because that code has many paths that can call VisitBinaryExpression. Passing csLhs through all of that would harm the code quality more than it's worth to help that edge case.
862+
if (await RewriteBinaryOperatorOrNullAsync(nextNode) is { } operatorNode) {
863+
csLhs = operatorNode;
864+
break;
865+
}
859866
}
860867

861-
return csLhs;
868+
// Walk back up the same levels converting as we go.
869+
for (; levelsToConvert > 0; currentNode = currentNode!.Parent as VBSyntax.BinaryExpressionSyntax, levelsToConvert--) {
870+
csLhs = (ExpressionSyntax)await ConvertBinaryExpressionAsync(currentNode, csLhs);
871+
}
862872

863-
VBSyntax.BinaryExpressionSyntax BinaryParentWithLhs(SyntaxNode currentSyntaxNode) => currentSyntaxNode.Parent is VBSyntax.BinaryExpressionSyntax {Left: var l} p && l == currentSyntaxNode ? p : null;
873+
return csLhs;
864874
}
865875

866876
private async Task<CSharpSyntaxNode> ConvertBinaryExpressionAsync(VBasic.Syntax.BinaryExpressionSyntax node, ExpressionSyntax lhs = null, ExpressionSyntax rhs = null)
867877
{
868-
if (await _operatorConverter.ConvertRewrittenBinaryOperatorOrNullAsync(node, TriviaConvertingExpressionVisitor.IsWithinQuery) is { } operatorNode) {
869-
return operatorNode;
870-
}
871-
872878
lhs ??= await node.Left.AcceptAsync<ExpressionSyntax>(TriviaConvertingExpressionVisitor);
873879
rhs ??= await node.Right.AcceptAsync<ExpressionSyntax>(TriviaConvertingExpressionVisitor);
874880

@@ -926,6 +932,9 @@ private async Task<CSharpSyntaxNode> ConvertBinaryExpressionAsync(VBasic.Syntax.
926932
return node.Parent.IsKind(VBasic.SyntaxKind.SimpleArgument) ? exp : exp.AddParens();
927933
}
928934

935+
private async Task<ExpressionSyntax> RewriteBinaryOperatorOrNullAsync(VBSyntax.BinaryExpressionSyntax node) =>
936+
await _operatorConverter.ConvertRewrittenBinaryOperatorOrNullAsync(node, TriviaConvertingExpressionVisitor.IsWithinQuery);
937+
929938
private async Task<CSharpSyntaxNode> WithRemovedRedundantConversionOrNullAsync(VBSyntax.InvocationExpressionSyntax conversionNode, ISymbol invocationSymbol)
930939
{
931940
if (invocationSymbol?.ContainingNamespace.MetadataName != nameof(Microsoft.VisualBasic) ||

Tests/CSharp/ExpressionTests/BinaryExpressionTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,30 @@ public void Foo()
724724
}");
725725
}
726726

727+
[Fact]
728+
public async Task RewrittenObjectOperatorDoesNotStackOverflowAsync()
729+
{
730+
await TestConversionVisualBasicToCSharpAsync(@"
731+
Sub S()
732+
Dim a As Object
733+
Dim b As Object
734+
If (1=Integer.Parse(""1"") And b And a = 1) Then
735+
Else If (a = 1 Or b Or a = 2 Or a = 3) Then
736+
End If
737+
End Sub", @"
738+
public void S()
739+
{
740+
var a = default(object);
741+
var b = default(object);
742+
if (Conversions.ToBoolean(Operators.AndObject(Operators.AndObject(1 == int.Parse(""1""), b), Operators.ConditionalCompareObjectEqual(a, 1, false))))
743+
{
744+
}
745+
else if (Conversions.ToBoolean(Operators.OrObject(Operators.OrObject(Operators.OrObject(Operators.ConditionalCompareObjectEqual(a, 1, false), b), Operators.ConditionalCompareObjectEqual(a, 2, false)), Operators.ConditionalCompareObjectEqual(a, 3, false))))
746+
{
747+
}
748+
}");
749+
}
750+
727751
[Fact(Skip = "Too slow")]
728752
public async Task DeeplyNestedBinaryExpressionShouldNotStackOverflowAsync()
729753
{

0 commit comments

Comments
 (0)