Skip to content

Commit

Permalink
break from multiple try
Browse files Browse the repository at this point in the history
  • Loading branch information
Hecate2 committed Jan 16, 2025
1 parent 980c6e7 commit 1eceb00
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 62 deletions.
47 changes: 27 additions & 20 deletions src/Neo.Compiler.CSharp/MethodConvert/Statement/BreakStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

using Microsoft.CodeAnalysis.CSharp.Syntax;
using Neo.VM;
using System.Collections.Generic;
using System.Linq;

namespace Neo.Compiler
{
Expand Down Expand Up @@ -45,31 +47,36 @@ private void ConvertBreakStatement(BreakStatementSyntax syntax)
{
using (InsertSequencePoint(syntax))
{
int nestedTryWithFinally = 0;
JumpTarget? breakTarget = null;
List<StatementContext> visitedTry = []; // from shallow to deep
foreach (StatementContext sc in _generalStatementStack)
{
{// start from the deepest context
// find the final break target
if (sc.BreakTarget != null)
{
if (nestedTryWithFinally == 0)
Jump(OpCode.JMP_L, sc.BreakTarget);
else
Jump(OpCode.ENDTRY_L, sc.BreakTarget);
return;
}
if (sc.StatementSyntax is TryStatementSyntax && sc.FinallyTarget != null)
{
if (nestedTryWithFinally > 0)
throw new CompilationException(sc.StatementSyntax, DiagnosticId.SyntaxNotSupported, "Neo VM does not support `break` from multi-layered nested try-catch with finally.");
if (sc.TryState != ExceptionHandlingState.Finally)
nestedTryWithFinally++;
else // Not likely to happen. C# syntax analyzer should forbid break in finally
throw new CompilationException(sc.StatementSyntax, DiagnosticId.SyntaxNotSupported, "Cannot break in finally.");
breakTarget = sc.BreakTarget;
break;
}
// stage the try stacks on the way
if (sc.StatementSyntax is TryStatementSyntax)
visitedTry.Add(sc);
}
// break is not handled
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Cannot find what to break. " +
$"If not syntax error, this is probably a compiler bug. " +
$"Check whether the compiler is leaving out a push into {nameof(_generalStatementStack)}.");
if (breakTarget == null)
// break is not handled
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Cannot find what to break. " +
$"If not syntax error, this is probably a compiler bug. " +
$"Check whether the compiler is leaving out a push into {nameof(_generalStatementStack)}.");

foreach (StatementContext sc in visitedTry)
// start from the most external try
// internal try should ENDTRY, targeting the correct external break target
breakTarget = sc.AddEndTry(breakTarget);

Jump(OpCode.JMP_L, breakTarget);
// We could use ENDTRY if current statement calling `break` is a try statement,
// but this job can be done by the optimizer
// Note that, do not Jump(OpCode.ENDTRY_L, breakTarget) here,
// because the breakTarget here is already an ENDTRY_L for current try stack.
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ private void ConvertIteratorForEachStatement(SemanticModel model, ForEachStateme
PopContinueTarget();
PopBreakTarget();
if (_generalStatementStack.Pop() != sc)
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside.");
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside. This is a compiler bug.");
}

/// <summary>
Expand Down Expand Up @@ -197,7 +197,7 @@ private void ConvertIteratorForEachVariableStatement(SemanticModel model, ForEac
PopContinueTarget();
PopBreakTarget();
if (_generalStatementStack.Pop() != sc)
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside.");
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside. This is a compiler bug.");
}

/// <summary>
Expand Down Expand Up @@ -266,7 +266,7 @@ private void ConvertArrayForEachStatement(SemanticModel model, ForEachStatementS
PopContinueTarget();
PopBreakTarget();
if (_generalStatementStack.Pop() != sc)
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside.");
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside. This is a compiler bug.");
}

/// <summary>
Expand Down Expand Up @@ -349,7 +349,7 @@ private void ConvertArrayForEachVariableStatement(SemanticModel model, ForEachVa
PopContinueTarget();
PopBreakTarget();
if (_generalStatementStack.Pop() != sc)
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside.");
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside. This is a compiler bug.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ private void ConvertDoStatement(SemanticModel model, DoStatementSyntax syntax)
PopContinueTarget();
PopBreakTarget();
if (_generalStatementStack.Pop() != sc)
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside.");
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside. This is a compiler bug.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ private void ConvertForStatement(SemanticModel model, ForStatementSyntax syntax)
PopContinueTarget();
PopBreakTarget();
if (_generalStatementStack.Pop() != sc)
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside.");
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside. This is a compiler bug.");
}
}
}
28 changes: 24 additions & 4 deletions src/Neo.Compiler.CSharp/MethodConvert/Statement/Statement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Neo.VM;
using Org.BouncyCastle.Asn1.X509;
using System.Collections.Generic;
using System.Linq;

namespace Neo.Compiler
{
Expand Down Expand Up @@ -39,19 +41,37 @@ internal class StatementContext(StatementSyntax statementSyntax,
public readonly JumpTarget? EndFinallyTarget = endFinallyTarget;
public /*readonly*/ Dictionary<ILabelSymbol, JumpTarget>? GotoLabels = gotoLabels;
public /*readonly*/ Dictionary<SwitchLabelSyntax, JumpTarget>? SwitchLabels = switchLabels;
// handles `break`, `continue` and `goto` in multi-layered nested try with finally
// key: target of this ENDTRY
// value: this ENDTRY
public Dictionary<JumpTarget, JumpTarget>? AdditionalEndTryTargetToInstruction { get; protected set; } = null;
//public readonly StatementSyntax? ParentStatement = parentStatement;
//public readonly HashSet<StatementSyntax>? ChildrenStatements = childrenStatements;

/// <param name="target">Jump target of this added ENDTRY</param>
/// <returns>The added ENDTRY</returns>
/// <exception cref="CompilationException"></exception>
public JumpTarget AddEndTry(JumpTarget target)
{
if (StatementSyntax is not TryStatementSyntax)
throw new CompilationException(StatementSyntax, DiagnosticId.SyntaxNotSupported, $"Can only append ENDTRY for TryStatement. Got {typeof(StatementSyntax)} {StatementSyntax}. This is a compiler bug.");
AdditionalEndTryTargetToInstruction ??= [];
if (AdditionalEndTryTargetToInstruction.TryGetValue(target, out JumpTarget? existingEndTry))
return existingEndTry;
Instruction i = new() { OpCode = OpCode.ENDTRY_L, Target = target };
existingEndTry = new JumpTarget() { Instruction = i };
AdditionalEndTryTargetToInstruction.Add(target, existingEndTry);
return existingEndTry;
}

public bool AddLabel(ILabelSymbol label, JumpTarget target)
{
if (GotoLabels == null)
GotoLabels = [];
GotoLabels ??= [];
return GotoLabels.TryAdd(label, target);
}
public bool AddLabel(SwitchLabelSyntax label, JumpTarget target)
{
if (SwitchLabels == null)
SwitchLabels = [];
SwitchLabels ??= [];
return SwitchLabels.TryAdd(label, target);
}
public bool ContainsLabel(ILabelSymbol label) => GotoLabels is not null && GotoLabels.ContainsKey(label);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ private void ConvertSwitchStatement(SemanticModel model, SwitchStatementSyntax s
PopSwitchLabels();
PopBreakTarget();
if (_generalStatementStack.Pop() != sc)
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside.");
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside. This is a compiler bug.");
}
}
}
12 changes: 11 additions & 1 deletion src/Neo.Compiler.CSharp/MethodConvert/Statement/TryStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ private void ConvertTryStatement(SemanticModel model, TryStatementSyntax syntax)
_tryStack.Push(new ExceptionHandling { State = ExceptionHandlingState.Try });
ConvertStatement(model, syntax.Block);
Jump(OpCode.ENDTRY_L, endTarget);
if (syntax.Catches.Count == 0 && sc.AdditionalEndTryTargetToInstruction != null)
// handles `break`, `continue` and `goto` in multi-layered nested try with finally
foreach (JumpTarget i in sc.AdditionalEndTryTargetToInstruction.Values)
AddInstruction(i.Instruction!);

if (syntax.Catches.Count > 1)
throw new CompilationException(syntax.Catches[1], DiagnosticId.MultiplyCatches, "Only support one single catch.");
if (syntax.Catches.Count > 0)
Expand Down Expand Up @@ -86,6 +91,11 @@ private void ConvertTryStatement(SemanticModel model, TryStatementSyntax syntax)
_exceptionStack.Push(exceptionIndex);
ConvertStatement(model, catchClause.Block);
Jump(OpCode.ENDTRY_L, endTarget);
if (sc.AdditionalEndTryTargetToInstruction != null)
// handles `break`, `continue` and `goto` in multi-layered nested try with finally
foreach (JumpTarget i in sc.AdditionalEndTryTargetToInstruction.Values)
AddInstruction(i.Instruction!);

if (exceptionSymbol is null)
RemoveAnonymousVariable(exceptionIndex);
else
Expand All @@ -103,7 +113,7 @@ private void ConvertTryStatement(SemanticModel model, TryStatementSyntax syntax)
endTarget.Instruction = AddInstruction(OpCode.NOP);
_tryStack.Pop();
if (_generalStatementStack.Pop() != sc)
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside.");
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside. This is a compiler bug.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ private void ConvertWhileStatement(SemanticModel model, WhileStatementSyntax syn
PopContinueTarget();
PopBreakTarget();
if (_generalStatementStack.Pop() != sc)
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside.");
throw new CompilationException(syntax, DiagnosticId.SyntaxNotSupported, $"Bad statement stack handling inside. This is a compiler bug.");
}
}
}
24 changes: 19 additions & 5 deletions tests/Neo.Compiler.CSharp.TestContracts/Contract_Break.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ public static void BreakInTryCatch()
finally { Storage.Put("\xff\x00", "\x01"); }
ExecutionEngine.Assert(Storage.Get("\xff\x00")! == "\x01");
foreach (object i in Storage.Find("\xff"))
try { throw new System.Exception(); }
try
{
for (int j = 0; j < 3;)
throw new System.Exception();
}
catch
{
do { break; }
while (true);
Storage.Put("\xff\x00", "\x00");
break; // break foreach; should execute finally
try { break; } // break foreach; should execute finally
finally { Storage.Put("\xff\x00", "\x00"); }
}
finally
{
Expand All @@ -37,14 +41,24 @@ public static void BreakInTryCatch()
{
for (int j = 0; j < 3;)
break;
throw new System.Exception();
}
catch
{
int j = 0;
while (j < 3)
break;
break; // foreach; should execute finally
ExecutionEngine.Assert(j == 0);
try
{
Storage.Put("\xff\x00", "\x03");
throw;
}
catch { break; } // foreach; should execute finally
finally
{
ExecutionEngine.Assert(Storage.Get("\xff\x00")! == "\x03");
Storage.Put("\xff\x00", "\x02");
}
}
finally
{
Expand Down
Loading

0 comments on commit 1eceb00

Please sign in to comment.